autd3_core/firmware/
pulse_width.rs1#[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)]
15pub struct PulseWidth {
17 inner: PulseWidthInner,
18}
19
20#[derive(Debug, Error, PartialEq, Copy, Clone)]
21pub enum PulseWidthError {
23 #[error("Pulse width ({0}) is out of range [0, {1})")]
25 PulseWidthOutOfRange(u32, u32),
26 #[error("Duty ratio ({0}) is out of range [0, 1)")]
28 DutyRatioOutOfRange(f32),
29}
30
31impl PulseWidth {
32 #[must_use]
36 pub const fn new(pulse_width: u32) -> Self {
37 Self {
38 inner: PulseWidthInner::Raw(pulse_width),
39 }
40 }
41
42 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 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}