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 SpinDirectionReversed,
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 shift: u8,
253 period_base: u16,
255 crc: u8,
257}
258
259impl ErpmTelemetry {
260 pub fn shift(&self) -> u8 {
262 self.shift
263 }
264
265 pub fn period_base(&self) -> u16 {
267 self.period_base
268 }
269
270 pub fn crc(&self) -> u8 {
272 self.crc
273 }
274
275 pub fn set_shift(&mut self, shift: u8) {
281 assert!(shift <= 7, "Shift value needs to be within 0..=7");
282
283 self.shift = shift;
284 self.crc = NormalDshot::compute_crc((self.shift as u16) << 9 | self.period_base) as u8;
285 }
286
287 pub fn set_period_base(&mut self, period_base: u16) {
293 assert!(period_base <= 511, "Period base needs to be within 0..=511");
294
295 self.period_base = period_base;
296 self.crc = NormalDshot::compute_crc((self.shift as u16) << 9 | self.period_base) as u8;
297 }
298
299 pub fn try_from_raw(raw: u16) -> Option<Self> {
303 let payload = raw >> 4; let received_crc = (raw & 0x0F) as u8;
305 let expected_crc = NormalDshot::compute_crc(payload) as u8;
306
307 if received_crc != expected_crc {
308 return None;
309 }
310
311 let shift = ((payload >> 9) & 0x07) as u8;
312 let period_base = payload & 0x1FF;
313
314 Some(Self {
315 shift,
316 period_base,
317 crc: received_crc,
318 })
319 }
320
321 pub fn period_us(&self) -> Option<u32> {
325 if self.period_base == 0 {
326 None } else {
328 let period = (self.period_base as u32) << self.shift;
329 if period == 0 {
330 None
331 } else {
332 Some(period)
333 }
334 }
335 }
336
337 pub fn erpm(&self) -> u32 {
342 match self.period_us() {
343 Some(period) if period > 0 => 60_000_000 / period,
344 _ => 0,
345 }
346 }
347
348 pub fn to_raw(&self) -> u16 {
350 let payload = ((self.shift as u16) << 9) | (self.period_base & 0x1FF);
351 let crc = self.crc as u16;
352 (payload << 4) | crc
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 const MAX_DUTY_CYCLE: u16 = 100;
361 const ZERO: u16 = MAX_DUTY_CYCLE / 3;
362 const ONE: u16 = ZERO * 2;
363
364 #[test]
365 fn duty_cycles_works() {
366 let frame = NormalFrame::new(999, false).unwrap();
367 assert_eq!(
368 frame.duty_cycles(MAX_DUTY_CYCLE),
369 [
370 ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ONE, ONE, ONE, ZERO, ZERO, ONE, ZERO,
371 ZERO, 0
372 ]
373 );
374 }
375
376 #[test]
377 fn duty_cycles_at_zero() {
378 let frame = NormalFrame::command(Command::MotorStop, false);
379 assert_eq!(
380 frame.duty_cycles(MAX_DUTY_CYCLE),
381 [
382 ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO,
383 ZERO, ZERO, 0
384 ]
385 );
386 }
387
388 #[test]
389 fn frame_constructs_correctly() {
390 let frame = NormalFrame::new(998, false).unwrap();
391 assert_eq!(frame.speed(), 998);
392 assert!(!frame.telemetry_enabled());
393 assert_eq!(frame.crc(), 0x06);
394 }
395
396 #[test]
397 fn frame_constructs_correctly_with_telemetry() {
398 let frame = NormalFrame::new(998, true).unwrap();
399 assert_eq!(frame.speed(), 998);
400 assert!(frame.telemetry_enabled());
401 assert_eq!(frame.crc(), 0x07);
402 }
403
404 #[test]
405 fn frame_constructs_correctly_off_centre() {
406 let frame = NormalFrame::new(50, false).unwrap();
407 assert_eq!(frame.speed(), 50);
408 }
409
410 #[test]
411 fn frame_rejects_invalid_speed_values() {
412 assert!(NormalFrame::new(2000, false).is_none())
413 }
414
415 #[test]
416 fn telemetry_setters_recompute_crc() {
417 let mut telemetry = ErpmTelemetry::try_from_raw(0x4646).unwrap();
418
419 telemetry.set_shift(5);
420 assert_eq!(
421 telemetry.crc(),
422 NormalDshot::compute_crc((telemetry.shift() as u16) << 9 | telemetry.period_base())
423 as u8
424 );
425
426 telemetry.set_period_base(42);
427 assert_eq!(
428 telemetry.crc(),
429 NormalDshot::compute_crc((telemetry.shift() as u16) << 9 | telemetry.period_base())
430 as u8
431 );
432 }
433
434 #[test]
435 fn telemetry_setters_preserve_round_trip() {
436 let mut telemetry = ErpmTelemetry::try_from_raw(0x4646).unwrap();
437 telemetry.set_shift(5);
438 telemetry.set_period_base(42);
439 assert_eq!(ErpmTelemetry::try_from_raw(telemetry.to_raw()), Some(telemetry));
440 }
441
442 #[test]
443 #[should_panic(expected = "Shift value needs to be within 0..=7")]
444 fn telemetry_set_shift_panics_on_out_of_bounds() {
445 let mut telemetry = ErpmTelemetry::try_from_raw(0x4646).unwrap();
446 telemetry.set_shift(8);
447 }
448
449 #[test]
450 #[should_panic(expected = "Period base needs to be within 0..=511")]
451 fn telemetry_set_period_base_panics_on_out_of_bounds() {
452 let mut telemetry = ErpmTelemetry::try_from_raw(0x4646).unwrap();
453 telemetry.set_period_base(512);
454 }
455
456 #[test]
457 fn telemetry_setters_update_values() {
458 let mut telemetry = ErpmTelemetry::try_from_raw(0x4646).unwrap();
459 telemetry.set_shift(5);
460 assert_eq!(telemetry.shift(), 5);
461 telemetry.set_period_base(42);
462 assert_eq!(telemetry.period_base(), 42);
463 }
464
465 #[test]
466 fn bidir_duty_cycles_works() {
467 let frame = BidirectionalFrame::new(998, false).unwrap();
468 assert_eq!(
469 frame.duty_cycles(MAX_DUTY_CYCLE),
470 [
471 ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ONE, ONE, ZERO, ZERO, ONE, ZERO,
472 ZERO, ONE, 0
473 ]
474 );
475 }
476}