1use ph_curves::MonotonicCurveLut256;
2
3#[derive(Copy, Clone, Debug, Eq, PartialEq)]
5pub enum MotorKind {
6 Erm,
8 Lra,
10}
11
12#[derive(Copy, Clone, Debug)]
19pub struct MotorProfile {
20 pub kick_ms: u16,
22 pub kick_frac: u8,
24 pub min_run_frac: u8,
28 pub max_frac: u8,
30 pub gamma_curve: Option<&'static MonotonicCurveLut256>,
32 pub ramp_step_ms: u16,
34 pub min_dt_ms: u16,
36 pub duty_step: u8,
38}
39
40impl MotorProfile {
41 #[allow(clippy::too_many_arguments)]
43 pub const fn new(
44 kick_ms: u16,
45 kick_frac: u8,
46 min_run_frac: u8,
47 max_frac: u8,
48 gamma_curve: Option<&'static MonotonicCurveLut256>,
49 ramp_step_ms: u16,
50 min_dt_ms: u16,
51 duty_step: u8,
52 ) -> Self {
53 Self {
54 kick_ms,
55 kick_frac,
56 min_run_frac,
57 max_frac,
58 gamma_curve,
59 ramp_step_ms,
60 min_dt_ms,
61 duty_step,
62 }
63 }
64
65 pub const fn default_erm() -> Self {
67 Self {
68 kick_ms: 8,
69 kick_frac: 255,
70 min_run_frac: 64,
71 max_frac: 255,
72 gamma_curve: None,
73 ramp_step_ms: 2,
74 min_dt_ms: 1,
75 duty_step: 8,
76 }
77 }
78
79 pub const fn default_lra() -> Self {
81 Self {
82 kick_ms: 5,
83 kick_frac: 230,
84 min_run_frac: 51,
85 max_frac: 230,
86 gamma_curve: None,
87 ramp_step_ms: 1,
88 min_dt_ms: 1,
89 duty_step: 6,
90 }
91 }
92}
93
94impl Default for MotorProfile {
95 fn default() -> Self {
96 Self::default_erm()
97 }
98}
99
100pub const DEFAULT_ERM_PROFILE: MotorProfile = MotorProfile::default_erm();
102
103pub const DEFAULT_LRA_PROFILE: MotorProfile = MotorProfile::default_lra();
105
106#[derive(Copy, Clone, Debug, Eq, PartialEq)]
108pub struct ErmConfig {
109 pub max_duty: u16,
111}
112
113impl ErmConfig {
114 pub const fn new(max_duty: u16) -> Self {
116 Self { max_duty }
117 }
118}
119
120#[derive(Copy, Clone, Debug, Eq, PartialEq)]
122pub struct LraConfig {
123 pub max_amplitude: u16,
125 pub resonant_hz: u16,
127}
128
129impl LraConfig {
130 pub const fn new(max_amplitude: u16, resonant_hz: u16) -> Self {
132 Self {
133 max_amplitude,
134 resonant_hz,
135 }
136 }
137}
138
139#[derive(Copy, Clone, Debug, Eq, PartialEq)]
141pub enum MotorConfig {
142 Erm(ErmConfig),
144 Lra(LraConfig),
146}
147
148impl MotorConfig {
149 pub const fn kind(&self) -> MotorKind {
151 match self {
152 Self::Erm(_) => MotorKind::Erm,
153 Self::Lra(_) => MotorKind::Lra,
154 }
155 }
156
157 pub(crate) fn drive(&self, level: u16, lra_frequency_hz: Option<u16>) -> DriveCommand {
163 if level == 0 {
164 return DriveCommand::Off;
165 }
166
167 match self {
168 Self::Erm(config) => match scale(level, config.max_duty) {
169 0 => DriveCommand::Off,
170 duty => DriveCommand::Erm { duty },
171 },
172 Self::Lra(config) => match scale(level, config.max_amplitude) {
173 0 => DriveCommand::Off,
174 amplitude => DriveCommand::Lra {
175 amplitude,
176 frequency_hz: lra_frequency_hz.unwrap_or(config.resonant_hz),
177 },
178 },
179 }
180 }
181}
182
183#[derive(Copy, Clone, Debug, Eq, PartialEq)]
185pub enum DriveCommand {
186 Off,
188 Erm {
190 duty: u16,
192 },
193 Lra {
195 amplitude: u16,
197 frequency_hz: u16,
199 },
200}
201
202fn scale(level: u16, max: u16) -> u16 {
203 let value = (u32::from(level) * u32::from(max) + u32::from(u16::MAX) / 2) / u32::from(u16::MAX);
204 value as u16
205}
206
207pub(crate) fn frac_to_level(frac: u8) -> u16 {
209 (((u32::from(frac) * u32::from(u16::MAX)) + 127) / 255) as u16
210}