stepper_motion/config/
units.rs

1//! Unit types for physical quantities.
2//!
3//! Provides type-safe representations of angles, velocities, accelerations,
4//! and motor steps to prevent unit confusion at compile time.
5
6use core::ops::{Add, Mul, Sub};
7
8use serde::Deserialize;
9
10use crate::error::ConfigError;
11
12/// Angular position in degrees.
13///
14/// Used for configuration and user-facing API. Internally converted to [`Steps`].
15#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Deserialize)]
16#[serde(transparent)]
17pub struct Degrees(pub f32);
18
19impl Degrees {
20    /// Create a new Degrees value.
21    #[inline]
22    pub const fn new(value: f32) -> Self {
23        Self(value)
24    }
25
26    /// Get the raw value.
27    #[inline]
28    pub const fn value(self) -> f32 {
29        self.0
30    }
31
32    /// Convert to radians.
33    #[inline]
34    pub fn to_radians(self) -> f32 {
35        self.0.to_radians()
36    }
37
38    /// Create from radians.
39    #[inline]
40    pub fn from_radians(radians: f32) -> Self {
41        Self(radians.to_degrees())
42    }
43}
44
45impl Add for Degrees {
46    type Output = Self;
47
48    fn add(self, rhs: Self) -> Self::Output {
49        Self(self.0 + rhs.0)
50    }
51}
52
53impl Sub for Degrees {
54    type Output = Self;
55
56    fn sub(self, rhs: Self) -> Self::Output {
57        Self(self.0 - rhs.0)
58    }
59}
60
61/// Angular velocity in degrees per second.
62#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Deserialize)]
63#[serde(transparent)]
64pub struct DegreesPerSec(pub f32);
65
66impl DegreesPerSec {
67    /// Create a new DegreesPerSec value.
68    #[inline]
69    pub const fn new(value: f32) -> Self {
70        Self(value)
71    }
72
73    /// Get the raw value.
74    #[inline]
75    pub const fn value(self) -> f32 {
76        self.0
77    }
78}
79
80impl Mul<f32> for DegreesPerSec {
81    type Output = Self;
82
83    fn mul(self, rhs: f32) -> Self::Output {
84        Self(self.0 * rhs)
85    }
86}
87
88/// Angular acceleration in degrees per second squared.
89#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Deserialize)]
90#[serde(transparent)]
91pub struct DegreesPerSecSquared(pub f32);
92
93impl DegreesPerSecSquared {
94    /// Create a new DegreesPerSecSquared value.
95    #[inline]
96    pub const fn new(value: f32) -> Self {
97        Self(value)
98    }
99
100    /// Get the raw value.
101    #[inline]
102    pub const fn value(self) -> f32 {
103        self.0
104    }
105}
106
107impl Mul<f32> for DegreesPerSecSquared {
108    type Output = Self;
109
110    fn mul(self, rhs: f32) -> Self::Output {
111        Self(self.0 * rhs)
112    }
113}
114
115/// Motor position in steps (absolute from origin).
116///
117/// Uses i64 for unlimited range in either direction.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
119pub struct Steps(pub i64);
120
121impl Steps {
122    /// Create a new Steps value.
123    #[inline]
124    pub const fn new(value: i64) -> Self {
125        Self(value)
126    }
127
128    /// Get the raw value.
129    #[inline]
130    pub const fn value(self) -> i64 {
131        self.0
132    }
133
134    /// Get absolute value as u64.
135    #[inline]
136    pub fn abs(self) -> u64 {
137        self.0.unsigned_abs()
138    }
139
140    /// Convert to degrees using steps per degree ratio.
141    #[inline]
142    pub fn to_degrees(self, steps_per_degree: f32) -> Degrees {
143        Degrees(self.0 as f32 / steps_per_degree)
144    }
145
146    /// Create from degrees using steps per degree ratio.
147    #[inline]
148    pub fn from_degrees(degrees: Degrees, steps_per_degree: f32) -> Self {
149        Self((degrees.0 * steps_per_degree) as i64)
150    }
151}
152
153impl Add for Steps {
154    type Output = Self;
155
156    fn add(self, rhs: Self) -> Self::Output {
157        Self(self.0 + rhs.0)
158    }
159}
160
161impl Sub for Steps {
162    type Output = Self;
163
164    fn sub(self, rhs: Self) -> Self::Output {
165        Self(self.0 - rhs.0)
166    }
167}
168
169/// Microstep divisor (1, 2, 4, 8, 16, 32, 64, 128, 256).
170///
171/// Validated at construction to be a power of 2 within the valid range.
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub struct Microsteps(u16);
174
175impl Microsteps {
176    /// Full step (no microstepping).
177    pub const FULL: Self = Self(1);
178    /// Half step.
179    pub const HALF: Self = Self(2);
180    /// Quarter step.
181    pub const QUARTER: Self = Self(4);
182    /// Eighth step.
183    pub const EIGHTH: Self = Self(8);
184    /// Sixteenth step.
185    pub const SIXTEENTH: Self = Self(16);
186    /// Thirty-second step.
187    pub const THIRTY_SECOND: Self = Self(32);
188    /// Sixty-fourth step.
189    pub const SIXTY_FOURTH: Self = Self(64);
190    /// 128th step.
191    pub const ONE_TWENTY_EIGHTH: Self = Self(128);
192    /// 256th step (maximum resolution).
193    pub const TWO_FIFTY_SIXTH: Self = Self(256);
194
195    /// Valid microstep values.
196    const VALID_VALUES: [u16; 9] = [1, 2, 4, 8, 16, 32, 64, 128, 256];
197
198    /// Create a new Microsteps value with validation.
199    ///
200    /// # Errors
201    ///
202    /// Returns `ConfigError::InvalidMicrosteps` if the value is not a valid power of 2.
203    pub fn new(value: u16) -> Result<Self, ConfigError> {
204        if Self::VALID_VALUES.contains(&value) {
205            Ok(Self(value))
206        } else {
207            Err(ConfigError::InvalidMicrosteps(value))
208        }
209    }
210
211    /// Get the raw divisor value.
212    #[inline]
213    pub const fn value(self) -> u16 {
214        self.0
215    }
216
217    /// Check if a value is valid.
218    #[inline]
219    pub fn is_valid(value: u16) -> bool {
220        Self::VALID_VALUES.contains(&value)
221    }
222}
223
224impl Default for Microsteps {
225    fn default() -> Self {
226        Self::FULL
227    }
228}
229
230impl TryFrom<u16> for Microsteps {
231    type Error = ConfigError;
232
233    fn try_from(value: u16) -> Result<Self, Self::Error> {
234        Self::new(value)
235    }
236}
237
238impl<'de> Deserialize<'de> for Microsteps {
239    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
240    where
241        D: serde::Deserializer<'de>,
242    {
243        use core::fmt::Write;
244        let value = u16::deserialize(deserializer)?;
245        Microsteps::new(value).map_err(|e| {
246            let mut buf = heapless::String::<128>::new();
247            let _ = write!(buf, "{}", e);
248            serde::de::Error::custom(buf.as_str())
249        })
250    }
251}
252
253/// Extension trait for creating unit types from primitives.
254pub trait UnitExt {
255    /// Convert to Degrees.
256    fn degrees(self) -> Degrees;
257    /// Convert to DegreesPerSec.
258    fn degrees_per_sec(self) -> DegreesPerSec;
259    /// Convert to DegreesPerSecSquared.
260    fn degrees_per_sec_squared(self) -> DegreesPerSecSquared;
261}
262
263impl UnitExt for f32 {
264    #[inline]
265    fn degrees(self) -> Degrees {
266        Degrees(self)
267    }
268
269    #[inline]
270    fn degrees_per_sec(self) -> DegreesPerSec {
271        DegreesPerSec(self)
272    }
273
274    #[inline]
275    fn degrees_per_sec_squared(self) -> DegreesPerSecSquared {
276        DegreesPerSecSquared(self)
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    #[test]
285    fn test_microsteps_valid_values() {
286        for &v in &Microsteps::VALID_VALUES {
287            assert!(Microsteps::new(v).is_ok());
288        }
289    }
290
291    #[test]
292    fn test_microsteps_invalid_values() {
293        assert!(Microsteps::new(0).is_err());
294        assert!(Microsteps::new(3).is_err());
295        assert!(Microsteps::new(17).is_err());
296        assert!(Microsteps::new(512).is_err());
297    }
298
299    #[test]
300    fn test_degrees_conversion() {
301        let d = Degrees::new(180.0);
302        assert!((d.to_radians() - core::f32::consts::PI).abs() < 0.0001);
303    }
304
305    #[test]
306    fn test_steps_to_degrees() {
307        let steps = Steps::new(3200);
308        let steps_per_degree = 3200.0 / 360.0;
309        let degrees = steps.to_degrees(steps_per_degree);
310        assert!((degrees.value() - 360.0).abs() < 0.01);
311    }
312}