Skip to content

Latest commit

 

History

History
298 lines (216 loc) · 17.1 KB

README.md

File metadata and controls

298 lines (216 loc) · 17.1 KB

Pico DShot

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.

Setting up the repo

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.

Running the Unit Tests

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

Compile

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 .

Pinout

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

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.


Code Overview

  • include header files to setup dshot variables and functions
    • packet.h module to compose a dshot packet from a dshot command
    • dshot.h configure pico hw (pwm, dma, rt) for dshot
    • kissesctelem.h functions to process onewire telem (crc8, buffer --> data)
    • onewire.h configure pico hw for onewire (uart, rt)
  • lib/extern/
    • pico-sdk/ pico sdk submodule
    • Unity/ submodule for testing framework
  • examples/
    • simple/ most basic boilerplate to start sending dshot packets
    • keyboard_control/ allows you to use serial input to send dshot commands
    • dshot_led/ send dshot packets to builtin led to see how the packets are sent
    • onewire_telemetry/ setup esc to request telemetry data
  • test/ unit test packet.h and kissesctelem.h

Dependency Graph:

|-- onewire
|   |-- kissesctelem
|   |-- dshot
|   |   |-- packet

To Do

Backlog

  • 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() (in pwm.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 using lib/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 Protocol

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:

$$ \text{pulse period} = \frac{1}{f_\text{dshot}} $$

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:

$$ \text{Pause pulses (or bits)} = \lceil \frac{2 \mu \text{s}}{\text{Pulse Period}} \rceil $$

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.

Example

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)


Onewire Uart Telemetry

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
Loading
  1. Pico: send dshot packet with Telemetry = 1. Reset telemetry immediately.
  2. ESC: receive dshot packet; send a transmission over onewire uart telemetry to Pico.
  3. Pico: receive the transmission, check its CRC8 and then convert the transmission to telemetry data.

Onewire Transmission

Each transmission is transferred over UART at:

  • 115200 baudrate
  • 3.6 V (Use a 1k resistor pull-up to 2.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:

$$ \text{rpm} = \frac{\text{erpm}}{\text{\# Magnet pole} \times 2} $$


Sources

Docs and Sample Implementation

Explanations of DShot

Other

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 :)

Bidirectional DShot