|
rcbench
Servo and ESC test bench: panel, IOmcu and shared C
|
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. | |
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
| #define DSHOT_CMD_MAX 47u |
above this the value is throttle
| #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.
| #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.
| #define DSHOT_FRAME_BITS 16u |
bits in one frame
| #define DSHOT_THROTTLE_MAX 2047u |
highest throttle value
| #define DSHOT_THROTTLE_MIN 48u |
lowest throttle value
| #define DSHOT_VALUE_MAX 2047u |
eleven bits
| enum 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.
| enum 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.
| uint32_t dshot_edt_amps | ( | uint16_t | count | ) |
Amperes for a DSHOT_TELEM_CURRENT payload.
| count | Payload count, 0 to 255. |
| void dshot_edt_bind | ( | dshot_edt_t * | e, |
| uint32_t | now_ms ) |
A new binding: nothing asked, nothing heard, an ask due at once.
| e | Ask state to bind. |
| now_ms | Current time, in milliseconds. |
| uint32_t dshot_edt_celsius | ( | uint16_t | count | ) |
Degrees Celsius for a DSHOT_TELEM_TEMPERATURE payload.
| count | Payload count, 0 to 255. |
| 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.
| e | Ask state. |
| stopped | true when the throttle this frame would carry is zero. |
| now_ms | Current time, in milliseconds. |
| 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.
| e | Ask state. |
| kind | Kind of the reply that arrived. |
| 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.
| e | Ask state. |
| now_ms | Current time, in milliseconds. |
| uint32_t dshot_edt_millivolts | ( | uint16_t | count | ) |
Millivolts for a DSHOT_TELEM_VOLTAGE payload (at most 255 * 250 = 63,750).
| count | Payload count, 0 to 255. |
| 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.
| value | Frame value, 0..2047, used as given. |
| telemetry | true asks the ESC to answer on its telemetry wire. |
| inverted | true selects bidirectional DShot (complemented checksum). |
| 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.
| line | The 21 captured line bits. |
| out | Receives the 16-bit value. |
| uint32_t dshot_gcr_encode | ( | uint16_t | value | ) |
The other direction, for the suite and for anything that fakes an ESC.
| value | The 16-bit value, checksum included. |
| 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.
| erpm | Electrical rpm. |
| pole_pairs | Pole pairs of the motor. |
pole_pairs is 0. | 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.
| samples | Capture, one bit per sample, MSB of word 0 first. |
| words | Number of 32-bit words in samples. |
| oversample | Samples per response bit, 2 or more. |
| line | Receives the 21 response bits. |
| bool dshot_telem_decode | ( | uint32_t | line, |
| bool | edt, | ||
| dshot_telem_t * | out ) |
The two steps above, together: 21 line bits to a reading.
| line | The 21 captured line bits. |
| edt | true when extended telemetry is on for this ESC. |
| out | Receives the decoded reading. |
| 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.
| value | The 16-bit frame value. |
| edt | true when extended telemetry is on for this ESC. |
| out | Receives the decoded 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.
| kind | Kind of reading to build. |
| payload | Payload; the low eight bits are used for the extended kinds. |
| 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.
| command | Command or travel step, 0 to span. |
| span | Largest command, the top of the travel. |
span; otherwise a value from 48 to 2047.