use ph_curves::MonotonicCurveLut256;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MotorKind {
Erm,
Lra,
}
#[derive(Copy, Clone, Debug)]
pub struct MotorProfile {
pub kick_ms: u16,
pub kick_frac: u8,
pub min_run_frac: u8,
pub max_frac: u8,
pub gamma_curve: Option<&'static MonotonicCurveLut256>,
pub ramp_step_ms: u16,
pub min_dt_ms: u16,
pub duty_step: u8,
}
impl MotorProfile {
#[allow(clippy::too_many_arguments)]
pub const fn new(
kick_ms: u16,
kick_frac: u8,
min_run_frac: u8,
max_frac: u8,
gamma_curve: Option<&'static MonotonicCurveLut256>,
ramp_step_ms: u16,
min_dt_ms: u16,
duty_step: u8,
) -> Self {
Self {
kick_ms,
kick_frac,
min_run_frac,
max_frac,
gamma_curve,
ramp_step_ms,
min_dt_ms,
duty_step,
}
}
pub const fn default_erm() -> Self {
Self {
kick_ms: 8,
kick_frac: 255,
min_run_frac: 64,
max_frac: 255,
gamma_curve: None,
ramp_step_ms: 2,
min_dt_ms: 1,
duty_step: 8,
}
}
pub const fn default_lra() -> Self {
Self {
kick_ms: 5,
kick_frac: 230,
min_run_frac: 51,
max_frac: 230,
gamma_curve: None,
ramp_step_ms: 1,
min_dt_ms: 1,
duty_step: 6,
}
}
}
impl Default for MotorProfile {
fn default() -> Self {
Self::default_erm()
}
}
pub const DEFAULT_ERM_PROFILE: MotorProfile = MotorProfile::default_erm();
pub const DEFAULT_LRA_PROFILE: MotorProfile = MotorProfile::default_lra();
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct ErmConfig {
pub max_duty: u16,
}
impl ErmConfig {
pub const fn new(max_duty: u16) -> Self {
Self { max_duty }
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct LraConfig {
pub max_amplitude: u16,
pub resonant_hz: u16,
}
impl LraConfig {
pub const fn new(max_amplitude: u16, resonant_hz: u16) -> Self {
Self {
max_amplitude,
resonant_hz,
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MotorConfig {
Erm(ErmConfig),
Lra(LraConfig),
}
impl MotorConfig {
pub const fn kind(&self) -> MotorKind {
match self {
Self::Erm(_) => MotorKind::Erm,
Self::Lra(_) => MotorKind::Lra,
}
}
pub(crate) fn drive(&self, level: u16, lra_frequency_hz: Option<u16>) -> DriveCommand {
if level == 0 {
return DriveCommand::Off;
}
match self {
Self::Erm(config) => match scale(level, config.max_duty) {
0 => DriveCommand::Off,
duty => DriveCommand::Erm { duty },
},
Self::Lra(config) => match scale(level, config.max_amplitude) {
0 => DriveCommand::Off,
amplitude => DriveCommand::Lra {
amplitude,
frequency_hz: lra_frequency_hz.unwrap_or(config.resonant_hz),
},
},
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum DriveCommand {
Off,
Erm {
duty: u16,
},
Lra {
amplitude: u16,
frequency_hz: u16,
},
}
fn scale(level: u16, max: u16) -> u16 {
let value = (u32::from(level) * u32::from(max) + u32::from(u16::MAX) / 2) / u32::from(u16::MAX);
value as u16
}
pub(crate) fn frac_to_level(frac: u8) -> u16 {
(((u32::from(frac) * u32::from(u16::MAX)) + 127) / 255) as u16
}