1#![no_std]
35
36pub trait DshotProtocol {
38 fn compute_crc(value: u16) -> u16;
40
41 fn is_inverted() -> bool;
43
44 fn get_translated_throttle(speed: u16) -> u16;
45}
46
47#[derive(Debug, Clone, Copy)]
49pub struct NormalDshot;
50
51impl DshotProtocol for NormalDshot {
52 fn compute_crc(value: u16) -> u16 {
53 (value ^ (value >> 4) ^ (value >> 8)) & 0x0F
54 }
55
56 fn is_inverted() -> bool {
57 false
58 }
59
60 fn get_translated_throttle(speed: u16) -> u16 {
61 (speed + 48) << 5
62 }
63}
64
65#[derive(Debug, Clone, Copy)]
67pub struct BidirectionalDshot;
68
69impl DshotProtocol for BidirectionalDshot {
70 fn compute_crc(value: u16) -> u16 {
71 (!(value ^ (value >> 4) ^ (value >> 8))) & 0x0F
72 }
73
74 fn is_inverted() -> bool {
75 true
76 }
77
78 fn get_translated_throttle(speed: u16) -> u16 {
79 let mask = 0b111_1111_1111;
80 (!(speed + 48) & mask) << 5
81 }
82}
83
84#[derive(Copy, Clone, Debug)]
86pub struct Frame<P: DshotProtocol = NormalDshot> {
87 inner: u16,
88 _protocol: core::marker::PhantomData<P>,
89}
90
91impl<P: DshotProtocol> Frame<P> {
92 pub fn new(speed: u16, request_telemetry: bool) -> Option<Self> {
101 if speed >= 2000 {
102 return None;
103 }
104
105 let translated_throttle = P::get_translated_throttle(speed);
106 let mut frame = Self {
107 inner: translated_throttle,
108 _protocol: core::marker::PhantomData,
109 };
110 if request_telemetry {
111 frame.inner |= 0x10;
112 }
113 frame.compute_crc();
114 Some(frame)
115 }
116
117 pub fn command(command: Command, request_telemetry: bool) -> Self {
119 let mut frame = Self {
120 inner: (command as u16) << 5,
121 _protocol: core::marker::PhantomData,
122 };
123 if request_telemetry {
124 frame.inner |= 0x10;
125 }
126 frame.compute_crc();
127 frame
128 }
129
130 pub fn speed(&self) -> u16 {
132 (self.inner >> 5) - 48
133 }
134
135 pub fn telemetry_enabled(&self) -> bool {
137 self.inner & 0x10 != 0
138 }
139
140 pub fn crc(&self) -> u16 {
142 self.inner & 0x0F
143 }
144
145 fn compute_crc(&mut self) {
147 let value = self.inner >> 4;
148 let crc = P::compute_crc(value);
149 self.inner = (self.inner & !0x0F) | crc;
150 }
151
152 pub fn inner(&self) -> u16 {
154 self.inner
155 }
156
157 pub fn duty_cycles(&self, max_duty_cycle: u16) -> [u16; 17] {
162 let mut value = self.inner;
163 let mut rv = [max_duty_cycle * 3 / 4; 17];
164 for item in rv.iter_mut() {
165 let bit = value & 0x8000;
166 if bit == 0 {
167 *item = max_duty_cycle * 3 / 8;
168 }
169 value <<= 1;
170 }
171 rv[16] = 0;
172 rv
173 }
174}
175
176pub type NormalFrame = Frame<NormalDshot>;
178pub type BidirectionalFrame = Frame<BidirectionalDshot>;
179
180#[derive(Copy, Clone, Debug)]
185pub enum Command {
186 MotorStop = 0,
187 Beep1,
189 Beep2,
191 Beep3,
193 Beep4,
195 Beep5,
197 ESCInfo,
199 SpinDirection1,
201 SpinDirection2,
203 ThreeDModeOn,
205 ThreeDModeOff,
207 SettingsRequest,
208 SettingsSave,
210 ExtendedTelemetryEnable,
212 ExtendedTelemetryDisable,
214
215 SpinDirectionNormal = 20,
218 SpinDirectonReversed,
220 Led0On,
221 Led1On,
222 Led2On,
223 Led3On,
224 Led0Off,
225 Led1Off,
226 Led2Off,
227 Led3Off,
228 AudioStreamModeToggle,
229 SilentModeToggle,
230 SignalLineTelemetryEnable,
232 SignalLineTelemetryDisable,
234 SignalLineContinuousERPMTelemetry,
236 SignalLineContinuousERPMPeriodTelemetry,
238
239 SignalLineTemperatureTelemetry = 42,
242 SignalLineVoltageTelemetry,
244 SignalLineCurrentTelemetry,
246 SignalLineConsumptionTelemetry,
248 SignalLineERPMTelemetry,
250 SignalLineERPMPeriodTelemetry,
252}
253
254#[derive(Copy, Clone, Debug, PartialEq)]
261pub struct ErpmTelemetry {
262 pub shift: u8,
264 pub period_base: u16,
266 pub crc: u8,
268}
269
270impl ErpmTelemetry {
271 pub fn try_from_raw(raw: u16) -> Option<Self> {
275 let payload = raw >> 4; let received_crc = (raw & 0x0F) as u8;
277 let expected_crc = NormalDshot::compute_crc(payload) as u8;
278
279 if received_crc != expected_crc {
280 return None;
281 }
282
283 let shift = ((payload >> 9) & 0x07) as u8;
284 let period_base = payload & 0x1FF;
285
286 Some(Self {
287 shift,
288 period_base,
289 crc: received_crc,
290 })
291 }
292
293 pub fn period_us(&self) -> Option<u32> {
297 if self.period_base == 0 {
298 None } else {
300 let period = (self.period_base as u32) << self.shift;
301 if period == 0 {
302 None
303 } else {
304 Some(period)
305 }
306 }
307 }
308
309 pub fn erpm(&self) -> u32 {
314 match self.period_us() {
315 Some(period) if period > 0 => 60_000_000 / period,
316 _ => 0,
317 }
318 }
319
320 pub fn to_raw(&self) -> u16 {
322 let payload = ((self.shift as u16) << 9) | (self.period_base & 0x1FF);
323 let crc = self.crc as u16;
324 (payload << 4) | crc
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 const MAX_DUTY_CYCLE: u16 = 100;
333 const ZERO: u16 = 37;
334 const ONE: u16 = 75;
335
336 #[test]
337 fn duty_cycles_works() {
338 let frame = NormalFrame::new(999, false).unwrap();
339 assert_eq!(
340 frame.duty_cycles(MAX_DUTY_CYCLE),
341 [
342 ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ONE, ONE, ONE, ZERO, ZERO, ONE, ZERO,
343 ZERO, 0
344 ]
345 );
346 }
347
348 #[test]
349 fn duty_cycles_at_zero() {
350 let frame = NormalFrame::command(Command::MotorStop, false);
351 assert_eq!(
352 frame.duty_cycles(MAX_DUTY_CYCLE),
353 [
354 ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO,
355 ZERO, ZERO, 0
356 ]
357 );
358 }
359
360 #[test]
361 fn frame_constructs_correctly() {
362 let frame = NormalFrame::new(998, false).unwrap();
363 assert_eq!(frame.speed(), 998);
364 assert!(!frame.telemetry_enabled());
365 assert_eq!(frame.crc(), 0x06);
366 }
367
368 #[test]
369 fn frame_constructs_correctly_with_telemetry() {
370 let frame = NormalFrame::new(998, true).unwrap();
371 assert_eq!(frame.speed(), 998);
372 assert!(frame.telemetry_enabled());
373 assert_eq!(frame.crc(), 0x07);
374 }
375
376 #[test]
377 fn frame_constructs_correctly_off_centre() {
378 let frame = NormalFrame::new(50, false).unwrap();
379 assert_eq!(frame.speed(), 50);
380 }
381
382 #[test]
383 fn frame_rejects_invalid_speed_values() {
384 assert!(NormalFrame::new(2000, false).is_none())
385 }
386
387 #[test]
388 fn bidirectional_throttle_works() {
389 let thr = BidirectionalDshot::get_translated_throttle(999);
390 assert_eq!(thr, 0b011_1110_1000_00000)
391 }
392}
393