dshot_codec/
nrzi_frame.rs1use core::ops::Deref;
2
3use super::{DshotError, DshotTelemetryFrame};
4
5#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, PartialOrd, Ord)]
11pub struct NrziFrame(u32);
12
13impl From<NrziFrame> for u32 {
14 #[inline]
15 fn from(frame: NrziFrame) -> Self {
16 frame.0
17 }
18}
19
20impl Deref for NrziFrame {
21 type Target = u32;
22
23 #[inline]
24 fn deref(&self) -> &Self::Target {
25 &self.0
26 }
27}
28
29impl NrziFrame {
30 const QUINTET_TO_NIBBLE: [u16; 32] = [
33 255, 255, 255, 255, 255, 255, 255, 255, 255, 9, 10, 11, 255, 13, 14, 15, 255, 255, 2, 3, 255, 5, 6, 7, 255, 0,
34 8, 1, 255, 4, 12, 255,
35 ];
36
37 #[inline]
38 #[must_use]
39 pub const fn from_raw_21(raw_21: u32) -> Self {
40 Self(raw_21 & 0x001F_FFFF)
42 }
43
44 #[inline]
45 #[must_use]
46 pub const fn raw_21(self) -> u32 {
47 self.0
48 }
49
50 #[inline]
53 #[must_use]
54 pub const fn is_valid(self) -> bool {
55 let value = self.0;
56 let checksum = (value ^ (value >> 4) ^ (value >> 8) ^ (value >> 12)) & 0x0F;
57 checksum == 0x0F
58 }
59
60 #[inline]
63 #[must_use]
64 const fn nrzi21_to_gcr20(value: u32) -> u32 {
65 let mut gcr_output: u32 = 0;
66
67 let mut previous_state = (value >> 20) & 0x01;
70
71 let mut ii = 19;
73 loop {
74 let current_state = (value >> ii) & 0x01;
75
76 let decoded_bit = current_state ^ previous_state;
78 gcr_output = (gcr_output << 1) | decoded_bit;
79
80 previous_state = current_state;
81
82 if ii == 0 {
83 break;
84 }
85 ii -= 1;
86 }
87
88 gcr_output
89 }
90
91 #[inline]
93 fn gcr20_to_erpm(gcr20: u32) -> Result<DshotTelemetryFrame, DshotError> {
94 let nibble0 = Self::QUINTET_TO_NIBBLE[(gcr20 & 0x1F) as usize];
95 let nibble1 = Self::QUINTET_TO_NIBBLE[((gcr20 >> 5) & 0x1F) as usize];
96 let nibble2 = Self::QUINTET_TO_NIBBLE[((gcr20 >> 10) & 0x1F) as usize];
97 let nibble3 = Self::QUINTET_TO_NIBBLE[((gcr20 >> 15) & 0x1F) as usize];
98
99 if nibble0 == 0xFF || nibble1 == 0xFF || nibble2 == 0xFF || nibble3 == 0xFF {
100 return Err(DshotError::InvalidNrziData);
101 }
102
103 let erpm_raw = nibble0 | (nibble1 << 4) | (nibble2 << 8) | (nibble3 << 12);
104 DshotTelemetryFrame::try_from(erpm_raw)
106 }
107
108 #[inline]
111 pub fn try_decode(self) -> Result<DshotTelemetryFrame, DshotError> {
112 if !self.is_valid() {
115 return Err(DshotError::InvalidChecksum);
116 }
117
118 let gcr20 = Self::nrzi21_to_gcr20(self.0);
119 let erpm_telemetry_frame = Self::gcr20_to_erpm(gcr20)?;
120 if erpm_telemetry_frame.checksum_is_ok() {
121 Ok(erpm_telemetry_frame)
122 } else {
123 Err(DshotError::InvalidChecksum)
124 }
125 }
126}
127
128#[allow(unused)]
129impl NrziFrame {
130 const NRZI_BIT_LENGTHS: [u32; 17] = [0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5];
131 const NRZI_SET_BITS: [u32; 6] = [0b_00000, 0b_00001, 0b_00011, 0b_00111, 0b_01111, 0b_11111];
132
133 #[inline]
135 fn nrzi21_to_erpm(nrzi21: u32) -> Result<DshotTelemetryFrame, DshotError> {
136 Self::gcr20_to_erpm(Self::nrzi21_to_gcr20(nrzi21))
137 }
138
139 pub fn decode_samples(value: u64) -> Result<DshotTelemetryFrame, DshotError> {
145 if (value & 0x8000_0000_0000_0000) != 0 {
147 return Err(DshotError::NoGcrData);
148 }
149
150 let mut consecutive_bit_count: usize = 1; let mut current_bit: u32 = 0;
152 let mut bit_count: u32 = 0;
153 let mut nrzi21_data: u32 = 0;
154
155 let mut mask: u64 = 0x4000_0000_0000_0000;
159 while mask != 0 {
160 if ((value & mask) != 0) == (current_bit != 0) {
161 consecutive_bit_count += 1;
163 if consecutive_bit_count > 16 {
164 return Err(DshotError::InvalidRunLength);
166 }
167 } else {
168 nrzi21_data <<= Self::NRZI_BIT_LENGTHS[consecutive_bit_count];
171 if current_bit != 0 {
173 nrzi21_data |= Self::NRZI_SET_BITS[Self::NRZI_BIT_LENGTHS[consecutive_bit_count] as usize];
174 }
175 bit_count += Self::NRZI_BIT_LENGTHS[consecutive_bit_count];
176 current_bit = !current_bit;
178 consecutive_bit_count = 1; }
180 mask >>= 1;
181 }
182
183 nrzi21_data <<= Self::NRZI_BIT_LENGTHS[consecutive_bit_count];
186 if current_bit != 0 {
188 nrzi21_data |= Self::NRZI_SET_BITS[Self::NRZI_BIT_LENGTHS[consecutive_bit_count] as usize];
189 }
190 bit_count += Self::NRZI_BIT_LENGTHS[consecutive_bit_count];
192
193 if bit_count < 21 {
195 return Err(DshotError::InvalidNrziData);
196 }
197
198 nrzi21_data >>= bit_count - 21;
200
201 let erpm_telemetry_frame = Self::nrzi21_to_erpm(nrzi21_data)?;
203
204 Ok(erpm_telemetry_frame)
205 }
206}
207
208#[cfg(test)]
209mod test_traits {
210 use super::*;
211
212 fn is_full<T: Sized + Send + Sync + Unpin + Copy + Clone + Default + PartialEq>() {}
213
214 #[test]
215 fn normal_types() {
216 is_full::<NrziFrame>();
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn gcr_decode_rejects_invalid_input() {
226 assert_eq!(Err(DshotError::InvalidChecksum), NrziFrame::from_raw_21(0).try_decode());
228 assert_eq!(Err(DshotError::InvalidChecksum), NrziFrame::from_raw_21(0x1FFFF).try_decode());
229 }
230 #[test]
231 fn valid() {
232 assert!(NrziFrame::from_raw_21(0xF000).is_valid()); assert!(NrziFrame::from_raw_21(0x8421).is_valid()); }
235
236 #[test]
237 fn invalid() {
238 assert!(!NrziFrame::from_raw_21(0x1234).is_valid()); assert!(!NrziFrame::from_raw_21(0x0000).is_valid()); }
241}