#![no_std]
pub trait DshotProtocol {
fn compute_crc(value: u16) -> u16;
fn is_inverted() -> bool;
}
#[derive(Debug, Clone, Copy)]
pub struct NormalDshot;
impl DshotProtocol for NormalDshot {
fn compute_crc(value: u16) -> u16 {
(value ^ (value >> 4) ^ (value >> 8)) & 0x0F
}
fn is_inverted() -> bool {
false
}
}
#[derive(Debug, Clone, Copy)]
pub struct BidirectionalDshot;
impl DshotProtocol for BidirectionalDshot {
fn compute_crc(value: u16) -> u16 {
(!(value ^ (value >> 4) ^ (value >> 8))) & 0x0F
}
fn is_inverted() -> bool {
true
}
}
#[derive(Copy, Clone, Debug)]
pub struct Frame<P: DshotProtocol = NormalDshot> {
inner: u16,
_protocol: core::marker::PhantomData<P>,
}
impl<P: DshotProtocol> Frame<P> {
pub fn new(speed: u16, request_telemetry: bool) -> Option<Self> {
if speed >= 2000 {
return None;
}
let translated_throttle = (speed + 48) << 5;
let mut frame = Self {
inner: translated_throttle,
_protocol: core::marker::PhantomData,
};
if request_telemetry {
frame.inner |= 0x10;
}
frame.compute_crc();
Some(frame)
}
pub fn command(command: Command, request_telemetry: bool) -> Self {
let mut frame = Self {
inner: (command as u16) << 5,
_protocol: core::marker::PhantomData,
};
if request_telemetry {
frame.inner |= 0x10;
}
frame.compute_crc();
frame
}
pub fn speed(&self) -> u16 {
(self.inner >> 5) - 48
}
pub fn telemetry_enabled(&self) -> bool {
self.inner & 0x10 != 0
}
pub fn crc(&self) -> u16 {
self.inner & 0x0F
}
fn compute_crc(&mut self) {
let value = self.inner >> 4;
let crc = P::compute_crc(value);
self.inner = (self.inner & !0x0F) | crc;
}
pub fn inner(&self) -> u16 {
self.inner
}
pub fn duty_cycles(&self, max_duty_cycle: u16) -> [u16; 17] {
let mut value = self.inner;
let mut rv = [max_duty_cycle * 2 / 3; 17];
for item in rv.iter_mut() {
let bit = value & 0x8000;
if bit == 0 {
*item = max_duty_cycle / 3;
}
value <<= 1;
}
rv[16] = 0;
rv
}
}
pub type NormalFrame = Frame<NormalDshot>;
pub type BidirectionalFrame = Frame<BidirectionalDshot>;
#[derive(Copy, Clone, Debug)]
pub enum Command {
MotorStop = 0,
Beep1,
Beep2,
Beep3,
Beep4,
Beep5,
ESCInfo,
SpinDirection1,
SpinDirection2,
ThreeDModeOn,
ThreeDModeOff,
SettingsRequest,
SettingsSave,
ExtendedTelemetryEnable,
ExtendedTelemetryDisable,
SpinDirectionNormal = 20,
SpinDirectonReversed,
Led0On,
Led1On,
Led2On,
Led3On,
Led0Off,
Led1Off,
Led2Off,
Led3Off,
AudioStreamModeToggle,
SilentModeToggle,
SignalLineTelemetryEnable,
SignalLineTelemetryDisable,
SignalLineContinuousERPMTelemetry,
SignalLineContinuousERPMPeriodTelemetry,
SignalLineTemperatureTelemetry = 42,
SignalLineVoltageTelemetry,
SignalLineCurrentTelemetry,
SignalLineConsumptionTelemetry,
SignalLineERPMTelemetry,
SignalLineERPMPeriodTelemetry,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ErpmTelemetry {
pub shift: u8,
pub period_base: u16,
pub crc: u8,
}
impl ErpmTelemetry {
pub fn try_from_raw(raw: u16) -> Option<Self> {
let payload = raw >> 4; let received_crc = (raw & 0x0F) as u8;
let expected_crc = NormalDshot::compute_crc(payload) as u8;
if received_crc != expected_crc {
return None;
}
let shift = ((payload >> 9) & 0x07) as u8;
let period_base = payload & 0x1FF;
Some(Self {
shift,
period_base,
crc: received_crc,
})
}
pub fn period_us(&self) -> Option<u32> {
if self.period_base == 0 {
None } else {
let period = (self.period_base as u32) << self.shift;
if period == 0 {
None
} else {
Some(period)
}
}
}
pub fn erpm(&self) -> u32 {
match self.period_us() {
Some(period) if period > 0 => 60_000_000 / period,
_ => 0,
}
}
pub fn to_raw(&self) -> u16 {
let payload = ((self.shift as u16) << 9) | (self.period_base & 0x1FF);
let crc = self.crc as u16;
(payload << 4) | crc
}
}
#[cfg(test)]
mod tests {
use super::*;
const MAX_DUTY_CYCLE: u16 = 100;
const ZERO: u16 = MAX_DUTY_CYCLE / 3;
const ONE: u16 = ZERO * 2;
#[test]
fn duty_cycles_works() {
let frame = NormalFrame::new(999, false).unwrap();
assert_eq!(
frame.duty_cycles(MAX_DUTY_CYCLE),
[
ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ONE, ONE, ONE, ZERO, ZERO, ONE, ZERO,
ZERO, 0
]
);
}
#[test]
fn duty_cycles_at_zero() {
let frame = NormalFrame::command(Command::MotorStop, false);
assert_eq!(
frame.duty_cycles(MAX_DUTY_CYCLE),
[
ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO,
ZERO, ZERO, 0
]
);
}
#[test]
fn frame_constructs_correctly() {
let frame = NormalFrame::new(998, false).unwrap();
assert_eq!(frame.speed(), 998);
assert!(!frame.telemetry_enabled());
assert_eq!(frame.crc(), 0x06);
}
#[test]
fn frame_constructs_correctly_with_telemetry() {
let frame = NormalFrame::new(998, true).unwrap();
assert_eq!(frame.speed(), 998);
assert!(frame.telemetry_enabled());
assert_eq!(frame.crc(), 0x07);
}
#[test]
fn frame_constructs_correctly_off_centre() {
let frame = NormalFrame::new(50, false).unwrap();
assert_eq!(frame.speed(), 50);
}
#[test]
fn frame_rejects_invalid_speed_values() {
assert!(NormalFrame::new(2000, false).is_none())
}
#[test]
fn bidir_duty_cycles_works() {
let frame = BidirectionalFrame::new(998, false).unwrap();
assert_eq!(
frame.duty_cycles(MAX_DUTY_CYCLE),
[
ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ONE, ONE, ZERO, ZERO, ONE, ZERO,
ZERO, ONE, 0
]
);
}
}