use crate::motion::{clamp, magnitude};
use core::f32::consts::PI;
#[derive(Clone, Copy, Debug)]
pub struct ServoMap {
min_us: u16,
max_us: u16,
range_deg: f32,
}
impl ServoMap {
pub fn standard() -> Self {
Self {
min_us: 1000,
max_us: 2000,
range_deg: 180.0,
}
}
pub fn new(min_us: u16, max_us: u16, range_deg: f32) -> Self {
Self {
min_us,
max_us,
range_deg: magnitude(range_deg),
}
}
pub fn pulse(&self, angle_deg: f32) -> u16 {
let span_us = self.max_us as f32 - self.min_us as f32;
let fraction = if self.range_deg == 0.0 {
0.0
} else {
clamp(angle_deg, 0.0, self.range_deg) / self.range_deg
};
(self.min_us as f32 + fraction * span_us + 0.5) as u16
}
pub fn angle(&self, pulse_us: u16) -> f32 {
let span_us = self.max_us as f32 - self.min_us as f32;
if span_us == 0.0 {
return 0.0;
}
let p = clamp(pulse_us as f32, self.min_us as f32, self.max_us as f32);
(p - self.min_us as f32) / span_us * self.range_deg
}
}
#[derive(Clone, Copy, Debug)]
pub struct Esc {
min_us: u16,
neutral_us: u16,
max_us: u16,
}
impl Esc {
pub fn bidirectional() -> Self {
Self {
min_us: 1000,
neutral_us: 1500,
max_us: 2000,
}
}
pub fn new(min_us: u16, neutral_us: u16, max_us: u16) -> Self {
Self {
min_us,
neutral_us,
max_us,
}
}
pub fn pulse(&self, throttle: f32) -> u16 {
let t = clamp(throttle, -1.0, 1.0);
let span = if t >= 0.0 {
self.max_us as f32 - self.neutral_us as f32
} else {
self.neutral_us as f32 - self.min_us as f32
};
(self.neutral_us as f32 + t * span + 0.5) as u16
}
}
const QUADRATURE_TABLE: [i8; 16] = [0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0];
fn encode(a: bool, b: bool) -> u8 {
((a as u8) << 1) | (b as u8)
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Quadrature {
state: u8,
count: i64,
}
impl Quadrature {
pub fn new() -> Self {
Self { state: 0, count: 0 }
}
pub fn starting(a: bool, b: bool) -> Self {
Self {
state: encode(a, b),
count: 0,
}
}
pub fn update(&mut self, a: bool, b: bool) -> i8 {
let next = encode(a, b);
let delta = QUADRATURE_TABLE[((self.state << 2) | next) as usize];
self.state = next;
self.count += delta as i64;
delta
}
pub fn count(&self) -> i64 {
self.count
}
pub fn reset(&mut self) {
self.count = 0;
}
}
#[derive(Clone, Copy, Debug)]
pub struct QuadratureScale {
counts_per_rev: f32,
wheel_radius: f32,
}
impl QuadratureScale {
pub fn new(counts_per_rev: f32, wheel_radius: f32) -> Self {
Self {
counts_per_rev: magnitude(counts_per_rev),
wheel_radius: magnitude(wheel_radius),
}
}
pub fn distance(&self, count: i64) -> f32 {
if self.counts_per_rev == 0.0 {
return 0.0;
}
let circumference = 2.0 * PI * self.wheel_radius;
(count as f32 / self.counts_per_rev) * circumference
}
pub fn velocity(&self, delta_count: i64, dt: f32) -> f32 {
if dt == 0.0 {
0.0
} else {
self.distance(delta_count) / dt
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn servo_maps_ends_and_centre() {
let servo = ServoMap::standard();
assert_eq!(servo.pulse(0.0), 1000);
assert_eq!(servo.pulse(90.0), 1500);
assert_eq!(servo.pulse(180.0), 2000);
assert_eq!(servo.pulse(999.0), 2000); assert!((servo.angle(1750) - 135.0).abs() < 1e-3);
}
#[test]
fn esc_maps_throttle_across_the_range() {
let esc = Esc::bidirectional();
assert_eq!(esc.pulse(0.0), 1500);
assert_eq!(esc.pulse(1.0), 2000);
assert_eq!(esc.pulse(-1.0), 1000);
assert_eq!(esc.pulse(0.5), 1750);
assert_eq!(esc.pulse(-2.0), 1000); }
#[test]
fn quadrature_counts_forward_and_backward() {
let mut enc = Quadrature::new();
let forward = [(false, true), (true, true), (true, false), (false, false)];
for &(a, b) in &forward {
assert_eq!(enc.update(a, b), 1);
}
assert_eq!(enc.count(), 4);
let backward = [(true, false), (true, true), (false, true), (false, false)];
for &(a, b) in &backward {
assert_eq!(enc.update(a, b), -1);
}
assert_eq!(enc.count(), 0);
}
#[test]
fn quadrature_ignores_no_change_and_illegal_jumps() {
let mut enc = Quadrature::new();
assert_eq!(enc.update(false, false), 0); assert_eq!(enc.update(true, true), 0); }
#[test]
fn scale_turns_ticks_into_distance_and_speed() {
let scale = QuadratureScale::new(360.0, 0.05);
let one_rev = 2.0 * PI * 0.05;
assert!((scale.distance(360) - one_rev).abs() < 1e-6);
assert!((scale.velocity(360, 2.0) - one_rev / 2.0).abs() < 1e-6);
assert_eq!(scale.velocity(360, 0.0), 0.0);
}
}