autd3_core/firmware/
pulse_width.rs

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