... with facilities to trigger effects via the system fsm.custom
parent
3abf599c4f
commit
135185f12b
@ -0,0 +1,422 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au>, robin <robin@rhoward.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "haptics.hpp" |
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
#include <initializer_list> |
||||||
|
#include <mutex> |
||||||
|
|
||||||
|
#include "assert.h" |
||||||
|
#include "driver/gpio.h" |
||||||
|
#include "driver/i2c.h" |
||||||
|
#include "esp_err.h" |
||||||
|
#include "esp_log.h" |
||||||
|
#include "freertos/projdefs.h" |
||||||
|
#include "hal/gpio_types.h" |
||||||
|
#include "hal/i2c_types.h" |
||||||
|
|
||||||
|
#include "i2c.hpp" |
||||||
|
|
||||||
|
namespace drivers { |
||||||
|
|
||||||
|
static constexpr char kTag[] = "haptics"; |
||||||
|
static constexpr uint8_t kHapticsAddress = 0x5A; |
||||||
|
|
||||||
|
Haptics::Haptics() { |
||||||
|
// TODO(robin): is this needed?
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(300)); |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
// 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)); |
||||||
|
|
||||||
|
// Set mode (internal trigger, on writing 1 to Go register)
|
||||||
|
WriteRegister(Register::kMode, static_cast<uint8_t>(Mode::kInternalTrigger)); |
||||||
|
|
||||||
|
// Set up a default effect (sequence of one effect)
|
||||||
|
SetWaveformEffect(kStartupEffect); |
||||||
|
} |
||||||
|
|
||||||
|
Haptics::~Haptics() {} |
||||||
|
|
||||||
|
void Haptics::WriteRegister(Register reg, uint8_t val) { |
||||||
|
uint8_t regRaw = static_cast<uint8_t>(reg); |
||||||
|
I2CTransaction transaction; |
||||||
|
transaction.start() |
||||||
|
.write_addr(kHapticsAddress, I2C_MASTER_WRITE) |
||||||
|
.write_ack(regRaw, val) |
||||||
|
.stop(); |
||||||
|
esp_err_t res = transaction.Execute(1); |
||||||
|
if (res != ESP_OK) { |
||||||
|
ESP_LOGW(kTag, "write failed: %s", esp_err_to_name(res)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
auto Haptics::PlayWaveformEffect(Effect effect) -> void { |
||||||
|
const std::lock_guard<std::mutex> lock{playing_effect_}; // locks until freed
|
||||||
|
|
||||||
|
SetWaveformEffect(effect); |
||||||
|
Go(); |
||||||
|
} |
||||||
|
|
||||||
|
// Starts the pre-programmed sequence
|
||||||
|
auto Haptics::Go() -> void { |
||||||
|
WriteRegister(Register::kGo, |
||||||
|
static_cast<uint8_t>(RegisterDefaults::kGo) | 0b00000001); |
||||||
|
} |
||||||
|
|
||||||
|
auto Haptics::SetWaveformEffect(Effect effect) -> void { |
||||||
|
if (!current_effect_ || current_effect_.value() != effect) { |
||||||
|
WriteRegister(Register::kWaveformSequenceSlot1, |
||||||
|
static_cast<uint8_t>(effect)); |
||||||
|
WriteRegister(Register::kWaveformSequenceSlot2, |
||||||
|
static_cast<uint8_t>(Effect::kStop)); |
||||||
|
} |
||||||
|
current_effect_ = effect; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
auto Haptics::TourEffects() -> void { |
||||||
|
TourEffects(Effect::kFirst, Effect::kLast, kDefaultLibrary); |
||||||
|
} |
||||||
|
auto Haptics::TourEffects(Library lib) -> void { |
||||||
|
TourEffects(Effect::kFirst, Effect::kLast, lib); |
||||||
|
} |
||||||
|
auto Haptics::TourEffects(Effect from, Effect to) -> void { |
||||||
|
TourEffects(from, to, kDefaultLibrary); |
||||||
|
} |
||||||
|
auto Haptics::TourEffects(Effect from, Effect to, Library lib) -> void { |
||||||
|
ESP_LOGI(kTag, "With library #%u...", static_cast<uint8_t>(lib)); |
||||||
|
|
||||||
|
for (uint8_t e = static_cast<uint8_t>(from); |
||||||
|
e <= static_cast<uint8_t>(to) && |
||||||
|
e <= static_cast<uint8_t>(Effect::kLast); |
||||||
|
e++) { |
||||||
|
auto effect = static_cast<Effect>(e); |
||||||
|
auto label = EffectToLabel(effect); |
||||||
|
|
||||||
|
if (effect == Effect::kDontUseThis_Longbuzzforprogrammaticstopping_100Pct) { |
||||||
|
ESP_LOGI(kTag, "Ignoring effect '%s'...", label.c_str()); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
ESP_LOGI(kTag, "Playing effect #%u: %s", e, label.c_str()); |
||||||
|
PlayWaveformEffect(effect); |
||||||
|
Go(); |
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(800 /*ms*/)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
auto Haptics::TourLibraries(Effect from, Effect to) -> void { |
||||||
|
for (uint8_t lib = 1; lib <= 5; lib++) { |
||||||
|
WriteRegister(Register::kWaveformLibrary, lib); |
||||||
|
|
||||||
|
for (auto e = static_cast<uint8_t>(Effect::kFirst); |
||||||
|
e <= static_cast<uint8_t>(Effect::kLast); e++) { |
||||||
|
auto effect = static_cast<Effect>(e); |
||||||
|
ESP_LOGI(kTag, "Library %u, Effect: %s", lib, |
||||||
|
EffectToLabel(effect).c_str()); |
||||||
|
|
||||||
|
PlayWaveformEffect(effect); |
||||||
|
Go(); |
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(800 /*ms*/)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
auto Haptics::PowerDown() -> void { |
||||||
|
WriteRegister(Register::kMode, static_cast<uint8_t>(Mode::kInternalTrigger) | |
||||||
|
ModeMask::kStandby); |
||||||
|
} |
||||||
|
|
||||||
|
auto Haptics::Reset() -> void { |
||||||
|
WriteRegister(Register::kMode, static_cast<uint8_t>(Mode::kInternalTrigger) | |
||||||
|
ModeMask::kDevReset); |
||||||
|
} |
||||||
|
|
||||||
|
auto Haptics::PowerUp() -> void { |
||||||
|
// FIXME: technically overwriting the RESERVED bits of Mode, but eh
|
||||||
|
uint8_t newMask = static_cast<uint8_t>(Mode::kInternalTrigger) & |
||||||
|
(~ModeMask::kStandby) & (~ModeMask::kDevReset); |
||||||
|
WriteRegister(Register::kMode, |
||||||
|
static_cast<uint8_t>(RegisterDefaults::kMode) | newMask); |
||||||
|
} |
||||||
|
|
||||||
|
auto Haptics::EffectToLabel(Effect effect) -> std::string { |
||||||
|
switch (static_cast<Effect>(effect)) { |
||||||
|
case Effect::kStrongClick_100Pct: |
||||||
|
return "Strong Click - 100%"; |
||||||
|
case Effect::kStrongClick_60Pct: |
||||||
|
return "Strong Click (60%)"; |
||||||
|
case Effect::kStrongClick_30Pct: |
||||||
|
return "Strong Click (30%)"; |
||||||
|
case Effect::kSharpClick_100Pct: |
||||||
|
return "Sharp Click (100%)"; |
||||||
|
case Effect::kSharpClick_60Pct: |
||||||
|
return "Sharp Click (60%)"; |
||||||
|
case Effect::kSharpClick_30Pct: |
||||||
|
return "Sharp Click (30%)"; |
||||||
|
case Effect::kSoftBump_100Pct: |
||||||
|
return "Soft Bump (100%)"; |
||||||
|
case Effect::kSoftBump_60Pct: |
||||||
|
return "Soft Bump (60%)"; |
||||||
|
case Effect::kSoftBump_30Pct: |
||||||
|
return "Soft Bump (30%)"; |
||||||
|
case Effect::kDoubleClick_100Pct: |
||||||
|
return "Double Click (100%)"; |
||||||
|
case Effect::kDoubleClick_60Pct: |
||||||
|
return "Double Click (60%)"; |
||||||
|
case Effect::kTripleClick_100Pct: |
||||||
|
return "Triple Click (100%)"; |
||||||
|
case Effect::kSoftFuzz_60Pct: |
||||||
|
return "Soft Fuzz (60%)"; |
||||||
|
case Effect::kStrongBuzz_100Pct: |
||||||
|
return "Strong Buzz (100%)"; |
||||||
|
case Effect::k750msAlert_100Pct: |
||||||
|
return "750ms Alert (100%)"; |
||||||
|
case Effect::k1000msAlert_100Pct: |
||||||
|
return "1000ms Alert (100%)"; |
||||||
|
case Effect::kStrongClick1_100Pct: |
||||||
|
return "Strong Click1 (100%)"; |
||||||
|
case Effect::kStrongClick2_80Pct: |
||||||
|
return "Strong Click2 (80%)"; |
||||||
|
case Effect::kStrongClick3_60Pct: |
||||||
|
return "Strong Click3 (60%)"; |
||||||
|
case Effect::kStrongClick4_30Pct: |
||||||
|
return "Strong Click4 (30%)"; |
||||||
|
case Effect::kMediumClick1_100Pct: |
||||||
|
return "Medium Click1 (100%)"; |
||||||
|
case Effect::kMediumClick2_80Pct: |
||||||
|
return "Medium Click2 (80%)"; |
||||||
|
case Effect::kMediumClick3_60Pct: |
||||||
|
return "Medium Click3 (60%)"; |
||||||
|
case Effect::kSharpTick1_100Pct: |
||||||
|
return "Sharp Tick1 (100%)"; |
||||||
|
case Effect::kSharpTick2_80Pct: |
||||||
|
return "Sharp Tick2 (80%)"; |
||||||
|
case Effect::kSharpTick3_60Pct: |
||||||
|
return "Sharp Tick3 (60%)"; |
||||||
|
case Effect::kShortDoubleClickStrong1_100Pct: |
||||||
|
return "Short Double Click Strong1 (100%)"; |
||||||
|
case Effect::kShortDoubleClickStrong2_80Pct: |
||||||
|
return "Short Double Click Strong2 (80%)"; |
||||||
|
case Effect::kShortDoubleClickStrong3_60Pct: |
||||||
|
return "Short Double Click Strong3 (60%)"; |
||||||
|
case Effect::kShortDoubleClickStrong4_30Pct: |
||||||
|
return "Short Double Click Strong4 (30%)"; |
||||||
|
case Effect::kShortDoubleClickMedium1_100Pct: |
||||||
|
return "Short Double Click Medium1 (100%)"; |
||||||
|
case Effect::kShortDoubleClickMedium2_80Pct: |
||||||
|
return "Short Double Click Medium2 (80%)"; |
||||||
|
case Effect::kShortDoubleClickMedium3_60Pct: |
||||||
|
return "Short Double Click Medium3 (60%)"; |
||||||
|
case Effect::kShortDoubleSharpTick1_100Pct: |
||||||
|
return "Short Double Sharp Tick1 (100%)"; |
||||||
|
case Effect::kShortDoubleSharpTick2_80Pct: |
||||||
|
return "Short Double Sharp Tick2 (80%)"; |
||||||
|
case Effect::kShortDoubleSharpTick3_60Pct: |
||||||
|
return "Short Double Sharp Tick3 (60%)"; |
||||||
|
case Effect::kLongDoubleSharpClickStrong1_100Pct: |
||||||
|
return "Long Double Sharp Click Strong1 (100%)"; |
||||||
|
case Effect::kLongDoubleSharpClickStrong2_80Pct: |
||||||
|
return "Long Double Sharp Click Strong2 (80%)"; |
||||||
|
case Effect::kLongDoubleSharpClickStrong3_60Pct: |
||||||
|
return "Long Double Sharp Click Strong3 (60%)"; |
||||||
|
case Effect::kLongDoubleSharpClickStrong4_30Pct: |
||||||
|
return "Long Double Sharp Click Strong4 (30%)"; |
||||||
|
case Effect::kLongDoubleSharpClickMedium1_100Pct: |
||||||
|
return "Long Double Sharp Click Medium1 (100%)"; |
||||||
|
case Effect::kLongDoubleSharpClickMedium2_80Pct: |
||||||
|
return "Long Double Sharp Click Medium2 (80%)"; |
||||||
|
case Effect::kLongDoubleSharpClickMedium3_60Pct: |
||||||
|
return "Long Double Sharp Click Medium3 (60%)"; |
||||||
|
case Effect::kLongDoubleSharpTick1_100Pct: |
||||||
|
return "Long Double Sharp Tick1 (100%)"; |
||||||
|
case Effect::kLongDoubleSharpTick2_80Pct: |
||||||
|
return "Long Double Sharp Tick2 (80%)"; |
||||||
|
case Effect::kLongDoubleSharpTick3_60Pct: |
||||||
|
return "Long Double Sharp Tick3 (60%)"; |
||||||
|
case Effect::kBuzz1_100Pct: |
||||||
|
return "Buzz1 (100%)"; |
||||||
|
case Effect::kBuzz2_80Pct: |
||||||
|
return "Buzz2 (80%)"; |
||||||
|
case Effect::kBuzz3_60Pct: |
||||||
|
return "Buzz3 (60%)"; |
||||||
|
case Effect::kBuzz4_40Pct: |
||||||
|
return "Buzz4 (40%)"; |
||||||
|
case Effect::kBuzz5_20Pct: |
||||||
|
return "Buzz5 (20%)"; |
||||||
|
case Effect::kPulsingStrong1_100Pct: |
||||||
|
return "Pulsing Strong1 (100%)"; |
||||||
|
case Effect::kPulsingStrong2_60Pct: |
||||||
|
return "Pulsing Strong2 (60%)"; |
||||||
|
case Effect::kPulsingMedium1_100Pct: |
||||||
|
return "Pulsing Medium1 (100%)"; |
||||||
|
case Effect::kPulsingMedium2_60Pct: |
||||||
|
return "Pulsing Medium2 (60%)"; |
||||||
|
case Effect::kPulsingSharp1_100Pct: |
||||||
|
return "Pulsing Sharp1 (100%)"; |
||||||
|
case Effect::kPulsingSharp2_60Pct: |
||||||
|
return "Pulsing Sharp2 (60%)"; |
||||||
|
case Effect::kTransitionClick1_100Pct: |
||||||
|
return "Transition Click1 (100%)"; |
||||||
|
case Effect::kTransitionClick2_80Pct: |
||||||
|
return "Transition Click2 (80%)"; |
||||||
|
case Effect::kTransitionClick3_60Pct: |
||||||
|
return "Transition Click3 (60%)"; |
||||||
|
case Effect::kTransitionClick4_40Pct: |
||||||
|
return "Transition Click4 (40%)"; |
||||||
|
case Effect::kTransitionClick5_20Pct: |
||||||
|
return "Transition Click5 (20%)"; |
||||||
|
case Effect::kTransitionClick6_10Pct: |
||||||
|
return "Transition Click6 (10%)"; |
||||||
|
case Effect::kTransitionHum1_100Pct: |
||||||
|
return "Transition Hum1 (100%)"; |
||||||
|
case Effect::kTransitionHum2_80Pct: |
||||||
|
return "Transition Hum2 (80%)"; |
||||||
|
case Effect::kTransitionHum3_60Pct: |
||||||
|
return "Transition Hum3 (60%)"; |
||||||
|
case Effect::kTransitionHum4_40Pct: |
||||||
|
return "Transition Hum4 (40%)"; |
||||||
|
case Effect::kTransitionHum5_20Pct: |
||||||
|
return "Transition Hum5 (20%)"; |
||||||
|
case Effect::kTransitionHum6_10Pct: |
||||||
|
return "Transition Hum6 (10%)"; |
||||||
|
|
||||||
|
// TODO: fix labels for XtoY-style
|
||||||
|
case Effect::kTransitionRampDownLongSmooth1_100to0Pct: |
||||||
|
return "Transition Ramp Down Long Smooth1 (100→0%)"; |
||||||
|
case Effect::kTransitionRampDownLongSmooth2_100to0Pct: |
||||||
|
return "Transition Ramp Down Long Smooth2 (100→0%)"; |
||||||
|
case Effect::kTransitionRampDownMediumSmooth1_100to0Pct: |
||||||
|
return "Transition Ramp Down Medium Smooth1 (100→0%)"; |
||||||
|
case Effect::kTransitionRampDownMediumSmooth2_100to0Pct: |
||||||
|
return "Transition Ramp Down Medium Smooth2 (100→0%)"; |
||||||
|
case Effect::kTransitionRampDownShortSmooth1_100to0Pct: |
||||||
|
return "Transition Ramp Down Short Smooth1 (100→0%)"; |
||||||
|
case Effect::kTransitionRampDownShortSmooth2_100to0Pct: |
||||||
|
return "Transition Ramp Down Short Smooth2 (100→0%)"; |
||||||
|
case Effect::kTransitionRampDownLongSharp1_100to0Pct: |
||||||
|
return "Transition Ramp Down Long Sharp1 (100→0%)"; |
||||||
|
case Effect::kTransitionRampDownLongSharp2_100to0Pct: |
||||||
|
return "Transition Ramp Down Long Sharp2 (100→0%)"; |
||||||
|
case Effect::kTransitionRampDownMediumSharp1_100to0Pct: |
||||||
|
return "Transition Ramp Down Medium Sharp1 (100→0%)"; |
||||||
|
case Effect::kTransitionRampDownMediumSharp2_100to0Pct: |
||||||
|
return "Transition Ramp Down Medium Sharp2 (100→0%)"; |
||||||
|
case Effect::kTransitionRampDownShortSharp1_100to0Pct: |
||||||
|
return "Transition Ramp Down Short Sharp1 (100→0%)"; |
||||||
|
case Effect::kTransitionRampDownShortSharp2_100to0Pct: |
||||||
|
return "Transition Ramp Down Short Sharp2 (100→0%)"; |
||||||
|
case Effect::kTransitionRampUpLongSmooth1_0to100Pct: |
||||||
|
return "Transition Ramp Up Long Smooth1 (0→100%)"; |
||||||
|
case Effect::kTransitionRampUpLongSmooth2_0to100Pct: |
||||||
|
return "Transition Ramp Up Long Smooth2 (0→100%)"; |
||||||
|
case Effect::kTransitionRampUpMediumSmooth1_0to100Pct: |
||||||
|
return "Transition Ramp Up Medium Smooth1 (0→100%)"; |
||||||
|
case Effect::kTransitionRampUpMediumSmooth2_0to100Pct: |
||||||
|
return "Transition Ramp Up Medium Smooth2 (0→100%)"; |
||||||
|
case Effect::kTransitionRampUpShortSmooth1_0to100Pct: |
||||||
|
return "Transition Ramp Up Short Smooth1 (0→100%)"; |
||||||
|
case Effect::kTransitionRampUpShortSmooth2_0to100Pct: |
||||||
|
return "Transition Ramp Up Short Smooth2 (0→100%)"; |
||||||
|
case Effect::kTransitionRampUpLongSharp1_0to100Pct: |
||||||
|
return "Transition Ramp Up Long Sharp1 (0→100%)"; |
||||||
|
case Effect::kTransitionRampUpLongSharp2_0to100Pct: |
||||||
|
return "Transition Ramp Up Long Sharp2 (0→100%)"; |
||||||
|
case Effect::kTransitionRampUpMediumSharp1_0to100Pct: |
||||||
|
return "Transition Ramp Up Medium Sharp1 (0→100%)"; |
||||||
|
case Effect::kTransitionRampUpMediumSharp2_0to100Pct: |
||||||
|
return "Transition Ramp Up Medium Sharp2 (0→100%)"; |
||||||
|
case Effect::kTransitionRampUpShortSharp1_0to100Pct: |
||||||
|
return "Transition Ramp Up Short Sharp1 (0→100%)"; |
||||||
|
case Effect::kTransitionRampUpShortSharp2_0to100Pct: |
||||||
|
return "Transition Ramp Up Short Sharp2 (0→100%)"; |
||||||
|
case Effect::kTransitionRampDownLongSmooth1_50to0Pct: |
||||||
|
return "Transition Ramp Down Long Smooth1 (50→0%)"; |
||||||
|
case Effect::kTransitionRampDownLongSmooth2_50to0Pct: |
||||||
|
return "Transition Ramp Down Long Smooth2 (50→0%)"; |
||||||
|
case Effect::kTransitionRampDownMediumSmooth1_50to0Pct: |
||||||
|
return "Transition Ramp Down Medium Smooth1 (50→0%)"; |
||||||
|
case Effect::kTransitionRampDownMediumSmooth2_50to0Pct: |
||||||
|
return "Transition Ramp Down Medium Smooth2 (50→0%)"; |
||||||
|
case Effect::kTransitionRampDownShortSmooth1_50to0Pct: |
||||||
|
return "Transition Ramp Down Short Smooth1 (50→0%)"; |
||||||
|
case Effect::kTransitionRampDownShortSmooth2_50to0Pct: |
||||||
|
return "Transition Ramp Down Short Smooth2 (50→0%)"; |
||||||
|
case Effect::kTransitionRampDownLongSharp1_50to0Pct: |
||||||
|
return "Transition Ramp Down Long Sharp1 (50→0%)"; |
||||||
|
case Effect::kTransitionRampDownLongSharp2_50to0Pct: |
||||||
|
return "Transition Ramp Down Long Sharp2 (50→0%)"; |
||||||
|
case Effect::kTransitionRampDownMediumSharp1_50to0Pct: |
||||||
|
return "Transition Ramp Down Medium Sharp1 (50→0%)"; |
||||||
|
case Effect::kTransitionRampDownMediumSharp2_50to0Pct: |
||||||
|
return "Transition Ramp Down Medium Sharp2 (50→0%)"; |
||||||
|
case Effect::kTransitionRampDownShortSharp1_50to0Pct: |
||||||
|
return "Transition Ramp Down Short Sharp1 (50→0%)"; |
||||||
|
case Effect::kTransitionRampDownShortSharp2_50to0Pct: |
||||||
|
return "Transition Ramp Down Short Sharp2 (50→0%)"; |
||||||
|
case Effect::kTransitionRampUpLongSmooth_10to50Pct: |
||||||
|
return "Transition Ramp Up Long Smooth10to (10→50%)"; |
||||||
|
case Effect::kTransitionRampUpLongSmooth_20to50Pct: |
||||||
|
return "Transition Ramp Up Long Smooth20to (10→50%)"; |
||||||
|
case Effect::kTransitionRampUpMediumSmooth_10to50Pct: |
||||||
|
return "Transition Ramp Up Medium Smooth (10→50%)"; |
||||||
|
case Effect::kTransitionRampUpMediumSmooth_20to50Pct: |
||||||
|
return "Transition Ramp Up Medium Smooth (20→50%)"; |
||||||
|
case Effect::kTransitionRampUpShortSmooth_10to50Pct: |
||||||
|
return "Transition Ramp Up Short Smooth (10→50%)"; |
||||||
|
case Effect::kTransitionRampUpShortSmooth_20to50Pct: |
||||||
|
return "Transition Ramp Up Short Smooth (20→50%)"; |
||||||
|
case Effect::kTransitionRampUpLongSharp_10to50Pct: |
||||||
|
return "Transition Ramp Up Long Sharp (10→50%)"; |
||||||
|
case Effect::kTransitionRampUpLongSharp_20to50Pct: |
||||||
|
return "Transition Ramp Up Long Sharp (20→50%)"; |
||||||
|
case Effect::kTransitionRampUpMediumSharp_10to50Pct: |
||||||
|
return "Transition Ramp Up Medium Sharp (10→50%)"; |
||||||
|
case Effect::kTransitionRampUpMediumSharp_20to50Pct: |
||||||
|
return "Transition Ramp Up Medium Sharp (20→50%)"; |
||||||
|
case Effect::kTransitionRampUpShortSharp_10to50Pct: |
||||||
|
return "Transition Ramp Up Short Sharp (10→50%)"; |
||||||
|
case Effect::kTransitionRampUpShortSharp_20to50Pct: |
||||||
|
return "Transition Ramp Up Short Sharp (20→50%)"; |
||||||
|
case Effect::kDontUseThis_Longbuzzforprogrammaticstopping_100Pct: |
||||||
|
return "DON'T USE: Long Buzz for Programmatic Stopping (100%)"; |
||||||
|
case Effect::kSmoothHum1NoKickOrBrakePulse_50Pct: |
||||||
|
return "Smooth Hum1 No Kick Or Brake Pulse (50%)"; |
||||||
|
case Effect::kSmoothHum2NoKickOrBrakePulse_40Pct: |
||||||
|
return "Smooth Hum2 No Kick Or Brake Pulse (40%)"; |
||||||
|
case Effect::kSmoothHum3NoKickOrBrakePulse_30Pct: |
||||||
|
return "Smooth Hum3 No Kick Or Brake Pulse (30%)"; |
||||||
|
case Effect::kSmoothHum4NoKickOrBrakePulse_20Pct: |
||||||
|
return "Smooth Hum4 No Kick Or Brake Pulse (20%)"; |
||||||
|
case Effect::kSmoothHum5NoKickOrBrakePulse_10Pct: |
||||||
|
return "Smooth Hum5 No Kick Or Brake Pulse (10%)"; |
||||||
|
default: |
||||||
|
return "UNKNOWN EFFECT"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace drivers
|
@ -0,0 +1,316 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au>, robin <robin@rhoward.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <initializer_list> |
||||||
|
#include <mutex> |
||||||
|
#include <optional> |
||||||
|
|
||||||
|
namespace drivers { |
||||||
|
|
||||||
|
class Haptics { |
||||||
|
public: |
||||||
|
static auto Create() -> Haptics* { return new Haptics(); } |
||||||
|
Haptics(); |
||||||
|
~Haptics(); |
||||||
|
|
||||||
|
// Not copyable or movable.
|
||||||
|
Haptics(const Haptics&) = delete; |
||||||
|
Haptics& operator=(const Haptics&) = delete; |
||||||
|
|
||||||
|
// See the datasheet for section references in the below comments:
|
||||||
|
// https://www.ti.com/lit/ds/symlink/drv2605l.pdf
|
||||||
|
|
||||||
|
// §12.1.2 Waveform Library Effects List
|
||||||
|
enum class Effect { |
||||||
|
kStop = 0, // Sentinel/terminator Effect for the Waveform Sequence Slots
|
||||||
|
kStrongClick_100Pct = 1, |
||||||
|
kStrongClick_60Pct = 2, |
||||||
|
kStrongClick_30Pct = 3, |
||||||
|
kSharpClick_100Pct = 4, |
||||||
|
kSharpClick_60Pct = 5, |
||||||
|
kSharpClick_30Pct = 6, |
||||||
|
kSoftBump_100Pct = 7, |
||||||
|
kSoftBump_60Pct = 8, |
||||||
|
kSoftBump_30Pct = 9, |
||||||
|
kDoubleClick_100Pct = 10, |
||||||
|
kDoubleClick_60Pct = 11, |
||||||
|
kTripleClick_100Pct = 12, |
||||||
|
kSoftFuzz_60Pct = 13, |
||||||
|
kStrongBuzz_100Pct = 14, |
||||||
|
k750msAlert_100Pct = 15, |
||||||
|
k1000msAlert_100Pct = 16, |
||||||
|
kStrongClick1_100Pct = 17, |
||||||
|
kStrongClick2_80Pct = 18, |
||||||
|
kStrongClick3_60Pct = 19, |
||||||
|
kStrongClick4_30Pct = 20, |
||||||
|
kMediumClick1_100Pct = 21, |
||||||
|
kMediumClick2_80Pct = 22, |
||||||
|
kMediumClick3_60Pct = 23, |
||||||
|
kSharpTick1_100Pct = 24, |
||||||
|
kSharpTick2_80Pct = 25, |
||||||
|
kSharpTick3_60Pct = 26, |
||||||
|
kShortDoubleClickStrong1_100Pct = 27, |
||||||
|
kShortDoubleClickStrong2_80Pct = 28, |
||||||
|
kShortDoubleClickStrong3_60Pct = 29, |
||||||
|
kShortDoubleClickStrong4_30Pct = 30, |
||||||
|
kShortDoubleClickMedium1_100Pct = 31, |
||||||
|
kShortDoubleClickMedium2_80Pct = 32, |
||||||
|
kShortDoubleClickMedium3_60Pct = 33, |
||||||
|
kShortDoubleSharpTick1_100Pct = 34, |
||||||
|
kShortDoubleSharpTick2_80Pct = 35, |
||||||
|
kShortDoubleSharpTick3_60Pct = 36, |
||||||
|
kLongDoubleSharpClickStrong1_100Pct = 37, |
||||||
|
kLongDoubleSharpClickStrong2_80Pct = 38, |
||||||
|
kLongDoubleSharpClickStrong3_60Pct = 39, |
||||||
|
kLongDoubleSharpClickStrong4_30Pct = 40, |
||||||
|
kLongDoubleSharpClickMedium1_100Pct = 41, |
||||||
|
kLongDoubleSharpClickMedium2_80Pct = 42, |
||||||
|
kLongDoubleSharpClickMedium3_60Pct = 43, |
||||||
|
kLongDoubleSharpTick1_100Pct = 44, |
||||||
|
kLongDoubleSharpTick2_80Pct = 45, |
||||||
|
kLongDoubleSharpTick3_60Pct = 46, |
||||||
|
kBuzz1_100Pct = 47, |
||||||
|
kBuzz2_80Pct = 48, |
||||||
|
kBuzz3_60Pct = 49, |
||||||
|
kBuzz4_40Pct = 50, |
||||||
|
kBuzz5_20Pct = 51, |
||||||
|
kPulsingStrong1_100Pct = 52, |
||||||
|
kPulsingStrong2_60Pct = 53, |
||||||
|
kPulsingMedium1_100Pct = 54, |
||||||
|
kPulsingMedium2_60Pct = 55, |
||||||
|
kPulsingSharp1_100Pct = 56, |
||||||
|
kPulsingSharp2_60Pct = 57, |
||||||
|
kTransitionClick1_100Pct = 58, |
||||||
|
kTransitionClick2_80Pct = 59, |
||||||
|
kTransitionClick3_60Pct = 60, |
||||||
|
kTransitionClick4_40Pct = 61, |
||||||
|
kTransitionClick5_20Pct = 62, |
||||||
|
kTransitionClick6_10Pct = 63, |
||||||
|
kTransitionHum1_100Pct = 64, |
||||||
|
kTransitionHum2_80Pct = 65, |
||||||
|
kTransitionHum3_60Pct = 66, |
||||||
|
kTransitionHum4_40Pct = 67, |
||||||
|
kTransitionHum5_20Pct = 68, |
||||||
|
kTransitionHum6_10Pct = 69, |
||||||
|
kTransitionRampDownLongSmooth1_100to0Pct = 70, |
||||||
|
kTransitionRampDownLongSmooth2_100to0Pct = 71, |
||||||
|
kTransitionRampDownMediumSmooth1_100to0Pct = 72, |
||||||
|
kTransitionRampDownMediumSmooth2_100to0Pct = 73, |
||||||
|
kTransitionRampDownShortSmooth1_100to0Pct = 74, |
||||||
|
kTransitionRampDownShortSmooth2_100to0Pct = 75, |
||||||
|
kTransitionRampDownLongSharp1_100to0Pct = 76, |
||||||
|
kTransitionRampDownLongSharp2_100to0Pct = 77, |
||||||
|
kTransitionRampDownMediumSharp1_100to0Pct = 78, |
||||||
|
kTransitionRampDownMediumSharp2_100to0Pct = 79, |
||||||
|
kTransitionRampDownShortSharp1_100to0Pct = 80, |
||||||
|
kTransitionRampDownShortSharp2_100to0Pct = 81, |
||||||
|
kTransitionRampUpLongSmooth1_0to100Pct = 82, |
||||||
|
kTransitionRampUpLongSmooth2_0to100Pct = 83, |
||||||
|
kTransitionRampUpMediumSmooth1_0to100Pct = 84, |
||||||
|
kTransitionRampUpMediumSmooth2_0to100Pct = 85, |
||||||
|
kTransitionRampUpShortSmooth1_0to100Pct = 86, |
||||||
|
kTransitionRampUpShortSmooth2_0to100Pct = 87, |
||||||
|
kTransitionRampUpLongSharp1_0to100Pct = 88, |
||||||
|
kTransitionRampUpLongSharp2_0to100Pct = 89, |
||||||
|
kTransitionRampUpMediumSharp1_0to100Pct = 90, |
||||||
|
kTransitionRampUpMediumSharp2_0to100Pct = 91, |
||||||
|
kTransitionRampUpShortSharp1_0to100Pct = 92, |
||||||
|
kTransitionRampUpShortSharp2_0to100Pct = 93, |
||||||
|
kTransitionRampDownLongSmooth1_50to0Pct = 94, |
||||||
|
kTransitionRampDownLongSmooth2_50to0Pct = 95, |
||||||
|
kTransitionRampDownMediumSmooth1_50to0Pct = 96, |
||||||
|
kTransitionRampDownMediumSmooth2_50to0Pct = 97, |
||||||
|
kTransitionRampDownShortSmooth1_50to0Pct = 98, |
||||||
|
kTransitionRampDownShortSmooth2_50to0Pct = 99, |
||||||
|
kTransitionRampDownLongSharp1_50to0Pct = 100, |
||||||
|
kTransitionRampDownLongSharp2_50to0Pct = 101, |
||||||
|
kTransitionRampDownMediumSharp1_50to0Pct = 102, |
||||||
|
kTransitionRampDownMediumSharp2_50to0Pct = 103, |
||||||
|
kTransitionRampDownShortSharp1_50to0Pct = 104, |
||||||
|
kTransitionRampDownShortSharp2_50to0Pct = 105, |
||||||
|
kTransitionRampUpLongSmooth_10to50Pct = 106, |
||||||
|
kTransitionRampUpLongSmooth_20to50Pct = 107, |
||||||
|
kTransitionRampUpMediumSmooth_10to50Pct = 108, |
||||||
|
kTransitionRampUpMediumSmooth_20to50Pct = 109, |
||||||
|
kTransitionRampUpShortSmooth_10to50Pct = 110, |
||||||
|
kTransitionRampUpShortSmooth_20to50Pct = 111, |
||||||
|
kTransitionRampUpLongSharp_10to50Pct = 112, |
||||||
|
kTransitionRampUpLongSharp_20to50Pct = 113, |
||||||
|
kTransitionRampUpMediumSharp_10to50Pct = 114, |
||||||
|
kTransitionRampUpMediumSharp_20to50Pct = 115, |
||||||
|
kTransitionRampUpShortSharp_10to50Pct = 116, |
||||||
|
kTransitionRampUpShortSharp_20to50Pct = 117, |
||||||
|
kSmoothHum1NoKickOrBrakePulse_50Pct = 119, |
||||||
|
kSmoothHum2NoKickOrBrakePulse_40Pct = 120, |
||||||
|
kSmoothHum3NoKickOrBrakePulse_30Pct = 121, |
||||||
|
kSmoothHum4NoKickOrBrakePulse_20Pct = 122, |
||||||
|
kSmoothHum5NoKickOrBrakePulse_10Pct = 123, |
||||||
|
|
||||||
|
// We can't use this one; need to have the EN pin hooked up.
|
||||||
|
kDontUseThis_Longbuzzforprogrammaticstopping_100Pct = 118, |
||||||
|
|
||||||
|
kFirst = kStrongClick_100Pct, |
||||||
|
kLast = kSmoothHum5NoKickOrBrakePulse_10Pct, |
||||||
|
}; |
||||||
|
|
||||||
|
static constexpr Effect kStartupEffect = Effect::kLongDoubleSharpTick1_100Pct; |
||||||
|
|
||||||
|
// §8.3.5.2 Internal Memory Interface
|
||||||
|
// Pick the ERM Library matching the motor.
|
||||||
|
enum class Library : uint8_t { |
||||||
|
A = 1, // 1.3V-3V, Rise: 40-60ms, Brake: 20-40ms
|
||||||
|
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+
|
||||||
|
}; |
||||||
|
|
||||||
|
static constexpr Library kDefaultLibrary = Library::C; |
||||||
|
|
||||||
|
auto PowerDown() -> void; |
||||||
|
auto Reset() -> void; |
||||||
|
|
||||||
|
auto PlayWaveformEffect(Effect effect) -> void; |
||||||
|
|
||||||
|
// Play a range of Effects
|
||||||
|
auto TourEffects() -> void; |
||||||
|
auto TourEffects(Effect from, Effect to) -> void; |
||||||
|
auto TourEffects(Library lib) -> void; |
||||||
|
auto TourEffects(Effect from, Effect to, Library lib) -> void; |
||||||
|
|
||||||
|
// Play a range of Effects to all the Libraries we support.
|
||||||
|
// TODO(robin): remove; I'm leaving this around for temporary testing
|
||||||
|
auto TourLibraries(Effect from, Effect to) -> void; |
||||||
|
|
||||||
|
private: |
||||||
|
std::optional<Effect> current_effect_; |
||||||
|
std::mutex playing_effect_; |
||||||
|
|
||||||
|
// §8.4.2 Changing Modes of Operation
|
||||||
|
enum class Mode : uint8_t { |
||||||
|
kInternalTrigger = 0, |
||||||
|
kExternalTriggerEdge = 1, |
||||||
|
kExternalTriggerLevel = 2, |
||||||
|
kPwmAnalog = 3, |
||||||
|
kAudioToVibe = 4, |
||||||
|
kRealtimePlayback = 5, |
||||||
|
kDiagnostics = 6, |
||||||
|
kAutoCalibrate = 7, |
||||||
|
}; |
||||||
|
|
||||||
|
struct ModeMask { |
||||||
|
// §8.4.1.4 Operation With STANDBY Control
|
||||||
|
static constexpr uint8_t kStandby = 0b01000000; |
||||||
|
// §8.4.1.5 Operation With DEV_RESET Control
|
||||||
|
static constexpr uint8_t kDevReset = 0b10000000; |
||||||
|
}; |
||||||
|
|
||||||
|
struct ControlMask { |
||||||
|
// Control1
|
||||||
|
static constexpr uint8_t kNErmLra = 0b10000000; |
||||||
|
// Control3
|
||||||
|
static constexpr uint8_t kErmOpenLoop = 0b00100000; |
||||||
|
}; |
||||||
|
|
||||||
|
// §8.6 Register Map
|
||||||
|
enum class Register { |
||||||
|
kStatus = 0x00, |
||||||
|
kMode = 0x01, |
||||||
|
|
||||||
|
kRealtimePlaybackInput = 0x02, |
||||||
|
kWaveformLibrary = 0x03, // see Library enum
|
||||||
|
|
||||||
|
kWaveformSequenceSlot1 = 0x04, |
||||||
|
kWaveformSequenceSlot2 = 0x05, |
||||||
|
kWaveformSequenceSlot3 = 0x06, |
||||||
|
kWaveformSequenceSlot4 = 0x07, |
||||||
|
kWaveformSequenceSlot5 = 0x08, |
||||||
|
kWaveformSequenceSlot6 = 0x09, |
||||||
|
kWaveformSequenceSlot7 = 0x0a, |
||||||
|
kWaveformSequenceSlot8 = 0x0b, |
||||||
|
|
||||||
|
kGo = 0x0C, |
||||||
|
|
||||||
|
// §8.3.5.2.2 Library Parameterization
|
||||||
|
kOverdriveTimeOffset = 0x0D, |
||||||
|
kSustainTimeOffsetPositive = 0x0E, |
||||||
|
kSustainTimeOffsetNegative = 0x0F, |
||||||
|
kBrakeTimeOffset = 0x10, |
||||||
|
kAudioToVibeControl = 0x11, |
||||||
|
|
||||||
|
kAudioToVibeInputLevelMin = 0x12, |
||||||
|
kAudioToVibeInputLevelMax = 0x13, |
||||||
|
kAudioToVibeOutputLevelMin = 0x14, |
||||||
|
kAudioToVibeOutputLevelMax = 0x15, |
||||||
|
kRatedVoltage = 0x16, |
||||||
|
kOverdriveClampVoltage = 0x17, |
||||||
|
kAutoCalibrationCompensationResult = 0x18, |
||||||
|
kAutoCalibrationBackEmfResult = 0x19, |
||||||
|
|
||||||
|
// A bunch of different options, not grouped
|
||||||
|
// in any particular sensible way
|
||||||
|
kControl1 = 0x1A, |
||||||
|
kControl2 = 0x1B, |
||||||
|
kControl3 = 0x1C, |
||||||
|
kControl4 = 0x1D, |
||||||
|
kControl5 = 0x1E, |
||||||
|
kControl6 = 0x1F, |
||||||
|
|
||||||
|
kSupplyVoltageMonitor = 0x21, // "VBAT"
|
||||||
|
kLraResonancePeriod = 0x22, |
||||||
|
}; |
||||||
|
|
||||||
|
enum class RegisterDefaults : uint8_t { |
||||||
|
kStatus = 0xE0, |
||||||
|
kMode = 0x40, |
||||||
|
kRealtimePlaybackInput = 0, |
||||||
|
kWaveformLibrary = 0x01, |
||||||
|
kWaveformSequenceSlot1 = 0x01, |
||||||
|
kWaveformSequenceSlot2 = 0, |
||||||
|
kWaveformSequenceSlot3 = 0, |
||||||
|
kWaveformSequenceSlot4 = 0, |
||||||
|
kWaveformSequenceSlot5 = 0, |
||||||
|
kWaveformSequenceSlot6 = 0, |
||||||
|
kWaveformSequenceSlot7 = 0, |
||||||
|
kWaveformSequenceSlot8 = 0, |
||||||
|
kGo = 0, |
||||||
|
kOverdriveTimeOffset = 0, |
||||||
|
kSustainTimeOffsetPositive = 0, |
||||||
|
kSustainTimeOffsetNegative = 0, |
||||||
|
kBrakeTimeOffset = 0, |
||||||
|
kAudioToVibeControl = 0x05, |
||||||
|
kAudioToVibeInputLevelMin = 0x19, |
||||||
|
kAudioToVibeInputLevelMax = 0xFF, |
||||||
|
kAudioToVibeOutputLevelMin = 0x19, |
||||||
|
kAudioToVibeOutputLevelMax = 0xFF, |
||||||
|
kRatedVoltage = 0x3E, |
||||||
|
kOverdriveClampVoltage = 0x8C, |
||||||
|
kAutoCalibrationCompensationResult = 0x0C, |
||||||
|
kAutoCalibrationBackEmfResult = 0x6C, |
||||||
|
kControl1 = 0x36, |
||||||
|
kControl2 = 0x93, |
||||||
|
kControl3 = 0xF5, |
||||||
|
kControl4 = 0xA0, |
||||||
|
kControl5 = 0x20, |
||||||
|
kControl6 = 0x80, |
||||||
|
kSupplyVoltageMonitor = 0, |
||||||
|
kLraResonancePeriod = 0, |
||||||
|
}; |
||||||
|
|
||||||
|
auto PowerUp() -> void; |
||||||
|
auto WriteRegister(Register reg, uint8_t val) -> void; |
||||||
|
|
||||||
|
auto SetWaveformEffect(Effect effect) -> void; |
||||||
|
auto Go() -> void; |
||||||
|
|
||||||
|
auto EffectToLabel(Effect effect) -> std::string; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace drivers
|
Loading…
Reference in new issue