autd3_core/firmware/
pulse_width.rs

1#[cfg(not(feature = "std"))]
2use num_traits::float::Float;
3
4use thiserror::Error;
5
6#[repr(C)]
7#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
8enum PulseWidthInner {
9    Duty(f32),
10    Raw(u32),
11}
12
13#[repr(C)]
14#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
15/// The pulse width.
16pub struct PulseWidth {
17    inner: PulseWidthInner,
18}
19
20#[derive(Debug, Error, PartialEq, Copy, Clone)]
21/// Error type for [`PulseWidth`].
22pub enum PulseWidthError {
23    /// Error when the pulse width is out of range.
24    #[error("Pulse width ({0}) is out of range [0, {1})")]
25    PulseWidthOutOfRange(u32, u32),
26    /// Error when the duty ratio is out of range.
27    #[error("Duty ratio ({0}) is out of range [0, 1)")]
28    DutyRatioOutOfRange(f32),
29}
30
31impl PulseWidth {
32    /// Creates a new [`PulseWidth`].
33    ///
34    /// Note that the period depends on the firmware version, so it is recommended to use [`PulseWidth::from_duty`] instead.
35    #[must_use]
36    pub const fn new(pulse_width: u32) -> Self {
37        Self {
38            inner: PulseWidthInner::Raw(pulse_width),
39        }
40    }
41
42    /// Creates a new [`PulseWidth`] from duty ratio.
43    pub fn from_duty(duty: f32) -> Result<Self, PulseWidthError> {
44        if !(0.0..1.0).contains(&duty) {
45            return Err(PulseWidthError::DutyRatioOutOfRange(duty));
46        };
47        Ok(Self {
48            inner: PulseWidthInner::Duty(duty),
49        })
50    }
51
52    /// Returns the pulse width.
53    pub fn pulse_width<T>(self, period: u32) -> Result<T, PulseWidthError>
54    where
55        T: TryFrom<u32> + TryInto<u32>,
56    {
57        let pulse_width = match self.inner {
58            PulseWidthInner::Duty(duty) => (duty * period as f32).round() as u32,
59            PulseWidthInner::Raw(raw) => raw,
60        };
61        if pulse_width >= period {
62            return Err(PulseWidthError::PulseWidthOutOfRange(pulse_width, period));
63        }
64        T::try_from(pulse_width)
65            .map_err(|_| PulseWidthError::PulseWidthOutOfRange(pulse_width, period))
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[rstest::rstest]
74    #[case(Ok(0), 0, 256)]
75    #[case(Ok(128), 128, 256)]
76    #[case(Ok(255), 255, 256)]
77    #[case(Err(PulseWidthError::PulseWidthOutOfRange(256, 256)), 256, 256)]
78    #[case(Ok(0), 0, 512)]
79    #[case(Ok(256), 256, 512)]
80    #[case(Ok(511), 511, 512)]
81    #[case(Err(PulseWidthError::PulseWidthOutOfRange(512, 512)), 512, 512)]
82    #[test]
83    fn test_pulse_width_new(
84        #[case] expected: Result<u16, PulseWidthError>,
85        #[case] pulse_width: u32,
86        #[case] period: u32,
87    ) {
88        assert_eq!(expected, PulseWidth::new(pulse_width).pulse_width(period));
89    }
90
91    #[rstest::rstest]
92    #[case(Ok(0), 0.0, 256)]
93    #[case(Ok(128), 0.5, 256)]
94    #[case(Ok(255), 255.0 / 256.0, 256)]
95    #[case(Err(PulseWidthError::DutyRatioOutOfRange(-0.5)), -0.5, 256)]
96    #[case(Err(PulseWidthError::DutyRatioOutOfRange(1.0)), 1.0, 256)]
97    #[case(Err(PulseWidthError::DutyRatioOutOfRange(1.5)), 1.5, 256)]
98    #[case(Ok(0), 0.0, 512)]
99    #[case(Ok(256), 0.5, 512)]
100    #[case(Ok(511), 511.0 / 512.0, 512)]
101    #[case(Err(PulseWidthError::DutyRatioOutOfRange(-0.5)), -0.5, 512)]
102    #[case(Err(PulseWidthError::DutyRatioOutOfRange(1.0)), 1.0, 512)]
103    #[case(Err(PulseWidthError::DutyRatioOutOfRange(1.5)), 1.5, 512)]
104    #[test]
105    fn test_pulse_width_from_duty(
106        #[case] expected: Result<u16, PulseWidthError>,
107        #[case] duty: f32,
108        #[case] period: u32,
109    ) {
110        assert_eq!(
111            expected,
112            PulseWidth::from_duty(duty).and_then(|p| p.pulse_width(period))
113        );
114    }
115}