1#![no_std]
35
36pub trait DshotProtocol {
38 fn compute_crc(value: u16) -> u16;
40
41 fn is_inverted() -> bool;
43}
44
45#[derive(Debug, Clone, Copy)]
47pub struct NormalDshot;
48
49impl DshotProtocol for NormalDshot {
50 fn compute_crc(value: u16) -> u16 {
51 (value ^ (value >> 4) ^ (value >> 8)) & 0x0F
52 }
53
54 fn is_inverted() -> bool {
55 false
56 }
57}
58
59#[derive(Debug, Clone, Copy)]
61pub struct BidirectionalDshot;
62
63impl DshotProtocol for BidirectionalDshot {
64 fn compute_crc(value: u16) -> u16 {
65 (!(value ^ (value >> 4) ^ (value >> 8))) & 0x0F
66 }
67
68 fn is_inverted() -> bool {
69 true
70 }
71}
72
73#[derive(Copy, Clone, Debug)]
75pub struct Frame<P: DshotProtocol = NormalDshot> {
76 inner: u16,
77 _protocol: core::marker::PhantomData<P>,
78}
79
80impl<P: DshotProtocol> Frame<P> {
81 pub fn new(speed: u16, request_telemetry: bool) -> Option<Self> {
90 if speed >= 2000 {
91 return None;
92 }
93
94 let translated_throttle = (speed + 48) << 5;
95 let mut frame = Self {
96 inner: translated_throttle,
97 _protocol: core::marker::PhantomData,
98 };
99 if request_telemetry {
100 frame.inner |= 0x10;
101 }
102 frame.compute_crc();
103 Some(frame)
104 }
105
106 pub fn command(command: Command, request_telemetry: bool) -> Self {
108 let mut frame = Self {
109 inner: (command as u16) << 5,
110 _protocol: core::marker::PhantomData,
111 };
112 if request_telemetry {
113 frame.inner |= 0x10;
114 }
115 frame.compute_crc();
116 frame
117 }
118
119 pub fn speed(&self) -> u16 {
121 (self.inner >> 5) - 48
122 }
123
124 pub fn telemetry_enabled(&self) -> bool {
126 self.inner & 0x10 != 0
127 }
128
129 pub fn crc(&self) -> u16 {
131 self.inner & 0x0F
132 }
133
134 fn compute_crc(&mut self) {
136 let value = self.inner >> 4;
137 let crc = P::compute_crc(value);
138 self.inner = (self.inner & !0x0F) | crc;
139 }
140
141 pub fn inner(&self) -> u16 {
143 self.inner
144 }
145
146 pub fn duty_cycles(&self, max_duty_cycle: u16) -> [u16; 17] {
151 let mut value = self.inner;
152 let mut rv = [max_duty_cycle * 2 / 3; 17];
153 for item in rv.iter_mut() {
154 let bit = value & 0x8000;
155 if bit == 0 {
156 *item = max_duty_cycle / 3;
157 }
158 value <<= 1;
159 }
160 rv[16] = 0;
161 rv
162 }
163}
164
165pub type NormalFrame = Frame<NormalDshot>;
167pub type BidirectionalFrame = Frame<BidirectionalDshot>;
168
169#[derive(Copy, Clone, Debug)]
174pub enum Command {
175 MotorStop = 0,
176 Beep1,
178 Beep2,
180 Beep3,
182 Beep4,
184 Beep5,
186 ESCInfo,
188 SpinDirection1,
190 SpinDirection2,
192 ThreeDModeOn,
194 ThreeDModeOff,
196 SettingsRequest,
197 SettingsSave,
199 ExtendedTelemetryEnable,
201 ExtendedTelemetryDisable,
203
204 SpinDirectionNormal = 20,
207 SpinDirectonReversed,
209 Led0On,
210 Led1On,
211 Led2On,
212 Led3On,
213 Led0Off,
214 Led1Off,
215 Led2Off,
216 Led3Off,
217 AudioStreamModeToggle,
218 SilentModeToggle,
219 SignalLineTelemetryEnable,
221 SignalLineTelemetryDisable,
223 SignalLineContinuousERPMTelemetry,
225 SignalLineContinuousERPMPeriodTelemetry,
227
228 SignalLineTemperatureTelemetry = 42,
231 SignalLineVoltageTelemetry,
233 SignalLineCurrentTelemetry,
235 SignalLineConsumptionTelemetry,
237 SignalLineERPMTelemetry,
239 SignalLineERPMPeriodTelemetry,
241}
242
243#[derive(Copy, Clone, Debug, PartialEq)]
250pub struct ErpmTelemetry {
251 pub shift: u8,
253 pub period_base: u16,
255 pub crc: u8,
257}
258
259impl ErpmTelemetry {
260 pub fn try_from_raw(raw: u16) -> Option<Self> {
264 let payload = raw >> 4; let received_crc = (raw & 0x0F) as u8;
266 let expected_crc = NormalDshot::compute_crc(payload) as u8;
267
268 if received_crc != expected_crc {
269 return None;
270 }
271
272 let shift = ((payload >> 9) & 0x07) as u8;
273 let period_base = payload & 0x1FF;
274
275 Some(Self {
276 shift,
277 period_base,
278 crc: received_crc,
279 })
280 }
281
282 pub fn period_us(&self) -> Option<u32> {
286 if self.period_base == 0 {
287 None } else {
289 let period = (self.period_base as u32) << self.shift;
290 if period == 0 {
291 None
292 } else {
293 Some(period)
294 }
295 }
296 }
297
298 pub fn erpm(&self) -> u32 {
303 match self.period_us() {
304 Some(period) if period > 0 => 60_000_000 / period,
305 _ => 0,
306 }
307 }
308
309 pub fn to_raw(&self) -> u16 {
311 let payload = ((self.shift as u16) << 9) | (self.period_base & 0x1FF);
312 let crc = self.crc as u16;
313 (payload << 4) | crc
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320
321 const MAX_DUTY_CYCLE: u16 = 100;
322 const ZERO: u16 = MAX_DUTY_CYCLE / 3;
323 const ONE: u16 = ZERO * 2;
324
325 #[test]
326 fn duty_cycles_works() {
327 let frame = NormalFrame::new(999, false).unwrap();
328 assert_eq!(
329 frame.duty_cycles(MAX_DUTY_CYCLE),
330 [
331 ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ONE, ONE, ONE, ZERO, ZERO, ONE, ZERO,
332 ZERO, 0
333 ]
334 );
335 }
336
337 #[test]
338 fn duty_cycles_at_zero() {
339 let frame = NormalFrame::command(Command::MotorStop, false);
340 assert_eq!(
341 frame.duty_cycles(MAX_DUTY_CYCLE),
342 [
343 ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO,
344 ZERO, ZERO, 0
345 ]
346 );
347 }
348
349 #[test]
350 fn frame_constructs_correctly() {
351 let frame = NormalFrame::new(998, false).unwrap();
352 assert_eq!(frame.speed(), 998);
353 assert!(!frame.telemetry_enabled());
354 assert_eq!(frame.crc(), 0x06);
355 }
356
357 #[test]
358 fn frame_constructs_correctly_with_telemetry() {
359 let frame = NormalFrame::new(998, true).unwrap();
360 assert_eq!(frame.speed(), 998);
361 assert!(frame.telemetry_enabled());
362 assert_eq!(frame.crc(), 0x07);
363 }
364
365 #[test]
366 fn frame_constructs_correctly_off_centre() {
367 let frame = NormalFrame::new(50, false).unwrap();
368 assert_eq!(frame.speed(), 50);
369 }
370
371 #[test]
372 fn frame_rejects_invalid_speed_values() {
373 assert!(NormalFrame::new(2000, false).is_none())
374 }
375
376 #[test]
377 fn bidir_duty_cycles_works() {
378 let frame = BidirectionalFrame::new(998, false).unwrap();
379 assert_eq!(
380 frame.duty_cycles(MAX_DUTY_CYCLE),
381 [
382 ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ONE, ONE, ZERO, ZERO, ONE, ZERO,
383 ZERO, ONE, 0
384 ]
385 );
386 }
387}