Adds LRA haptic support (open-loop only for now).
This commit is contained in:
+48
-17
@@ -28,24 +28,55 @@ namespace drivers {
|
||||
static constexpr char kTag[] = "haptics";
|
||||
static constexpr uint8_t kHapticsAddress = 0x5A;
|
||||
|
||||
Haptics::Haptics() {
|
||||
Haptics::Haptics(const std::variant<ErmMotor, LraMotor>& motor) {
|
||||
PowerUp();
|
||||
|
||||
// Put into ERM Open Loop:
|
||||
// (§8.5.4.1 Programming for ERM Open-Loop Operation)
|
||||
// - Turn off N_ERM_LRA first
|
||||
WriteRegister(Register::kControl1,
|
||||
static_cast<uint8_t>(RegisterDefaults::kControl1) &
|
||||
(~ControlMask::kNErmLra));
|
||||
// - Turn on ERM_OPEN_LOOP
|
||||
WriteRegister(Register::kControl3,
|
||||
static_cast<uint8_t>(RegisterDefaults::kControl3) |
|
||||
ControlMask::kErmOpenLoop);
|
||||
// TODO: Break out into helper functions
|
||||
// TODO: Use LRA closed loop instead, loading in calibration data from NVS, or
|
||||
// running calibration + storing the result first if necessary.
|
||||
|
||||
// Set library
|
||||
// TODO(robin): try the other libraries and test response. C is marginal, D
|
||||
// too much?
|
||||
WriteRegister(Register::kWaveformLibrary, static_cast<uint8_t>(kDefaultLibrary));
|
||||
if (std::holds_alternative<ErmMotor>(motor)) {
|
||||
ESP_LOGI(kTag, "Setting up ERM motor...");
|
||||
|
||||
// Put into ERM Open Loop:
|
||||
// (§8.5.4.1 Programming for ERM Open-Loop Operation)
|
||||
|
||||
// - Turn off N_ERM_LRA first
|
||||
WriteRegister(Register::kFeedbackControl,
|
||||
static_cast<uint8_t>(RegisterDefaults::kFeedbackControl) &
|
||||
(~ControlMask::kNErmLra));
|
||||
|
||||
// - Turn on ERM_OPEN_LOOP
|
||||
WriteRegister(Register::kControl3,
|
||||
static_cast<uint8_t>(RegisterDefaults::kControl3) |
|
||||
ControlMask::kErmOpenLoop);
|
||||
|
||||
// Set library
|
||||
// TODO(robin): try the other libraries and test response. C is marginal, D
|
||||
// too much?
|
||||
WriteRegister(Register::kWaveformLibrary, static_cast<uint8_t>(kDefaultErmLibrary));
|
||||
|
||||
} else if (std::holds_alternative<LraMotor>(motor)) {
|
||||
ESP_LOGI(kTag, "Setting up LRA motor...");
|
||||
// TODO:
|
||||
// auto lraInit = std::get<LraMotor>(motor);
|
||||
|
||||
// Put into LRA Open Loop:
|
||||
// (§8.5.4.1 Programming for LRA Open-Loop Operation)
|
||||
|
||||
// - Turn on N_ERM_LRA first
|
||||
WriteRegister(Register::kFeedbackControl,
|
||||
static_cast<uint8_t>(RegisterDefaults::kFeedbackControl) &
|
||||
(ControlMask::kNErmLra));
|
||||
|
||||
// - Turn on LRA_OPEN_LOOP
|
||||
WriteRegister(Register::kControl3,
|
||||
static_cast<uint8_t>(RegisterDefaults::kControl3) |
|
||||
ControlMask::kLraOpenLoop);
|
||||
|
||||
// Set library; only option is the LRA one for, well, LRA motors.
|
||||
WriteRegister(Register::kWaveformLibrary, static_cast<uint8_t>(Library::LRA));
|
||||
}
|
||||
|
||||
// Set mode (internal trigger, on writing 1 to Go register)
|
||||
WriteRegister(Register::kMode, static_cast<uint8_t>(Mode::kInternalTrigger));
|
||||
@@ -94,13 +125,13 @@ auto Haptics::SetWaveformEffect(Effect effect) -> void {
|
||||
|
||||
|
||||
auto Haptics::TourEffects() -> void {
|
||||
TourEffects(Effect::kFirst, Effect::kLast, kDefaultLibrary);
|
||||
TourEffects(Effect::kFirst, Effect::kLast, kDefaultErmLibrary);
|
||||
}
|
||||
auto Haptics::TourEffects(Library lib) -> void {
|
||||
TourEffects(Effect::kFirst, Effect::kLast, lib);
|
||||
}
|
||||
auto Haptics::TourEffects(Effect from, Effect to) -> void {
|
||||
TourEffects(from, to, kDefaultLibrary);
|
||||
TourEffects(from, to, kDefaultErmLibrary);
|
||||
}
|
||||
auto Haptics::TourEffects(Effect from, Effect to, Library lib) -> void {
|
||||
ESP_LOGI(kTag, "With library #%u...", static_cast<uint8_t>(lib));
|
||||
|
||||
@@ -11,13 +11,24 @@
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
namespace drivers {
|
||||
|
||||
typedef std::monostate ErmMotor;
|
||||
struct LraMotor {
|
||||
// TODO: fill out with calibration data from https://www.ti.com/lit/ds/symlink/drv2605l.pdf
|
||||
bool hi;
|
||||
};
|
||||
|
||||
class Haptics {
|
||||
public:
|
||||
static auto Create() -> Haptics* { return new Haptics(); }
|
||||
Haptics();
|
||||
static auto Create(const std::variant<ErmMotor, LraMotor>& motor)
|
||||
-> Haptics* {
|
||||
return new Haptics(motor);
|
||||
}
|
||||
|
||||
Haptics(const std::variant<ErmMotor, LraMotor>& motor);
|
||||
~Haptics();
|
||||
|
||||
// Not copyable or movable.
|
||||
@@ -169,11 +180,12 @@ class Haptics {
|
||||
B = 2, // 3V, Rise: 40-60ms, Brake: 5-15ms
|
||||
C = 3, // 3V, Rise: 60-80ms, Brake: 10-20ms
|
||||
D = 4, // 3V, Rise: 100-140ms, Brake: 15-25ms
|
||||
E = 5 // 3V, Rise: >140ms, Brake: >30ms
|
||||
// 6 is LRA-only, 7 is 4.5V+
|
||||
E = 5, // 3V, Rise: >140ms, Brake: >30ms
|
||||
LRA = 6
|
||||
// 7 is 4.5V+
|
||||
};
|
||||
|
||||
static constexpr Library kDefaultLibrary = Library::C;
|
||||
static constexpr Library kDefaultErmLibrary = Library::C;
|
||||
|
||||
auto PowerDown() -> void;
|
||||
auto Reset() -> void;
|
||||
@@ -216,8 +228,10 @@ class Haptics {
|
||||
struct ControlMask {
|
||||
// FeedbackControl
|
||||
static constexpr uint8_t kNErmLra = 0b10000000;
|
||||
|
||||
// Control3
|
||||
static constexpr uint8_t kErmOpenLoop = 0b00100000;
|
||||
static constexpr uint8_t kLraOpenLoop = 0b00000001;
|
||||
};
|
||||
|
||||
// §8.6 Register Map
|
||||
|
||||
@@ -68,6 +68,7 @@ class NvsStorage {
|
||||
auto Read() -> void;
|
||||
auto Write() -> bool;
|
||||
|
||||
// Hardware Compatibility
|
||||
auto LockPolarity() -> bool;
|
||||
auto LockPolarity(bool) -> void;
|
||||
|
||||
@@ -76,6 +77,10 @@ class NvsStorage {
|
||||
auto DisplaySize(std::pair<std::optional<uint16_t>, std::optional<uint16_t>>)
|
||||
-> void;
|
||||
|
||||
auto HapticMotorIsErm() -> bool;
|
||||
auto HapticMotorIsErm(bool) -> void;
|
||||
// /Hardware Compatibility
|
||||
|
||||
auto PreferredBluetoothDevice() -> std::optional<bluetooth::MacAndName>;
|
||||
auto PreferredBluetoothDevice(std::optional<bluetooth::MacAndName>) -> void;
|
||||
|
||||
@@ -130,6 +135,7 @@ class NvsStorage {
|
||||
Setting<uint8_t> lock_polarity_;
|
||||
Setting<uint16_t> display_cols_;
|
||||
Setting<uint16_t> display_rows_;
|
||||
Setting<uint8_t> haptic_motor_type_;
|
||||
|
||||
Setting<uint8_t> brightness_;
|
||||
Setting<uint8_t> sensitivity_;
|
||||
|
||||
@@ -39,6 +39,7 @@ static constexpr char kKeyScrollSensitivity[] = "scroll";
|
||||
static constexpr char kKeyLockPolarity[] = "lockpol";
|
||||
static constexpr char kKeyDisplayCols[] = "dispcols";
|
||||
static constexpr char kKeyDisplayRows[] = "disprows";
|
||||
static constexpr char kKeyHapticMotorType[] = "hapticmtype";
|
||||
static constexpr char kKeyDbAutoIndex[] = "dbautoindex";
|
||||
|
||||
static auto nvs_get_string(nvs_handle_t nvs, const char* key)
|
||||
@@ -166,6 +167,7 @@ NvsStorage::NvsStorage(nvs_handle_t handle)
|
||||
lock_polarity_(kKeyLockPolarity),
|
||||
display_cols_(kKeyDisplayCols),
|
||||
display_rows_(kKeyDisplayRows),
|
||||
haptic_motor_type_(kKeyHapticMotorType),
|
||||
brightness_(kKeyBrightness),
|
||||
sensitivity_(kKeyScrollSensitivity),
|
||||
amp_max_vol_(kKeyAmpMaxVolume),
|
||||
@@ -188,6 +190,7 @@ auto NvsStorage::Read() -> void {
|
||||
lock_polarity_.read(handle_);
|
||||
display_cols_.read(handle_);
|
||||
display_rows_.read(handle_);
|
||||
haptic_motor_type_.read(handle_),
|
||||
brightness_.read(handle_);
|
||||
sensitivity_.read(handle_);
|
||||
amp_max_vol_.read(handle_);
|
||||
@@ -205,6 +208,7 @@ auto NvsStorage::Write() -> bool {
|
||||
lock_polarity_.write(handle_);
|
||||
display_cols_.write(handle_);
|
||||
display_rows_.write(handle_);
|
||||
haptic_motor_type_.write(handle_),
|
||||
brightness_.write(handle_);
|
||||
sensitivity_.write(handle_);
|
||||
amp_max_vol_.write(handle_);
|
||||
@@ -243,6 +247,16 @@ auto NvsStorage::LockPolarity(bool p) -> void {
|
||||
lock_polarity_.set(p);
|
||||
}
|
||||
|
||||
auto NvsStorage::HapticMotorIsErm() -> bool {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
return haptic_motor_type_.get().value_or(0) > 0;
|
||||
}
|
||||
|
||||
auto NvsStorage::HapticMotorIsErm(bool p) -> void {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
haptic_motor_type_.set(p);
|
||||
}
|
||||
|
||||
auto NvsStorage::DisplaySize()
|
||||
-> std::pair<std::optional<uint16_t>, std::optional<uint16_t>> {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
|
||||
@@ -62,6 +62,10 @@ auto Booting::entry() -> void {
|
||||
sServices->nvs(
|
||||
std::unique_ptr<drivers::NvsStorage>(drivers::NvsStorage::OpenSync()));
|
||||
|
||||
// HACK: tell the unit that it has an ERM motor (we will likely default to
|
||||
// LRAs in future, but all the current units in the field use ERMs.)
|
||||
sServices->nvs().HapticMotorIsErm(true);
|
||||
|
||||
// HACK: fix up the switch polarity on newer dev units
|
||||
// sServices->nvs().LockPolarity(false);
|
||||
|
||||
@@ -85,7 +89,11 @@ auto Booting::entry() -> void {
|
||||
sServices->samd(std::unique_ptr<drivers::Samd>(drivers::Samd::Create()));
|
||||
sServices->touchwheel(
|
||||
std::unique_ptr<drivers::TouchWheel>{drivers::TouchWheel::Create()});
|
||||
sServices->haptics(std::make_unique<drivers::Haptics>());
|
||||
sServices->haptics(std::make_unique<drivers::Haptics>(
|
||||
sServices->nvs().HapticMotorIsErm()
|
||||
? std::variant<drivers::ErmMotor, drivers::LraMotor>(
|
||||
drivers::ErmMotor())
|
||||
: drivers::LraMotor()));
|
||||
|
||||
auto adc = drivers::AdcBattery::Create();
|
||||
sServices->battery(std::make_unique<battery::Battery>(
|
||||
|
||||
Reference in New Issue
Block a user