Skip to content

jettify/uf-dshot

Repository files navigation

uf-dshot

CI codecov crates.io docs.rs

uf-dshot is a no_std Rust crate for packing and unpacking DShot frames. It supports standard DShot command and throttle frames, bidirectional DShot telemetry, and an STM32 example backend for Embassy based projects.

Features

scope
  • Encode DShot throttle and command frames
  • Request telemetry on the separate wire for standard DShot
  • Decode bidirectional DShot telemetry from the same wire
  • Provide protocol level types and timing hints for platform code
  • Offer an STM32 specific integration layer behind the embassy-stm32 feature

Cargo Features

  • defmt
    • Enables defmt::Format for public types
  • embassy-stm32
    • Enables Embassy based STM32 helpers
    • Pulls in embassy-sync, embassy-time, and the STM32 HAL dependency

Installation

Add the crate to Cargo.toml:

[dependencies]
uf-dshot = "0.1"

Enable features when needed:

[dependencies]
uf-dshot = { version = "0.1", features = ["defmt", "embassy-stm32"] }

Or use Cargo:

cargo add uf-dshot

Core Example (Platform Agnostic)

use uf_dshot::{Command, DshotTx};

fn main() {
    let tx = DshotTx::standard().with_telemetry_request(true);

    let throttle = tx.throttle(200).unwrap();
    assert!(throttle.payload != 0);
    assert!(tx.telemetry_request());

    let cmd = tx.command(Command::Beep1);
    let bits = cmd.bits_msb_first();
    assert_eq!(bits.len(), 16);
}

For command execution timing, use Command::exec_policy(). Commands that are often documented with 6 repeats use a conservative 10-repeat policy in this crate (similar to Betaflight).

STM32 Embassy Example

#![no_std]
#![no_main]

#[macro_use]
mod fmt;

use embassy_time::{Duration, Ticker};

#[cfg(not(feature = "defmt"))]
use panic_halt as _;

use embassy_executor::Spawner;
use uf_dshot::embassy_stm32::{DshotPortPin, InterruptHandler, Stm32BidirDshotPort};
use uf_dshot::DshotSpeed;
#[cfg(feature = "defmt")]
use {defmt_rtt as _, panic_probe as _};

const ESC_SPEED: DshotSpeed = DshotSpeed::Dshot300;
const DEMO_THROTTLE: u16 = 150;
const LOG_INTERVAL: u32 = 100;
const LOG_INTERVAL_ERR: u32 = 1;

embassy_stm32::bind_interrupts!(struct DmaIrqs {
    DMA2_STREAM3 => InterruptHandler<embassy_stm32::peripherals::DMA2_CH3>;
});

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_stm32::init(Default::default());

    let motor_pin = DshotPortPin::new(p.PA8);

    let mut esc = unwrap!(Stm32BidirDshotPort::new_ch1(
        p.TIM1, p.DMA2_CH3, DmaIrqs, motor_pin, ESC_SPEED,
    ));

    let frame_period =
        Duration::from_micros(u64::from(ESC_SPEED.timing_hints().min_frame_period_us) * 3);
    let mut ticker = Ticker::every(frame_period);

    info!("start arm");
    unwrap!(esc.arm_for(Duration::from_secs(5)).await);

    info!("spinning at throttle {}", DEMO_THROTTLE);
    let mut frame_count = 0u32;
    loop {
        frame_count = frame_count.wrapping_add(1);
        match esc.send_throttle_and_receive(DEMO_THROTTLE).await {
            Ok(telemetry) => {
                if frame_count % LOG_INTERVAL == 0 {
                    info!("frame={} telemetry={:?}", frame_count, telemetry);
                }
            }
            Err(err) => {
                if frame_count % LOG_INTERVAL_ERR == 0 {
                    error!("frame={} err={:?}", frame_count, err);
                }
            }
        }
        ticker.next().await;
    }
}

Inspirations

  1. https://github.com/symonb/Bidirectional-DSHOT-and-RPM-Filter
  2. https://symonb.github.io/docs/drone/ESC/ESC_prot_impl_2_2
  3. https://brushlesswhoop.com/dshot-and-bidirectional-dshot/
  4. https://github.com/Ragarnoy/embassy-dshot

License

This project is licensed under the Apache 2.0. See the LICENSE file for details.

About

No description or website provided.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors