This repo is being developed to use a RPi Pico to send dshot commands to ESCs.
Normally, a flight controller sends commands to an ESC to control a motor. In this repo, we are using a Rpi Pico as a stand in for the flight controller. This is a work in progress. This repo is being developed to be used as submodule for pico-tts, which is a project to measure drone motor KPIs using a pico.
git clone [email protected]:Guppy16/pico-dshot.git
git submodule update --init
cd lib/extern/pico-sdk
git submodule update --init lib/tinyusb
Note in the last line, we intialise TinyUSB
, because it is required to use the uart on the pico for serial read and write.
We use Unity Test as the framework for our tests. Assuming all the submodules have been setup:
mkdir test/build && cd $_
cmake ..
cmake --build .
ctest --verbose
Examples are provided in example/
.
Below shows how the simple example can be compiled (the others can be compiled similarily).
The simple example can be used to check if the motor and ESC are connected correctly.
Note that you may need to install the GCC cross compiler as mentioned in the pico-sdk quick-start guide.
mkdir examples/simple/build && cd $_
cmake ..
cmake --build .
The gpio pins can be configured in the examples in the corresponding main.cpp
file. This is what has been assumed:
pin | gpio | Pico Pad | ESC Pin |
---|---|---|---|
13 | UART RX | Onewire Uart TX Telemetry | |
14 | PWM | Dshot Signal | |
18 | GND | ESC Signal GND |
Note that the GND pin can be any ground pint, but Pin 18 is used as it's closest to the ESC signal pin.
Separately, note that you may need to solder a wire on the ESC UART TX pad to connect to the pico.
Upload dshot_example.uf2
to the pico, and open a serial connection to it.
Depending on your motor spec, it will automatically arm (i.e ready to receive commands; not start spinning).
The serial monitor will print some configuration options, which can be used to verify that the Pico is behaving.
Ideally, the motor will beep when connected to a power source.
include
header files to setup dshot variables and functionspacket.h
module to compose a dshot packet from a dshot commanddshot.h
configure pico hw (pwm, dma, rt) for dshotkissesctelem.h
functions to process onewire telem (crc8, buffer --> data)onewire.h
configure pico hw for onewire (uart, rt)
lib/extern/
pico-sdk/
pico sdk submoduleUnity/
submodule for testing framework
examples/
simple/
most basic boilerplate to start sending dshot packetskeyboard_control/
allows you to use serial input to send dshot commandsdshot_led/
send dshot packets to builtin led to see how the packets are sentonewire_telemetry/
setup esc to request telemetry data
test/
unit testpacket.h
andkissesctelem.h
Dependency Graph:
|-- onewire
| |-- kissesctelem
| |-- dshot
| | |-- packet
- Replace onewire telem with autotelemetry. Likely, the ESC can be reconfigured using some kind of passthrough. Protocol discussion on Github issue. GitHub issue with info on BlHel Suite.
- Attempt arm sequence according to BLHeli docs
- Currently dma writes to a PWM slice counter compare. This slice corresponds to two channels, hence dma may overwrite another channel. Is there a way to validate this? Can we use smth similar to
hw_write_masked()
(inpwm.h
)? - If composing a dshot pckt from cmd ever becomes the bottleneck, an alternative is to use a lookup table: address corresponds to 12 bit command (ignoring CRC), which maps to packets (an array of length 16, where each element is a 16 bit duty cycle). Memory usage: 2^12 address x (16 x 16 packet) = 2^20 bit word = 1 MB. This can be further compressed as the telemetry bit affects only the last two nibbles. Note: Pico flash = 2 MB.
- Test dshot performance using 125 MHz and 120 MHz mcu clk. May need to play around with
vco
usinglib/extern/pico-sdk/src/rp2_common/hardware_clocks/scripts/vcocalc.py
to find valid sys clock frequencies. - Write unit tests that will work on the Pico. Write normal unit tests similar to Example 2.
- C code style and documentation according to this guide
DShot is a digital protocol used to send commands from a flight controller to an ESC, in order to control a motor. DShot has discretised resolution, where as PWM is analogue (on the FC side albeit discretised on the ESC due to hw limitations). The digital protocal has advantages in accuracy, precision, resolution and communication. Typically, dshot commands are sent over PWM hardware (note that we are not controlling the motor using "pulse width modulation", but are rather piggy backing off of the PWM hardware to send dshot frames!).
A Dshot frame is constructed as follows:
Value | Telemetry | Checksum |
---|---|---|
11 bits | 1 bit | 4 bit |
0 - 2047 | Boolean | CRC |
Dshot Value | Meaning |
---|---|
0 | Disarm, but hasn't been implemented |
1 - 47 | Reserved for special use |
48 - 2047 | Throttle position (resolution of 2000) |
Each bit is transmit as a high/low duty cycle in a "pulse" using PWM hw:
bit | duty cycle |
---|---|
0 | < 33% |
1 | > 75% |
The dshot frequency defines the pulse period:
To indicate a frame reset, a pause of at least 2 μs is required between frames (source Betaflight wiki). This pause can be represented as 0 duty cycles pulses:
Dshot freq / kHz | pulse period / μs | Pause bits |
---|---|---|
150 | 6.67 | 1 |
600 | 1.67 | 2 |
1200 | 0.833 | 3 |
Our implementation uses a constant value of 4 bits as a pause between frames (i.e. we assume a max Dshot freq of 2000).
Hence, the PWM hw is sent a packet of 20 pulses (16 for a DShot frame and 4 for a frame reset).
This is set in packet.h::dshot_packet_length
.
To make the motors beep a DShot packet is constructed as follows:
The Dshot command is: Value = 1
, Telemetry = 1
→ First 12 bits of the frame: Frame[0:12] = [Value|Telemetry] = 0x003
→ The 4 bit crc is: CRC = 0 XOR 0 XOR 3 = 3
→ Concatonate Frame[0:16] = [Value|Telemetry|CRC] = 0x0033
→ Compose packet (with frame reset pulses at the end): Packet = LLLL LLLL LLHH LLHH 0000
The packet is transmit from left to right (i.e. big endian).
(Please note that most of this nomenclature is taken from the betaflight dshot wiki, but some of it may not be standard)
There are two overarching protocols that can be used to request telemetry data:
- uart (onewire / autotelemetry)
- dshot (bidirectional dshot / extended dshot telemetry)
These protocols are not mutually exclusive. This section is about obtaining telemetry using onewire uart telemetry. Onewire sends a variety of data every 800 μs, whereas bidirectional dshot just sends eRPM telemetry but every 80 μs (Dshot600). Onewire is described in the KISS ESC 32-bit series onewire telemetry protocol datasheet. The process looks like this:
sequenceDiagram
box Pico
participant pico_dshot as dshot
participant Pico onewire as onewire
end
box ESC
participant ESC dshot as dshot
participant ESC onewire as onewire
end
loop Every 1ms
Note over pico_dshot: Set Telemetry = 1
pico_dshot->>ESC dshot: Send dshot packet
Note over pico_dshot: Reset Telemetry = 0
ESC onewire->>Pico onewire: 10 bytes Uart Telemetry _Transmission_
Note over pico_dshot, Pico onewire: Process transmission <br/> into telemetry data
end
- Pico: send dshot packet with
Telemetry = 1
. Reset telemetry immediately. - ESC: receive dshot packet; send a transmission over onewire uart telemetry to Pico.
- Pico: receive the transmission, check its CRC8 and then convert the transmission to telemetry data.
Each transmission is transferred over UART at:
115200 baudrate
3.6 V
(Use a1k
resistor pull-up to2.5 - 5 V
to remove noise)10 bytes
:
Byte(s) | Value | Resolution |
---|---|---|
0 | Temperature | 1 C |
1 | 2 | (centi) Voltage | 10 mV |
3 | 4 | (centi) Current | 10 mA |
5 | 6 | Consumption | 1 mAh |
7 | 8 | Electrical rpm | 100 erpm |
9 | 8-bit CRC |
To convert erpm to rpm:
- Pico SDK API Docs. Some quick links: dma
- Documentation on the Pico incl spec, datasheets, pinout, etc.
- Pico examples from the rpi github incl
dma/
. There's an interesting example on pairing an adc with dma here. Note that when viewing pico examples, they use#include "pico/stdlib.h"
. This is not to be used in the Arduino framework! as explained in this post. - Unity Assertion Reference is a useful guide handbook for unit testing with Unity framework.
- Unit testing on Embedded Target using PlatformIO
- CMake file for Unity Testing was inspired from Rainer Poisel, Throw The Switch and Testing with CMake and CTest.
- Diagrams using Mermaid.js
- Betaflight DShot Wiki
- Spencer's HW Blog has a quick overview on the DShot protocol, list of the dshot command codes (which shd be sourced somewhere in the betaflight repo), and implmenetation overviews using scp and dma.
- This post has a simple explanation of dshot with a few examples
- DShot - the missing handbook has supported hw, dshot frame example, arming, telemetry, bi-directional dshot
- BLHeli dshot special command spec
- Missing Handbook also has a good explanation of commands Original RC Groups post on dshot
- SiieeFPV has a YT vid explaining DMA implementation on a Kinetis K66. This vid was a useful in understanding what was needed for our implementation.
- KISS ESC onewire telemetry protocol pdf
- CRC8 algorithm explanation website
- Use Zadig to install drivers for the RPi boot interface on Windows. This makes the flashing experience a LOT better! This has been mentioned in platform io and github forums (in depth here, briefly here, which links to this).
- Wiz IO Pico: seems like an alternative to the Arduino framework used in PlatformIO? More details can be found on their Baremetal wiki
- Rpi Pico Forum post. This person has balls to try and implemenet dshot using assembly!!
- List of Arduino libraries for the RP2040. I've not used any yet
- Interesting post on processing serial without blocking
- Upload port required issue. I don't think this issue will be faced if using Zadig
- BLHeli 32 Manual
- In the future, this repo may want to use GitHub hosted runners to create GitHub Packages. This may require some knowledge on setting up GitHub Actions, as well as the Workflow syntax
- A thread on the rpi forum about inspecting the contents of the alarm pool for logging
- A blog post with a dshot implementation using SPI with an Arduino Uno
NOTE: (Although we don't use this functionality), a common implmentation measuring timer uses 32 bit time (note that 64 bit is possible using timer_hw->timerawl
but more effort..)
timer_hw->timerawl
returns a 32 bit number representing the number of microseconds since power on- This will overflow at around 72 hrs, so must assume that this longer than the operation timer of the pico
- This has been the cause of many accidental failures in the past :)
- DShot - the missing handbook also talks about bi-directional dshot
- RPi Pico Forum thread on Evaluating PWM signal. This could be useful for bidirectional dshot
- Pico Example to measure duty cycle
- Interesting PR on the first implementation of bidir dshot. This discussion alludes to the politics of the protocol
- Note that we need to check the FW version on the ESC to see if it supports bidir (and EDT). BLHeli Passthrough is implemented as an Arduino Lib for ESP32 and some Arduinos. A good exercise would be to add support for the Pico. This may be a betaflight implementation of passthrough, but I couldn't understand it. BLHeli Suite is also needed.
- Researching this topic, I came across DMA "burst" mode, which apparently helps in transitioning from send to receive. Not sure, but maybe a starting point can be achieved from this post
- Chained DMA may be useful to switch from write to read configuration