rcbench
Servo and ESC test bench: panel, IOmcu and shared C
Loading...
Searching...
No Matches
dshot.h File Reference

DShot: sixteen bits per frame to an ESC (electronic speed controller), and the GCR (group-coded recording) burst that comes back. More...

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

Go to the source code of this file.

Data Structures

struct  dshot_telem_t
 One decoded response. More...
struct  dshot_edt_t
 When to send DSHOT_CMD_EDT_ENABLE, and whether the ESC has answered. More...

Macros

#define DSHOT_FRAME_BITS   16u
#define DSHOT_VALUE_MAX   2047u
#define DSHOT_CMD_MAX   47u
#define DSHOT_THROTTLE_MIN   48u
#define DSHOT_THROTTLE_MAX   2047u
#define DSHOT_CMD_REPEATS   10u
 How many times a command has to be repeated before an ESC acts on it.
#define DSHOT_TELEM_BITS   21u
 Bits in the response, including the leading one the line code produces.
#define DSHOT_TELEM_RATE_NUM   5u
 The response rate, as a fraction of the DShot rate: 5/4.
#define DSHOT_TELEM_RATE_DEN   4u
#define DSHOT_EDT_MV_PER_COUNT   250u
 Units of the extended telemetry payload, per count of its eight bits (0 to 255).
#define DSHOT_EDT_A_PER_COUNT   1u
#define DSHOT_EDT_DEGC_PER_COUNT   1u
#define DSHOT_EDT_RETRY_MS   500u
 From the last repeat of an ask that got no answer to the start of the next.
#define DSHOT_EDT_ASKS   10u
 Asks per run before an ESC that never answers is left alone.

Enumerations

enum  dshot_cmd_t {
  DSHOT_CMD_MOTOR_STOP = 0 , DSHOT_CMD_BEEP1 = 1 , DSHOT_CMD_BEEP2 = 2 , DSHOT_CMD_BEEP3 = 3 ,
  DSHOT_CMD_BEEP4 = 4 , DSHOT_CMD_BEEP5 = 5 , DSHOT_CMD_ESC_INFO = 6 , DSHOT_CMD_SPIN_DIRECTION_1 = 7 ,
  DSHOT_CMD_SPIN_DIRECTION_2 = 8 , DSHOT_CMD_3D_MODE_OFF = 9 , DSHOT_CMD_3D_MODE_ON = 10 , DSHOT_CMD_SETTINGS_REQUEST = 11 ,
  DSHOT_CMD_SAVE_SETTINGS = 12 , DSHOT_CMD_EDT_ENABLE = 13 , DSHOT_CMD_EDT_DISABLE = 14 , DSHOT_CMD_SPIN_NORMAL = 20 ,
  DSHOT_CMD_SPIN_REVERSED = 21
}
 The command range, 0 to 47. More...
enum  dshot_telem_kind_t {
  DSHOT_TELEM_ERPM = 0 , DSHOT_TELEM_TEMPERATURE , DSHOT_TELEM_VOLTAGE , DSHOT_TELEM_CURRENT ,
  DSHOT_TELEM_DEBUG1 , DSHOT_TELEM_DEBUG2 , DSHOT_TELEM_STRESS , DSHOT_TELEM_STATUS ,
  DSHOT_TELEM_KINDS
}
 What one response carried. More...

Functions

uint16_t dshot_frame (uint16_t value, bool telemetry, bool inverted)
 Build a frame.
uint16_t dshot_throttle (uint16_t command, uint16_t span)
 Map a command of 0..span onto the throttle range.
uint32_t dshot_edt_millivolts (uint16_t count)
 Millivolts for a DSHOT_TELEM_VOLTAGE payload (at most 255 * 250 = 63,750).
uint32_t dshot_edt_amps (uint16_t count)
 Amperes for a DSHOT_TELEM_CURRENT payload.
uint32_t dshot_edt_celsius (uint16_t count)
 Degrees Celsius for a DSHOT_TELEM_TEMPERATURE payload.
bool dshot_gcr_decode (uint32_t line, uint16_t *out)
 Undo the line code and the group code: 21 line bits to a 16-bit value.
uint32_t dshot_gcr_encode (uint16_t value)
 The other direction, for the suite and for anything that fakes an ESC.
bool dshot_telem_from_value (uint16_t value, bool edt, dshot_telem_t *out)
 A 16-bit value to what it meant, checksum first.
bool dshot_telem_decode (uint32_t line, bool edt, dshot_telem_t *out)
 The two steps above, together: 21 line bits to a reading.
uint16_t dshot_telem_value (dshot_telem_kind_t kind, uint16_t payload)
 Build a response, for the suite and for anything that fakes an ESC.
uint32_t dshot_rpm (uint32_t erpm, uint8_t pole_pairs)
 Electrical rpm to mechanical rpm.
void dshot_edt_bind (dshot_edt_t *e, uint32_t now_ms)
 A new binding: nothing asked, nothing heard, an ask due at once.
void dshot_edt_idle (dshot_edt_t *e, uint32_t now_ms)
 Not driving: the next run owes the ask again, due from its first frame.
bool dshot_edt_frame (dshot_edt_t *e, bool stopped, uint32_t now_ms)
 Whether the frame about to go out is a repeat of DSHOT_CMD_EDT_ENABLE.
void dshot_edt_heard (dshot_edt_t *e, dshot_telem_kind_t kind)
 A reply arrived; an extended kind after an ask ends the asking.
bool dshot_rx_bits (const uint32_t *samples, size_t words, uint8_t oversample, uint32_t *line)
 Turn an oversampled capture of the line into the 21 bits of the response.

Detailed Description

DShot: sixteen bits per frame to an ESC (electronic speed controller), and the GCR (group-coded recording) burst that comes back.

Implements the DShot protocol of ESCs. DShot is the name of a protocol; it is used here only to say which ESCs this module works with. The project claims no right in the name (NOTICE, "Trademarks").

A frame is eleven bits of value, one bit asking for telemetry and four bits of checksum, sent most significant bit first. There is no start bit, no stop bit and no idle pattern: the bit period is the whole synchronisation, so both ends agree on the rate by configuration and a frame is located by the gap before it. A one is high for 75% of the bit and a zero for 37.5%.

The value is not a throttle everywhere in its range. Zero stops the motor, 1 to 47 are commands, and only 48 to 2047 are throttle. A driver that maps a proportion of travel straight onto 0..2047 sends beeps and direction changes on its way up from idle.

Bidirectional DShot inverts the line and the checksum. The ESC answers about 30us after the frame with 21 bits at 5/4 of the DShot rate, GCR coded, carrying an electrical period rather than a speed. Nothing about that burst is timed here: this file turns a captured sample stream into bits, bits into a value, and a value into rpm (revolutions per minute). The capture itself is a PIO (programmable input/output) program on the IOmcu.

What is written from the published description and has not been confirmed against an ESC on this bench: the extended-telemetry frame types and their units, and the 5/4 response rate. The eRPM path is exercised end to end by the host suite against frames this file also builds, which proves the arithmetic and not the wire.

SPDX-License-Identifier: MIT

Macro Definition Documentation

◆ DSHOT_CMD_MAX

#define DSHOT_CMD_MAX   47u

above this the value is throttle

◆ DSHOT_CMD_REPEATS

#define DSHOT_CMD_REPEATS   10u

How many times a command has to be repeated before an ESC acts on it.

A command is an ordinary frame; the ESC tells a command from a glitch by counting repeats. Sending one once does nothing at all, which looks exactly like a driver that is not working.

◆ DSHOT_EDT_MV_PER_COUNT

#define DSHOT_EDT_MV_PER_COUNT   250u

Units of the extended telemetry payload, per count of its eight bits (0 to 255).

Voltage is 0.25V per count, current 1A per count, temperature 1 degree Celsius per count.

◆ DSHOT_FRAME_BITS

#define DSHOT_FRAME_BITS   16u

bits in one frame

◆ DSHOT_THROTTLE_MAX

#define DSHOT_THROTTLE_MAX   2047u

highest throttle value

◆ DSHOT_THROTTLE_MIN

#define DSHOT_THROTTLE_MIN   48u

lowest throttle value

◆ DSHOT_VALUE_MAX

#define DSHOT_VALUE_MAX   2047u

eleven bits

Enumeration Type Documentation

◆ dshot_cmd_t

The command range, 0 to 47.

Only the ones this bench sends are named. The rest are passed through as numbers: naming a command implies it was tried, and the programmer screen sends codes the ESC's own documentation lists.

◆ dshot_telem_kind_t

What one response carried.

An ESC that has been sent DSHOT_CMD_EDT_ENABLE interleaves the other kinds between eRPM frames. One that has not sends nothing but eRPM, so a decoder that only understands eRPM still works and simply never sees the rest.

Enumerator
DSHOT_TELEM_ERPM 

an electrical period, in microseconds

DSHOT_TELEM_TEMPERATURE 

degrees Celsius

DSHOT_TELEM_VOLTAGE 

DSHOT_EDT_MV_PER_COUNT millivolts

DSHOT_TELEM_CURRENT 

DSHOT_EDT_A_PER_COUNT amperes

DSHOT_TELEM_DEBUG1 

type nibble 0x08; payload not decoded

DSHOT_TELEM_DEBUG2 

type nibble 0x0A; payload not decoded

DSHOT_TELEM_STRESS 

type nibble 0x0C; payload not decoded

DSHOT_TELEM_STATUS 

type nibble 0x0E; payload not decoded

DSHOT_TELEM_KINDS 

How many kinds there are, for anything holding one slot per kind.

Function Documentation

◆ dshot_edt_amps()

uint32_t dshot_edt_amps ( uint16_t count)

Amperes for a DSHOT_TELEM_CURRENT payload.

Parameters
countPayload count, 0 to 255.
Returns
Amperes.

◆ dshot_edt_bind()

void dshot_edt_bind ( dshot_edt_t * e,
uint32_t now_ms )

A new binding: nothing asked, nothing heard, an ask due at once.

Parameters
eAsk state to bind.
now_msCurrent time, in milliseconds.

◆ dshot_edt_celsius()

uint32_t dshot_edt_celsius ( uint16_t count)

Degrees Celsius for a DSHOT_TELEM_TEMPERATURE payload.

Parameters
countPayload count, 0 to 255.
Returns
Degrees Celsius.

◆ dshot_edt_frame()

bool dshot_edt_frame ( dshot_edt_t * e,
bool stopped,
uint32_t now_ms )

Whether the frame about to go out is a repeat of DSHOT_CMD_EDT_ENABLE.

stopped says the throttle this frame would carry is zero. False means send the throttle.

Parameters
eAsk state.
stoppedtrue when the throttle this frame would carry is zero.
now_msCurrent time, in milliseconds.
Returns
true when the frame is a repeat of the ask.

◆ dshot_edt_heard()

void dshot_edt_heard ( dshot_edt_t * e,
dshot_telem_kind_t kind )

A reply arrived; an extended kind after an ask ends the asking.

Parameters
eAsk state.
kindKind of the reply that arrived.

◆ dshot_edt_idle()

void dshot_edt_idle ( dshot_edt_t * e,
uint32_t now_ms )

Not driving: the next run owes the ask again, due from its first frame.

Called on every service while disarmed, so it is idempotent; asked is kept.

Parameters
eAsk state.
now_msCurrent time, in milliseconds.

◆ dshot_edt_millivolts()

uint32_t dshot_edt_millivolts ( uint16_t count)

Millivolts for a DSHOT_TELEM_VOLTAGE payload (at most 255 * 250 = 63,750).

Parameters
countPayload count, 0 to 255.
Returns
Millivolts.

◆ dshot_frame()

uint16_t dshot_frame ( uint16_t value,
bool telemetry,
bool inverted )

Build a frame.

value is 0..2047 and is used as given: mapping travel onto it is dshot_throttle()'s job, and a caller that wants to send command 12 must be able to say 12. telemetry asks the ESC to answer on its telemetry wire (the separate serial one, not the signal line). inverted selects bidirectional DShot, whose checksum is the complement of the ordinary one so that a receiver cannot mistake one protocol for the other.

Returns 0 for a value above 2047: zero is motor-stop, which is the safe reading of a number this end could not encode.

Parameters
valueFrame value, 0..2047, used as given.
telemetrytrue asks the ESC to answer on its telemetry wire.
invertedtrue selects bidirectional DShot (complemented checksum).
Returns
The 16-bit frame. 0 for a value above 2047: zero is motor-stop, which is the safe reading of a number this end could not encode.

◆ dshot_gcr_decode()

bool dshot_gcr_decode ( uint32_t line,
uint16_t * out )

Undo the line code and the group code: 21 line bits to a 16-bit value.

The line is differential – each bit is the previous one exclusive-ored with the data – so a run of identical bits becomes a level that does not change, which is what keeps the ESC's clock and this end's from having to agree for long. Undoing it is x ^ (x >> 1), and the leading bit falls off the top. The 20 bits below are four quintets, each one nibble.

Returns false for a quintet that is not in the table, which is what a missed or extra sample looks like.

Parameters
lineThe 21 captured line bits.
outReceives the 16-bit value.
Returns
false for a quintet that is not in the table, which is what a missed or extra sample looks like; true otherwise.

◆ dshot_gcr_encode()

uint32_t dshot_gcr_encode ( uint16_t value)

The other direction, for the suite and for anything that fakes an ESC.

Parameters
valueThe 16-bit value, checksum included.
Returns
The 21 line bits.

◆ dshot_rpm()

uint32_t dshot_rpm ( uint32_t erpm,
uint8_t pole_pairs )

Electrical rpm to mechanical rpm.

pole_pairs is half the magnet count, and it is the one number the wire does not carry: an ESC reports electrical periods and has no idea what it is bolted to. Zero pole pairs returns zero rather than dividing by it.

Parameters
erpmElectrical rpm.
pole_pairsPole pairs of the motor.
Returns
Mechanical rpm, or 0 when pole_pairs is 0.

◆ dshot_rx_bits()

bool dshot_rx_bits ( const uint32_t * samples,
size_t words,
uint8_t oversample,
uint32_t * line )

Turn an oversampled capture of the line into the 21 bits of the response.

The capture is a bit per sample, most significant bit of word 0 first, at oversample samples per response bit. Sampling at a fixed rate and dividing is not enough on its own: the ESC's bit rate is its own crystal's, a percent or two from this end's, and 21 bits is long enough for that to walk a sample point off the end of a bit. The phase is therefore reset at every transition, the same way a UART (universal asynchronous receiver-transmitter) resynchronises on a start bit, and each bit is read from the middle of its window.

The response is located by the first falling edge: bidirectional DShot idles high, so everything before that edge is idle line and not data.

Returns false when the capture holds no falling edge, or ends before 21 bits have been read.

Parameters
samplesCapture, one bit per sample, MSB of word 0 first.
wordsNumber of 32-bit words in samples.
oversampleSamples per response bit, 2 or more.
lineReceives the 21 response bits.
Returns
false when the capture holds no falling edge, or ends before 21 bits have been read; true otherwise.

◆ dshot_telem_decode()

bool dshot_telem_decode ( uint32_t line,
bool edt,
dshot_telem_t * out )

The two steps above, together: 21 line bits to a reading.

Parameters
lineThe 21 captured line bits.
edttrue when extended telemetry is on for this ESC.
outReceives the decoded reading.
Returns
true when both steps succeed.

◆ dshot_telem_from_value()

bool dshot_telem_from_value ( uint16_t value,
bool edt,
dshot_telem_t * out )

A 16-bit value to what it meant, checksum first.

The checksum is the complement of the frame's, so a correct value leaves 0x0F rather than 0 when it is folded; this returns false otherwise.

edt says whether extended telemetry was turned on and accepted for this ESC. It cannot be inferred from the bits: the type nibble that marks an extended frame is an ordinary exponent and mantissa in an eRPM one, and the two are only told apart by the guarantee an extended-telemetry ESC makes about normalising its exponent. Declaring it false reads every frame as eRPM, which is correct for every ESC that was not asked.

Parameters
valueThe 16-bit frame value.
edttrue when extended telemetry is on for this ESC.
outReceives the decoded reading.
Returns
true for a valid checksum, false otherwise.

◆ dshot_telem_value()

uint16_t dshot_telem_value ( dshot_telem_kind_t kind,
uint16_t payload )

Build a response, for the suite and for anything that fakes an ESC.

Parameters
kindKind of reading to build.
payloadPayload; the low eight bits are used for the extended kinds.
Returns
The 16-bit response value, checksum included.

◆ dshot_throttle()

uint16_t dshot_throttle ( uint16_t command,
uint16_t span )

Map a command of 0..span onto the throttle range.

Zero is DSHOT_CMD_MOTOR_STOP and everything above it lands in 48..2047, so the command range is never entered by a throttle that is merely low. The discontinuity at the bottom is the protocol's, not this function's: an ESC has no idea of "barely turning".

A command above span returns DSHOT_CMD_MOTOR_STOP, the same reading of a fault that dshot_frame() applies to a value it cannot encode. A command equal to span is the top of the range and returns 2047.

Parameters
commandCommand or travel step, 0 to span.
spanLargest command, the top of the travel.
Returns
DSHOT_CMD_MOTOR_STOP for 0 or a command above span; otherwise a value from 48 to 2047.