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

The inbound half of DShot: the GCR (group-coded recording) burst an ESC (electronic speed controller) sends back on the signal line. More...

#include "dshot.h"
#include <stddef.h>

Macros

#define GCR_DATA_MASK   0x000FFFFFu
 The line is differential: each bit is the one before it exclusive-ored with the data, so the data is recovered by exclusive-oring the line with itself shifted down one.
#define EDT_TYPE_TEMPERATURE   0x02u
 Extended telemetry types.
#define EDT_TYPE_VOLTAGE   0x04u
#define EDT_TYPE_CURRENT   0x06u
#define EDT_TYPE_DEBUG1   0x08u
#define EDT_TYPE_DEBUG2   0x0Au
#define EDT_TYPE_STRESS   0x0Cu
#define EDT_TYPE_STATUS   0x0Eu
#define ERPM_STOPPED   0x0FFFu
 The payload an ESC sends for a motor that is not turning.
#define US_PER_MINUTE   60000000u
 One electrical revolution in microseconds, to electrical rpm.

Functions

static uint8_t nibble_of (uint8_t quintet)
 0xFF for the sixteen codes that are not in the table.
static uint16_t fold (uint16_t v)
 The checksum, folded over the twelve bits that carry the reading.
uint32_t dshot_gcr_encode (uint16_t value)
 The other direction, for the suite and for anything that fakes an ESC.
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.
static void erpm_from_payload (uint16_t payload, dshot_telem_t *out)
 Fill an eRPM reading from a twelve-bit payload.
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.
static bool sample_at (const uint32_t *samples, size_t i)
 Read one sample of the capture.
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.
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.

Variables

static const uint8_t k_quintet [16]
 Sixteen five-bit codes, one per nibble.

Detailed Description

The inbound half of DShot: the GCR (group-coded recording) burst an ESC (electronic speed controller) sends back on the signal line.

See dshot.h for the model.

SPDX-License-Identifier: MIT

Macro Definition Documentation

◆ EDT_TYPE_TEMPERATURE

#define EDT_TYPE_TEMPERATURE   0x02u

Extended telemetry types.

The type is the top nibble of the twelve-bit payload and is always even, because an eRPM payload always has bit 8 set: an ESC with extended telemetry enabled normalises its exponent so that it does. An ESC without it sends nothing but eRPM, and then bit 8 means what it says – which is why the caller has to declare whether the mode was turned on rather than this file guessing from the bits.

◆ GCR_DATA_MASK

#define GCR_DATA_MASK   0x000FFFFFu

The line is differential: each bit is the one before it exclusive-ored with the data, so the data is recovered by exclusive-oring the line with itself shifted down one.

Bit 20 is the seed and is not data; it is sent as zero, because the line idles high in bidirectional DShot and the response has to begin with a falling edge for a receiver to find it at all.

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_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_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_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.

◆ erpm_from_payload()

void erpm_from_payload ( uint16_t payload,
dshot_telem_t * out )
static

Fill an eRPM reading from a twelve-bit payload.

The payload is a nine-bit mantissa in bits 8..0 and a three-bit exponent in bits 11..9.

Parameters
payloadThe twelve-bit payload.
outReceives kind, period_us and erpm.

◆ fold()

uint16_t fold ( uint16_t v)
static

The checksum, folded over the twelve bits that carry the reading.

The response inverts it, which is what stops a decoder locked onto the wrong bit boundary from accepting a shifted copy of a valid frame.

Parameters
vThe payload, the twelve bits that carry the reading.
Returns
The folded checksum, 0 to 15.

◆ nibble_of()

uint8_t nibble_of ( uint8_t quintet)
static

0xFF for the sixteen codes that are not in the table.

Parameters
quintetFive-bit code, 0 to 31.
Returns
The nibble, 0 to 15, or 0xFF for the sixteen codes that are not in the table.

◆ sample_at()

bool sample_at ( const uint32_t * samples,
size_t i )
static

Read one sample of the capture.

Parameters
samplesCapture words, one bit per sample, MSB of word 0 first.
iSample index, 0 to 32 * words - 1.
Returns
true when sample i is high.

Variable Documentation

◆ k_quintet

const uint8_t k_quintet[16]
static
Initial value:
= {
0x19u, 0x1Bu, 0x12u, 0x13u, 0x1Du, 0x15u, 0x16u, 0x17u,
0x1Au, 0x09u, 0x0Au, 0x0Bu, 0x1Eu, 0x0Du, 0x0Eu, 0x0Fu,
}

Sixteen five-bit codes, one per nibble.

The point of the code is that no code word has two adjacent zeros and none has more than two adjacent ones at a join, so the line changes level often enough for a receiver to keep its phase without a separate clock. The table is the protocol's; it is not derived from anything.