This repo is being developed to use a RPi Pico to send dshot commands to ESCs. This is a work in progress.
- git clone repo
- git submodule init
- git submodule update
cd lib/extern/pico-sdk; git submodule update --init
<-- This was required forTinyUSB
This repo is being developed to use a RPi Pico to send dshot commands to ESCs. This is a work in progress.
We use Unity Test as the framework for our tests. The CMake file was inspired from the Rainer Poisel, Throw The Switch and Testing with CMake and CTest. Assuming all the submodules have been setup
mkdir test/build && cd $_
cmake ..
cmake --build .
ctest --verbose
|-- lib
|-- dshot
|-- config
|-- tts
|-- shoot
|-- utils
|-- src
| -- main.cpp
|-- test
| -- test_dshot.cpp
-
lib/
contains the libraries used to implement dshot on the picodshot/
is a standalone module that is used to convert a dshot command to a frame that can be sent using pwm hardwareconfig/
a header file for dshot globals, which are used to configure the hardware intts/
tts/
contains the hardware implementations for dma, pwm, alarm poolshoot/
implements sending dshot commands using the pwm hw as well as a repeating timer. A future merge request will add telem over uart in this moduleutils/
just an led flashing for debugging
-
src/
containsmain.cpp
which uses serial input to send dshot commands -
test/
contains a unit test fordshot/
as well as a cmake config file
Dependency Graph:
|-- shoot
| |-- tts
| | |-- config
| |-- dshot
|-- utils
- Convert
dshot/
module to just a header file using static inlines (make sure to check it works with the unit tests) - Move
config.h
totts/dshotglobals.h
. Wrap variables in a namespace and remove prefixDSHOT_
(a bit difficult because dshot namespace already exists) -
tts.h
can be renameddshothw.h
? - Maybe change / clarify the use of code and command
- attempt proper arm sequence
- Try:
DSHOT_SPEED = DEBUG ? 0.008 : 1200 // kHz
(this may get rid of some other ternaries on DEBUG) - Add validation to ensure PWM, DMA, repeating timer have been setup correctly. MCU_CLK could take in a measured reading. Use
lib/extern/pico-sdk/src/rp2_common/hardware_clocks/scripts/vcocalc.py
to find valid sys clock frequencies. - Currently dma writes to a PWM counter compare. This actually writes to two dma channels (because upper / lower 16 bits are separate counters). Hence we render one dma channel useless. Is it possible to implement this in a better way?
- Do we need to use the Arduino framework? Or can we just use the Pico SDK and import libraries 3rd party libs if necessary? If the latter, we could either consider Wiz IO or check out this post.
- Transfer
print_*_setup
functions to logging / utils? Maybe checkrefactor
branch. Maybe include the serial printf lib. There is an interesting post on printable classes - Scheme to represent DShot codes. enum is possible for special codes, but shouldn't be used for throttle values?
- Explore the idea of using / generating a look up table to convert dshot to pwm counter values (and vice versa for bidir dshot). Can this be hooked up with Programmable IO? Memory usage: 2^16 command x 32 bit word = 32k x 64 bit word (that might be too much).
- I believe that the uart and serial are initialised in
stdio_init_all()
(called inmain.cpp
). Perhaps we should usestdio_usb_init()
andstdio_uart_init()
separately. For telemetry (not yet committed), Peter usedstdio_uart_init()
afterstdio_init_all()
and didn't have errors.
A flight controller (Pico) sends commands to the ESC to control the motor. DShot is a digital protocol which typically sends commands using PWM hardware (note that we are not sending PWM frames, but are rather piggy backing off of its hardware to send dshot frames!). DShot is has discretised resolution, where as PWM was analogue on FC side (albeit discretised on the ESC due to hw limitations), however the advantages in accuracy, precision, resolution and communication outweigh this.
A Dshot command is constructed as follows:
- 11 bit number representing the code (0 to 2047)
- 1 bit telemetry flag
- 4 bit CRC
To transmit this command we construct a frame by converting the bits to a voltage signal. Each bit is represented as a duty cycle on pwm hw
- 0 = < 33%
- 1 = > 75%
- Note that a prefix and suffix 0 duty cycle (not a 0 bit!) is required in transmission to tell each frame apart
The period of each bit (i.e. the period set in the PWM hw) is determined by by the DShot freq. i.e. DShot150 sets the PWM hw freq to 150 kHz
Dshot Codes: 0 - Disarm (though not implemented?) 1 - 47: reserved for special use 48 - 2047: Throttle (2000 steps of precision)
For example, to make the motors beep the DShot frame is constructed as follows:
Code = 1
, Telemetry = 1
→ First 12 bits of the command 0x003
→ CRC = 0 XOR 0 XOR 3 = 3
→ Command = 0x0033
→ Transmitted from left to right, the frame is: LLLL LLLL LLHH LLHH
(Please note that this nomenclature is not standard, but I believe it is clear)
It takes some time to calculate and send a dshot frame. To mitigate the effect of calculation, the implementation can use two buffers to store the DMA frame: a main buffer to store the current frame being sent by DMA, and a second buffer to store the next frame. This second buffer is only used if DMA is still sending the current frame, otherwise we default to storing the next frame in the main buffer. Further optimisation can be done by checking if the current dshot code and telem are the same as the next one, in which case there is no need to recalculate the frame.
Aim: Gather data on motor performance
-
linear ramp test (how long do we ramp for?)
-
step response
-
code a pid loop, and see it's effect on motor performance
-
Interestingly, since the number of dshot values is discretised, it might be possible to create a table characterising the response from one throttle value to another!?
-
In
send_dshot_frame()
, there could be a safety measure to see if the motors are responding, and only then send a command. If not, don't send a command, and the ESC will disarm itself.
Possible pico telemetry:
- Count number of dshot frames sent
- Measure how long each main loop takes
- Measure min and max dshot update freq
- Use Zadig to install drivers for the RPi boot interface. This makes the flashing experience a LOT better! A thread on the platform io forum goes in to more detail. [This thread[(https://community.platformio.org/t/raspberry-pi-pico-upload-problem/22809/7) also linked to this github comment mentions to use Zadig.
-
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. -
PlatformIO Documentation on Pico. It only mentions the Arduino framework, but more seem to be avaialable (see other links here).
-
Unity Assertion Reference is a useful guide handbook for unit testing with Unity framework.
- 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.
-
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, but it might be best to use them as reference instead of importing them?
-
Interesting post on processing serial without blocking
-
Upload port required issue. I don't think this issue will be faced if using Zadig
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