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

DShot out and the reply back. More...

#include "out_dshot.h"
#include <string.h>
#include "hardware/gpio.h"
#include "hardware/pio.h"
#include "hardware/structs/io_bank0.h"
#include "dshot.pio.h"

Data Structures

struct  dshot_out_t
 One bound output: its pin, its mode and its state machines. More...

Macros

#define CAP_WORDS   8u
 Eight words is 256 samples at five to a reply bit, which is 51 bits against a reply of 21.
#define CAP_MIN_WORDS   4u
 The fewest words that can hold a whole reply: one idle sample plus 21 bits at five samples each is 106, which is four words.

Functions

static dshot_out_t * find (uint8_t pin)
 Find the slot bound to a pin.
static const pio_program_t * tx_program (bool bidir)
 Pick the transmitter program for a mode.
static bool claim_receiver (dshot_out_t *s)
 The receiver has to sit on the state machine one above its transmitter, in the same block: the transmitter raises a flag relative to its own number and the receiver waits on the flag one below its own, so the pairing is what keeps two ESCs on one block from starting each other's receivers.
static void release_pad (uint8_t pin)
 Give the pad back exactly as it was at boot: no inversion, no pull, and a plain input rather than a pin still muxed to a PIO block.
static void teardown (dshot_out_t *s)
 Remove the programs, release the state machines and the pad.
bool out_dshot_bind (uint8_t pin, uint16_t rate_kbit, bool bidirectional)
 Take pin at rate_kbit kbit/s.
void out_dshot_release (uint8_t pin)
 Give back the pin and the state machines.
void out_dshot_stop (uint8_t pin)
 Stop sending.
static void restart (PIO pio, uint sm, uint offset)
 Put a state machine back at the top of its program with nothing queued.
void out_dshot_send (uint8_t pin, uint16_t value, bool telemetry)
 Send one frame.
bool out_dshot_poll (uint8_t pin, bool edt, dshot_telem_t *out)
 Take the last reply, if one arrived and decoded.

Variables

static dshot_out_t s_out [OUT_DSHOT_MAX_PINS]
 The bound outputs, one slot per pin up to OUT_DSHOT_MAX_PINS.

Detailed Description

DShot out and the reply back.

See out_dshot.h.

SPDX-License-Identifier: MIT

Macro Definition Documentation

◆ CAP_MIN_WORDS

#define CAP_MIN_WORDS   4u

The fewest words that can hold a whole reply: one idle sample plus 21 bits at five samples each is 106, which is four words.

A poll that took less than this would take a reply that has not finished arriving. The loop turns over thousands of times in the 70us a capture takes, so the first poll after a frame always finds the queue short – draining it there would throw away every reply this bench will ever get.

◆ CAP_WORDS

#define CAP_WORDS   8u

Eight words is 256 samples at five to a reply bit, which is 51 bits against a reply of 21.

The state machine stalls when the queue is full, so the extra is idle line after the burst rather than anything that has to arrive in time.

Function Documentation

◆ claim_receiver()

bool claim_receiver ( dshot_out_t * s)
static

The receiver has to sit on the state machine one above its transmitter, in the same block: the transmitter raises a flag relative to its own number and the receiver waits on the flag one below its own, so the pairing is what keeps two ESCs on one block from starting each other's receivers.

Parameters
sBinding with its transmitter already loaded.
Returns
true when the receiver is claimed and loaded; false when that state machine is taken or the program does not fit.

◆ find()

dshot_out_t * find ( uint8_t pin)
static

Find the slot bound to a pin.

Parameters
pinGPIO number.
Returns
The slot, or NULL when pin is not bound.

◆ out_dshot_bind()

bool out_dshot_bind ( uint8_t pin,
uint16_t rate_kbit,
bool bidirectional )

Take pin at rate_kbit kbit/s.

bidirectional inverts the line and claims a second state machine, which has to be the one above the transmitter's in the same block: the two rendezvous on a flag relative to their own numbers, so an ESC on one block cannot start another's receiver. A bind that cannot get that pair is refused rather than falling back to a one-way output that would look like an ESC with no telemetry.

A pin that is already bound with the same rate and mode stays as it is and the call returns true. With a different rate or mode it returns false and the old binding keeps running; release the pin first to change it.

Parameters
pinGPIO number of the output.
rate_kbitBit rate in kbit/s; 0 is refused.
bidirectionaltrue inverts the line and claims a receiver.
Returns
true when bound, or already bound the same way; false otherwise.

◆ out_dshot_poll()

bool out_dshot_poll ( uint8_t pin,
bool edt,
dshot_telem_t * out )

Take the last reply, if one arrived and decoded.

Returns false on a bidirectional pin that has not answered, on a reply that failed its checksum, and always on a plain DShot pin. A false is not an error to report: an ESC that does not do bidirectional DShot simply never answers, and that is what it looks like.

edt says whether this ESC was asked for extended telemetry and is expected to have accepted it. It cannot be read off the wire, and getting it wrong only matters for an ESC that does not normalise its exponent: one that does either sets the mantissa's top bit, which reads as a period, or leaves the type nibble at zero, which reads as a period too.

Parameters
pinGPIO number of a bound output.
edttrue when extended telemetry was asked for and accepted.
outReceives the decoded reply when true is returned.
Returns
true for a decoded reply; false for none, a bad checksum, or a plain DShot pin.

◆ out_dshot_release()

void out_dshot_release ( uint8_t pin)

Give back the pin and the state machines.

Parameters
pinGPIO number of a bound output; other values do nothing.

◆ out_dshot_send()

void out_dshot_send ( uint8_t pin,
uint16_t value,
bool telemetry )

Send one frame.

value is 0..2047 and is used as given, so a caller can send a command as readily as a throttle; dshot_throttle() is what maps travel onto it. The frame is dropped rather than queued if the previous one has not gone yet, because a queue of stale throttle values is worse than a missed update.

On a bidirectional pin this also arms the receiver for the reply.

Parameters
pinGPIO number of a bound output; other values do nothing.
valueFrame value, 0..2047, used as given.
telemetrytrue asks the ESC to answer on its telemetry wire.

◆ out_dshot_stop()

void out_dshot_stop ( uint8_t pin)

Stop sending.

No frames means no edges, which is what the output bank means by not driving. An ESC stops on silence after its own timeout; sending explicit zeros instead would keep it armed and waiting, which is not what a disarmed bench should look like from the ESC's side.

Parameters
pinGPIO number of a bound output; other values do nothing.

◆ release_pad()

void release_pad ( uint8_t pin)
static

Give the pad back exactly as it was at boot: no inversion, no pull, and a plain input rather than a pin still muxed to a PIO block.

A pin left muxed is one the next configuration cannot have and one a scope reads as driven by something that no longer exists.

Parameters
pinGPIO number.

◆ restart()

void restart ( PIO pio,
uint sm,
uint offset )
static

Put a state machine back at the top of its program with nothing queued.

Parameters
pioPIO block.
smState machine number in the block.
offsetProgram offset in the block.

◆ teardown()

void teardown ( dshot_out_t * s)
static

Remove the programs, release the state machines and the pad.

Parameters
sBinding to tear down; cleared to zero.

◆ tx_program()

const pio_program_t * tx_program ( bool bidir)
static

Pick the transmitter program for a mode.

Parameters
bidirtrue for the bidirectional program.
Returns
The program to load.