autd3_core/firmware/
pulse_width.rs

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