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

The bank rendered onto pins. More...

#include "outputs_hw.h"
#include <string.h>
#include "pico/stdlib.h"
#include "dshot.h"
#include "link_pages.h"
#include "out_dshot.h"
#include "out_ppm.h"
#include "out_pwm.h"
#include "port_park.h"
#include "ppm.h"

Data Structures

struct  slot_state_t
 Render state of one slot: its DShot schedule, extended telemetry and the last PWM pulse. More...
struct  port_state_t
 The programming ports: pins of their own on the same generator, each rendered from one channel of the bank. More...
struct  telem_t
 The last readings an ESC gave, with the clock each arrived on. More...

Macros

#define DSHOT_UPDATE_HZ   1000u
 How often an ESC (electronic speed controller) is told what to do.
#define DSHOT_PERIOD_US   (1000000u / DSHOT_UPDATE_HZ)

Functions

static void ports_release_dshot (void)
 Every DShot port let go: no frame, its state machine free, held again by the next outputs_hw_ports().
static uint32_t now_ms (void)
 The millisecond clock the telemetry ages and the ask schedule run on.
static int reporting_slot (void)
 The slot whose readings the bench publishes: the lowest-numbered bound bidirectional output.
static void forget_telem (void)
 Drop everything an ESC said.
static void forget_slot_telem (unsigned slot)
 The same, for one slot: what it is bound to has changed, so what its ESC said belongs to an output that is no longer there.
void outputs_hw_init (void)
 Forget every binding.
bool outputs_hw_bound (uint8_t slot)
 Whether slot slot is configured and bound to real silicon.
static bool same (const out_slot_t *a, const out_slot_t *b)
 Tells whether two slot configurations bind the same way.
static void unbind (const out_slot_t *s)
 Releases the pin of a slot from its driver.
static bool bind (const out_slot_t *s)
 Binds a slot's pin to its driver.
void outputs_hw_apply (const outputs_t *o, const uint16_t *rate_hz)
 Make the bindings match the bank's slot table, each slot at the rate in rate_hz (OUT_MAX_SLOTS entries, from outputs_slot_rates()) rather than its own.
void outputs_hw_apply_only (const outputs_t *o, const uint16_t *rate_hz, uint8_t may_bind)
 As outputs_hw_apply(), with only the slots in may_bind bound; a slot outside it is released and left unbound.
static void record_telem (unsigned slot, const dshot_telem_t *t)
 One reply, filed under what it turned out to be.
static void service_ppm (const outputs_t *o, const out_slot_t *s, bool drive)
 Renders one PPM slot for this pass.
static void service_dshot (const outputs_t *o, const out_slot_t *s, slot_state_t *st, unsigned slot, bool drive)
 Renders one DShot slot or port for this pass.
void outputs_hw_ports (const uint8_t *pin, const uint16_t *rate_hz, uint8_t parked, uint8_t dshot, uint8_t channel0)
 The programming ports' pins: pin[n] for port n, LINK_PG_PORTS_MAX entries, a value above OUT_MAX_PIN for a port without one.
uint8_t outputs_hw_ports_bound (void)
 The ports whose function holds their pin, bit n for port n: bound to the generator or to DShot, or parked.
void outputs_hw_ports_drive (uint8_t ports)
 Which bound ports render their channel's pulse while the bank drives, bit n for port n.
void outputs_hw_watch (int ch)
 Watch output channel ch for a move capture's edge; a negative ch stops watching.
bool outputs_hw_edge (uint64_t *us)
 The edge stamped since the last call, once: true with its time in us.
static void service_pwm (slot_state_t *st, const out_slot_t *s, uint16_t pulse)
 A PWM slot's pulse for this pass.
static bool slot_driving (const outputs_t *o, const out_slot_t *s)
 Whether the bank is armed for the role of a channel of s, the slot as this layer bound it.
void outputs_hw_service (const outputs_t *o)
 Render the bank onto the pins.
bool outputs_hw_erpm (uint32_t *erpm, uint32_t *age_ms)
 The last electrical rpm a bidirectional DShot ESC (electronic speed controller) reported, and how long ago in milliseconds.
bool outputs_hw_edt (dshot_telem_kind_t kind, uint16_t *value, uint32_t *age_ms)
 The last extended-telemetry reading of kind, and how long ago in milliseconds.

Variables

static out_slot_t s_shadow [OUT_MAX_SLOTS]
static slot_state_t s_state [OUT_MAX_SLOTS]
static port_state_t s_port [LINK_PG_PORTS_MAX]
static uint8_t s_port_drive
 The ports that render their pulse (outputs_hw_ports_drive()).
static int s_watch_ch = -1
 The capture's edge: the output channel watched, -1 for none, and the frame stamped for it, until taken.
static bool s_edge_ready
static uint64_t s_edge_us
static telem_t s_telem [OUT_MAX_SLOTS]

Detailed Description

The bank rendered onto pins.

See outputs_hw.h.

SPDX-License-Identifier: MIT

Macro Definition Documentation

◆ DSHOT_UPDATE_HZ

#define DSHOT_UPDATE_HZ   1000u

How often an ESC (electronic speed controller) is told what to do.

The OUTPUTS page's rate field is a bit rate for DShot, not a frame rate, so this is the one output timing the wire does not carry. A kilohertz is twenty times the rate the panel polls at and far inside every ESC's own signal timeout, and for bidirectional DShot it is also the telemetry rate: a thousand speed readings a second against a bench that samples fifty.

Function Documentation

◆ bind()

bool bind ( const out_slot_t * s)
static

Binds a slot's pin to its driver.

Parameters
sThe slot to bind.
Returns
True when the driver took the pin.

◆ forget_slot_telem()

void forget_slot_telem ( unsigned slot)
static

The same, for one slot: what it is bound to has changed, so what its ESC said belongs to an output that is no longer there.

Parameters
slotThe slot. Ignored when out of range.

◆ forget_telem()

void forget_telem ( void )
static

Drop everything an ESC said.

At the end of a run, because the next run need not be the same ESC: one can be unplugged and another fitted between two arms, and a reading that outlived its sender would be published as the new one's for as long as its staleness window lasts – and would seed that run's peaks on the way past.

◆ outputs_hw_apply()

void outputs_hw_apply ( const outputs_t * o,
const uint16_t * rate_hz )

Make the bindings match the bank's slot table, each slot at the rate in rate_hz (OUT_MAX_SLOTS entries, from outputs_slot_rates()) rather than its own.

Call after anything that changes a slot, a role or a rate. A slot that has not changed is left alone rather than rebuilt, so reconfiguring one output does not interrupt another; a slot the silicon cannot serve is left unbound, and outputs_hw_bound() says so.

Parameters
oThe output bank.
rate_hzOUT_MAX_SLOTS entries, from outputs_slot_rates().

◆ outputs_hw_apply_only()

void outputs_hw_apply_only ( const outputs_t * o,
const uint16_t * rate_hz,
uint8_t may_bind )

As outputs_hw_apply(), with only the slots in may_bind bound; a slot outside it is released and left unbound.

A refused OUTPUTS write puts back exactly the set that was bound before it, so a slot unbound then cannot take the resource a slot bound then needs. When a slot that runs on PIO (DShot, PPM) is bound anew, every DShot programming port is let go first, so the slots take the state machines before the ports; the caller binds the ports again with outputs_hw_ports() after.

Parameters
oThe output bank.
rate_hzOUT_MAX_SLOTS entries, from outputs_slot_rates().
may_bindBit mask of the slots that may be bound. The others are released and left unbound.

◆ outputs_hw_bound()

bool outputs_hw_bound ( uint8_t slot)

Whether slot slot is configured and bound to real silicon.

Parameters
slotSlot index.
Returns
True when the slot is bound.

◆ outputs_hw_edge()

bool outputs_hw_edge ( uint64_t * us)

The edge stamped since the last call, once: true with its time in us.

Parameters
usReceives the time of the edge, in microseconds.
Returns
True when an edge was stamped since the last call.

◆ outputs_hw_edt()

bool outputs_hw_edt ( dshot_telem_kind_t kind,
uint16_t * value,
uint32_t * age_ms )

The last extended-telemetry reading of kind, and how long ago in milliseconds.

Raw: the payload byte as the ESC sent it, because the units belong to the frame type and not to this file. Returns false for a kind no reply has carried, which is every kind on an ESC that does not do extended telemetry and all of them until one has been asked and has answered.

Each kind carries its own age. They do not arrive together: speed comes back on every frame and the rest are interleaved between them.

Parameters
kindThe telemetry frame type.
valueReceives the payload byte, as the ESC sent it.
age_msReceives the time since the reading, in milliseconds.
Returns
True when a reply of kind has been received. False otherwise.

◆ outputs_hw_erpm()

bool outputs_hw_erpm ( uint32_t * erpm,
uint32_t * age_ms )

The last electrical rpm a bidirectional DShot ESC (electronic speed controller) reported, and how long ago in milliseconds.

Electrical rather than mechanical: an ESC reports periods and has no idea what it is bolted to, and the pole count arrives separately. Returns false when no ESC has ever answered.

Parameters
erpmReceives the electrical rpm.
age_msReceives the time since the reading, in milliseconds.
Returns
True when an ESC has answered. False when none has ever answered.

◆ outputs_hw_init()

void outputs_hw_init ( void )

Forget every binding.

Call once, before the first apply.

◆ outputs_hw_ports()

void outputs_hw_ports ( const uint8_t * pin,
const uint16_t * rate_hz,
uint8_t parked,
uint8_t dshot,
uint8_t channel0 )

The programming ports' pins: pin[n] for port n, LINK_PG_PORTS_MAX entries, a value above OUT_MAX_PIN for a port without one.

A port in parked (bit n, FUNC NONE) holds its pin as an input with its pull-down and renders nothing. A port in dshot (bit n, FUNC DSHOT150 to DSHOT600) is bound to a DShot state machine at rate_hz[n] kbit/s and rendered as a plain DShot slot is: a frame every 1ms while the bank is armed for its channel's role, the throttle, and none while it is not. Any other port's pin is bound to the PWM generator at rate_hz[n] frames a second (SERVO PWM). A port whose pin, rate or function moved is released and held again; one that did not is left running. A port is no slot: it is rendered from bank channel channel0 + n. A pin the chip does not have, or a DShot pin no state machine is left for, is not held.

Parameters
pinPin of each port, one entry per port.
rate_hzRate of each port: kbit/s for DShot, frames per second for PWM.
parkedBit n set when port n is parked.
dshotBit n set when port n runs DShot.
channel0Bank channel of port 0.

◆ outputs_hw_ports_bound()

uint8_t outputs_hw_ports_bound ( void )

The ports whose function holds their pin, bit n for port n: bound to the generator or to DShot, or parked.

Returns
Bit n set for port n.

◆ outputs_hw_ports_drive()

void outputs_hw_ports_drive ( uint8_t ports)

Which bound ports render their channel's pulse while the bank drives, bit n for port n.

A port left out is written a pulse of 0, as a disarmed bank writes it: the pulse in progress completes and the pin rests low. None until told.

Parameters
portsBit n set for port n.

◆ outputs_hw_service()

void outputs_hw_service ( const outputs_t * o)

Render the bank onto the pins.

Call every pass.

Nothing edges unless outputs_driving() is true. DShot has a send rate of its own, because its frame rate is not the bank's business and the OUTPUTS page's rate field is a bit rate for it rather than a frame rate.

Parameters
oThe output bank.

◆ outputs_hw_watch()

void outputs_hw_watch ( int ch)

Watch output channel ch for a move capture's edge; a negative ch stops watching.

The first later pass that renders a pulse other than the one before it on ch's PWM slot or programming port writes it stamped (out_pwm_write_stamped()) and ends the watch: the start of the frame that first carries the new pulse, on time_us_64(). A pulse of 0, the bank stopping, is not an edge. A slot that is not PWM never stamps.

Parameters
chOutput channel to watch, or a negative value to stop watching.

◆ record_telem()

void record_telem ( unsigned slot,
const dshot_telem_t * t )
static

One reply, filed under what it turned out to be.

Each kind keeps its own arrival clock because they do not arrive at one rate: speed comes back on every frame and the extended kinds are interleaved between them, a few a second at most.

Parameters
slotThe slot the reply came from. Ignored when out of range.
tThe reply.

◆ reporting_slot()

int reporting_slot ( void )
static

The slot whose readings the bench publishes: the lowest-numbered bound bidirectional output.

Defined rather than "whichever answered last", so the numbers on the screen belong to one motor and stay with it. A bench running two ESCs reports the first; the page carries one set of readings and choosing between them here is the only place that choice can be made once.

Returns
The slot index, or -1 when no bidirectional output is bound.

◆ same()

bool same ( const out_slot_t * a,
const out_slot_t * b )
static

Tells whether two slot configurations bind the same way.

Parameters
aOne slot.
bThe other slot.
Returns
True when driver, channels, pin and rate all match.

◆ service_dshot()

void service_dshot ( const outputs_t * o,
const out_slot_t * s,
slot_state_t * st,
unsigned slot,
bool drive )
static

Renders one DShot slot or port for this pass.

Parameters
oThe output bank.
sThe slot or port, bound as DShot.
stIts render state.
slotIts slot index, or OUT_MAX_SLOTS for a programming port.
driveTrue while the bank is armed for its channel.

◆ service_ppm()

void service_ppm ( const outputs_t * o,
const out_slot_t * s,
bool drive )
static

Renders one PPM slot for this pass.

Parameters
oThe output bank.
sThe slot, bound as PPM.
driveTrue to write the channels' pulses, false to stop the pin.

◆ service_pwm()

void service_pwm ( slot_state_t * st,
const out_slot_t * s,
uint16_t pulse )
static

A PWM slot's pulse for this pass.

On the watched channel, the first pulse other than the last one rendered – a changed command, not a disarm – is written stamped: the start of the frame that first carries it is the capture's edge. Once: the watch ends with it, stamped or not.

Parameters
stThe render state of the slot or port.
sThe slot or port, bound as PWM.
pulsePulse width in microseconds. 0 stops the pin edging.

◆ slot_driving()

bool slot_driving ( const outputs_t * o,
const out_slot_t * s )
static

Whether the bank is armed for the role of a channel of s, the slot as this layer bound it.

The shadow rather than the bank's slot: the shadow is what the pin is doing.

Parameters
oThe output bank.
sThe slot, from s_shadow.
Returns
True when any of the slot's channels is driving.

◆ unbind()

void unbind ( const out_slot_t * s)
static

Releases the pin of a slot from its driver.

Parameters
sThe slot whose pin is released.