|
|
@ -29,11 +29,8 @@ static constexpr char kTag[] = "haptics"; |
|
|
|
static constexpr uint8_t kHapticsAddress = 0x5A; |
|
|
|
static constexpr uint8_t kHapticsAddress = 0x5A; |
|
|
|
|
|
|
|
|
|
|
|
Haptics::Haptics(const std::variant<ErmMotor, LraMotor>& motor) { |
|
|
|
Haptics::Haptics(const std::variant<ErmMotor, LraMotor>& motor) { |
|
|
|
PowerUp(); |
|
|
|
// Bring the driver out of standby, and put it into auto-calibration mode.
|
|
|
|
|
|
|
|
WriteRegister(Register::kMode, 0b00000111); |
|
|
|
// 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (std::holds_alternative<ErmMotor>(motor)) { |
|
|
|
if (std::holds_alternative<ErmMotor>(motor)) { |
|
|
|
ESP_LOGI(kTag, "Setting up ERM motor..."); |
|
|
|
ESP_LOGI(kTag, "Setting up ERM motor..."); |
|
|
@ -59,21 +56,39 @@ Haptics::Haptics(const std::variant<ErmMotor, LraMotor>& motor) { |
|
|
|
|
|
|
|
|
|
|
|
} else if (std::holds_alternative<LraMotor>(motor)) { |
|
|
|
} else if (std::holds_alternative<LraMotor>(motor)) { |
|
|
|
ESP_LOGI(kTag, "Setting up LRA motor..."); |
|
|
|
ESP_LOGI(kTag, "Setting up LRA motor..."); |
|
|
|
// TODO:
|
|
|
|
// TODO: Save and restore calibration data instead of recalibrating each
|
|
|
|
// auto lraInit = std::get<LraMotor>(motor);
|
|
|
|
// boot.
|
|
|
|
|
|
|
|
|
|
|
|
// Put into LRA Open Loop:
|
|
|
|
// Set rated voltage to 1v8; see equation (5) in Section 8.5.2.1.
|
|
|
|
// (§8.5.4.1 Programming for LRA Open-Loop Operation)
|
|
|
|
WriteRegister(Register::kRatedVoltage, 0x46); |
|
|
|
|
|
|
|
|
|
|
|
// - Turn on N_ERM_LRA first
|
|
|
|
// Set overdrive voltage to 2v6 see equation (9) in Section 8.5.2.2. This
|
|
|
|
WriteRegister(Register::kFeedbackControl, |
|
|
|
// is based on a max operating voltage of 1v85 RMS.
|
|
|
|
static_cast<uint8_t>(RegisterDefaults::kFeedbackControl) & |
|
|
|
// FIXME: Could this be higher?
|
|
|
|
(ControlMask::kNErmLra)); |
|
|
|
WriteRegister(Register::kOverdriveClampVoltage, 0x7B); |
|
|
|
|
|
|
|
|
|
|
|
// - Turn on LRA_OPEN_LOOP
|
|
|
|
// Enable LRA mode, with default brake factor, loop gain, and back-EMF.
|
|
|
|
WriteRegister(Register::kControl3, |
|
|
|
WriteRegister(Register::kFeedbackControl, 0b10110110); |
|
|
|
static_cast<uint8_t>(RegisterDefaults::kControl3) | |
|
|
|
|
|
|
|
ControlMask::kLraOpenLoop); |
|
|
|
// Set the drive time for our 235Hz motors. This is a period of 4.2ms, so
|
|
|
|
|
|
|
|
// a drive time of 2.1ms. See Table 24 in section 8.6.21. Leave the other
|
|
|
|
|
|
|
|
// bits in this register with their default values.
|
|
|
|
|
|
|
|
WriteRegister(Register::kControl1, 0b10010000); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Ensure closed-loop mode is set.
|
|
|
|
|
|
|
|
WriteRegister(Register::kControl3, 0b10000000); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Use a short auto-calibration time. For our application, a longer time
|
|
|
|
|
|
|
|
// doesn't seem to affect much.
|
|
|
|
|
|
|
|
WriteRegister(Register::kControl4, 0b00000000); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Start auto-calibration.
|
|
|
|
|
|
|
|
WriteRegister(Register::kGo, 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for calibration to complete.
|
|
|
|
|
|
|
|
do { |
|
|
|
|
|
|
|
vTaskDelay(1); |
|
|
|
|
|
|
|
} while ((ReadRegister(Register::kGo) & 1) > 0); |
|
|
|
|
|
|
|
|
|
|
|
// Set library; only option is the LRA one for, well, LRA motors.
|
|
|
|
// Set library; only option is the LRA one for, well, LRA motors.
|
|
|
|
WriteRegister(Register::kWaveformLibrary, |
|
|
|
WriteRegister(Register::kWaveformLibrary, |
|
|
@ -82,9 +97,6 @@ Haptics::Haptics(const std::variant<ErmMotor, LraMotor>& motor) { |
|
|
|
|
|
|
|
|
|
|
|
// Set mode (internal trigger, on writing 1 to Go register)
|
|
|
|
// Set mode (internal trigger, on writing 1 to Go register)
|
|
|
|
WriteRegister(Register::kMode, static_cast<uint8_t>(Mode::kInternalTrigger)); |
|
|
|
WriteRegister(Register::kMode, static_cast<uint8_t>(Mode::kInternalTrigger)); |
|
|
|
|
|
|
|
|
|
|
|
// Set up a default effect (sequence of one effect)
|
|
|
|
|
|
|
|
SetWaveformEffect(kStartupEffect); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Haptics::~Haptics() {} |
|
|
|
Haptics::~Haptics() {} |
|
|
@ -102,9 +114,31 @@ void Haptics::WriteRegister(Register reg, uint8_t val) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto Haptics::ReadRegister(Register reg) -> uint8_t { |
|
|
|
|
|
|
|
uint8_t regRaw = static_cast<uint8_t>(reg); |
|
|
|
|
|
|
|
uint8_t ret = 0; |
|
|
|
|
|
|
|
I2CTransaction transaction; |
|
|
|
|
|
|
|
transaction.start() |
|
|
|
|
|
|
|
.write_addr(kHapticsAddress, I2C_MASTER_WRITE) |
|
|
|
|
|
|
|
.write_ack(regRaw) |
|
|
|
|
|
|
|
.start() |
|
|
|
|
|
|
|
.write_addr(kHapticsAddress, I2C_MASTER_READ) |
|
|
|
|
|
|
|
.read(&ret, I2C_MASTER_NACK) |
|
|
|
|
|
|
|
.stop(); |
|
|
|
|
|
|
|
esp_err_t res = transaction.Execute(1); |
|
|
|
|
|
|
|
if (res != ESP_OK) { |
|
|
|
|
|
|
|
ESP_LOGW(kTag, "read failed: %s", esp_err_to_name(res)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return ret; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto Haptics::PlayWaveformEffect(Effect effect) -> void { |
|
|
|
auto Haptics::PlayWaveformEffect(Effect effect) -> void { |
|
|
|
const std::lock_guard<std::mutex> lock{playing_effect_}; // locks until freed
|
|
|
|
const std::lock_guard<std::mutex> lock{playing_effect_}; // locks until freed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Interrupt any already-running effect. This makes haptic feedback feel a
|
|
|
|
|
|
|
|
// little bit more responsive.
|
|
|
|
|
|
|
|
WriteRegister(Register::kGo, 0); |
|
|
|
|
|
|
|
|
|
|
|
SetWaveformEffect(effect); |
|
|
|
SetWaveformEffect(effect); |
|
|
|
Go(); |
|
|
|
Go(); |
|
|
|
} |
|
|
|
} |
|
|
|