autd3_core/firmware/
pulse_width.rs1use 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)]
12pub struct PulseWidth {
14 inner: PulseWidthInner,
15}
16
17#[derive(Debug, PartialEq, Copy, Clone)]
18pub enum PulseWidthError {
20 PulseWidthOutOfRange(u32, u32),
22 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 #[must_use]
50 pub const fn new(pulse_width: u32) -> Self {
51 Self {
52 inner: PulseWidthInner::Raw(pulse_width),
53 }
54 }
55
56 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 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}