dshot_frame/lib.rs
1//! Support for the DShot ESC protocol
2//!
3//! DShot has two-byte frames, where the first 11 bits are the throttle speed, bit 12 is a
4//! telemetry request flag, and the last four bits are a checksum.
5//!
6//! Throttle values below 48 are reserved for special commands.
7//!
8//! It is transmitted over the wire at a fixed speed, with ones and zeroes both being pulses, but
9//! ones being twice as long as zeroes.
10//!
11//! ## Usage
12//!
13//! This example is adapted from an embassy-stm32 codebase:
14//!
15//! ```ignore
16//! let mut pwm = SimplePwm::new(
17//! timer,
18//! Some(PwmPin::new_ch1(pin, OutputType::PushPull)),
19//! None,
20//! None,
21//! None,
22//! Hertz(150_000),
23//! CountingMode::EdgeAlignedUp,
24//! );
25//! let max_duty_cycle = pwm.get_max_duty() as u16;
26//!
27//! let frame = Frame::new(1000, false).unwrap();
28//! pwm.waveform_up(&mut dma, Ch1, &frame.duty_cycles()).await;
29//! // Pull the line low after sending a frame.
30//! pwm.set_duty(channel, 0);
31//! pwm.enable(channel);
32//! ```
33
34// TODO Bidirectional DShot
35
36#![no_std]
37
38/// A frame of two bytes that get send over the wire.
39#[derive(Copy, Clone, Debug)]
40pub struct Frame {
41 inner: u16,
42}
43
44impl Frame {
45 /// Creates a new frame with the given speed (0-1999) and telemetry request.
46 ///
47 /// Returns [`None`] if the speed is out of bounds.
48 ///
49 /// ```
50 /// # use dshot_frame::*;
51 /// assert_eq!(Frame::new(1000, false).unwrap().speed(), 1000);
52 /// ```
53 pub fn new(speed: u16, request_telemetry: bool) -> Option<Self> {
54 if speed >= 2000 {
55 return None;
56 }
57
58 let translated_throttle = (speed + 48) << 5;
59 let mut frame = Self {
60 inner: translated_throttle,
61 };
62 if request_telemetry {
63 frame.inner |= 0x10;
64 }
65 frame.compute_crc();
66 Some(frame)
67 }
68
69 /// Creates a new frame with the given [`Command`] and telemetry request.
70 pub fn command(command: Command, request_telemetry: bool) -> Self {
71 let mut frame = Self {
72 inner: (command as u16) << 5,
73 };
74 if request_telemetry {
75 frame.inner |= 0x10;
76 }
77 frame.compute_crc();
78 frame
79 }
80
81 /// Returns the speed value (0-1999).
82 pub fn speed(&self) -> u16 {
83 (self.inner >> 5) - 48
84 }
85
86 /// Returns whether telemetry is enabled.
87 pub fn telemetry_enabled(&self) -> bool {
88 self.inner & 0x10 != 0
89 }
90
91 /// Returns the CRC checksum.
92 pub fn crc(&self) -> u16 {
93 self.inner & 0x0F
94 }
95
96 /// Computes the CRC based on the first 12 bits and ORs it in.
97 fn compute_crc(&mut self) {
98 let value = self.inner >> 4;
99 let crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F;
100 self.inner |= crc;
101 }
102
103 /// Returns the raw [`u16`].
104 pub fn inner(&self) -> u16 {
105 self.inner
106 }
107
108 /// Returns an array of duty cycles for use in PWM DMA.
109 ///
110 /// This contains an extra element that is always zero to ensure the PWM output gets pulled low
111 /// at the end of the sequence. It can be sliced off if not needed.
112 pub fn duty_cycles(&self, max_duty_cycle: u16) -> [u16; 17] {
113 let mut value = self.inner;
114 let mut rv = [max_duty_cycle * 3 / 4; 17];
115 for item in rv.iter_mut() {
116 let bit = value & 0x8000;
117 if bit == 0 {
118 *item = max_duty_cycle * 3 / 8;
119 }
120 value <<= 1;
121 }
122 rv[16] = 0;
123 rv
124 }
125}
126
127/// Fixed commands that occupy the lower 48 speed values.
128///
129/// Some commands need to be sent multiple times to be acted upon to prevent accidental bit-flips
130/// wreaking havoc.
131#[derive(Copy, Clone, Debug)]
132pub enum Command {
133 MotorStop = 0,
134 /// Wait at least 260ms before next command.
135 Beep1,
136 /// Wait at least 260ms before next command.
137 Beep2,
138 /// Wait at least 260ms before next command.
139 Beep3,
140 /// Wait at least 260ms before next command.
141 Beep4,
142 /// Wait at least 260ms before next command.
143 Beep5,
144 /// Wait at least 12ms before next command.
145 ESCInfo,
146 /// Needs 6 transmissions.
147 SpinDirection1,
148 /// Needs 6 transmissions.
149 SpinDirection2,
150 /// Needs 6 transmissions.
151 ThreeDModeOn,
152 /// Needs 6 transmissions.
153 ThreeDModeOff,
154 SettingsRequest,
155 /// Needs 6 transmissions. Wait at least 35ms before next command.
156 SettingsSave,
157 /// Needs 6 transmissions.
158 ExtendedTelemetryEnable,
159 /// Needs 6 transmissions.
160 ExtendedTelemetryDisable,
161
162 // 15-19 are unassigned.
163 /// Needs 6 transmissions.
164 SpinDirectionNormal = 20,
165 /// Needs 6 transmissions.
166 SpinDirectonReversed,
167 Led0On,
168 Led1On,
169 Led2On,
170 Led3On,
171 Led0Off,
172 Led1Off,
173 Led2Off,
174 Led3Off,
175 AudioStreamModeToggle,
176 SilentModeToggle,
177 /// Needs 6 transmissions. Enables individual signal line commands.
178 SignalLineTelemetryEnable,
179 /// Needs 6 transmissions. Disables individual signal line commands.
180 SignalLineTelemetryDisable,
181 /// Needs 6 transmissions. Enables individual signal line commands.
182 SignalLineContinuousERPMTelemetry,
183 /// Needs 6 transmissions. Enables individual signal line commands.
184 SignalLineContinuousERPMPeriodTelemetry,
185
186 // 36-41 are unassigned.
187 /// 1ÂșC per LSB.
188 SignalLineTemperatureTelemetry = 42,
189 /// 10mV per LSB, 40.95V max.
190 SignalLineVoltageTelemetry,
191 /// 100mA per LSB, 409.5A max.
192 SignalLineCurrentTelemetry,
193 /// 10mAh per LSB, 40.95Ah max.
194 SignalLineConsumptionTelemetry,
195 /// 100erpm per LSB, 409500erpm max.
196 SignalLineERPMTelemetry,
197 /// 16us per LSB, 65520us max.
198 SignalLineERPMPeriodTelemetry,
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 const MAX_DUTY_CYCLE: u16 = 100;
206 const ZERO: u16 = 37;
207 const ONE: u16 = 75;
208
209 #[test]
210 fn duty_cycles_works() {
211 let frame = Frame::new(999, false).unwrap();
212 assert_eq!(
213 frame.duty_cycles(MAX_DUTY_CYCLE),
214 [
215 ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ONE, ONE, ONE, ZERO, ZERO, ONE, ZERO,
216 ZERO, 0
217 ]
218 );
219 }
220
221 #[test]
222 fn duty_cycles_at_zero() {
223 let frame = Frame::command(Command::MotorStop, false);
224 assert_eq!(
225 frame.duty_cycles(MAX_DUTY_CYCLE),
226 [
227 ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO,
228 ZERO, ZERO, 0
229 ]
230 );
231 }
232
233 #[test]
234 fn frame_constructs_correctly() {
235 let frame = Frame::new(998, false).unwrap();
236 assert_eq!(frame.speed(), 998);
237 assert!(!frame.telemetry_enabled());
238 assert_eq!(frame.crc(), 0x06);
239 }
240
241 #[test]
242 fn frame_constructs_correctly_with_telemetry() {
243 let frame = Frame::new(998, true).unwrap();
244 assert_eq!(frame.speed(), 998);
245 assert!(frame.telemetry_enabled());
246 assert_eq!(frame.crc(), 0x07);
247 }
248
249 #[test]
250 fn frame_constructs_correctly_off_centre() {
251 let frame = Frame::new(50, false).unwrap();
252 assert_eq!(frame.speed(), 50);
253 }
254
255 #[test]
256 fn frame_rejects_invalid_speed_values() {
257 assert!(Frame::new(2000, false).is_none())
258 }
259}