sat_frame.h
Generated from the header
This page is rodeo-sdk/common/sat_frame.h — its comments and declarations.
To change it, change the header.
The contract is hardware/rodeo-motherboard/docs/RODEO_SATELLITE_CHAIN_CONTRACT.md; section numbers below (§2.1) point into it. This header is the contract in code, and BOTH firmwares include it: the Seed’s chain driver in this SDK, and the satellite firmware in rodeo-satellite/ (by path). A byte layout lives here once, so the two ends cannot drift, and common/tests/sat_frame_gtest.cpp exercises it from both sides, down to a simulated chain of satellites.
Plain C99 with static inline functions, because the satellites are built with ch32v003fun, a C toolchain, on a part with 16 KB of flash and 2 KB of RAM. No allocation, no tables, nothing that isn’t used costs anything. C++ includes it unchanged. Pure logic: no hardware, no clock, no libDaisy.
SAT_BAUD
#define SAT_BAUD 1000000u // 8N1, so a byte takes 10 usSAT_IDLE_GAP_BYTES
#define SAT_IDLE_GAP_BYTES 2u // a gap this long starts (or aborts) a frameSAT_SATELLITE_BOOT_MS
#define SAT_SATELLITE_BOOT_MS 20u // a satellite passes frames this soon after powerSAT_FIRST_DISCOVER_MS
#define SAT_FIRST_DISCOVER_MS 50u // the Seed waits this long after powerSAT_SCAN_PERIOD_MS
#define SAT_SCAN_PERIOD_MS 4u // default: 250 scans a secondSAT_RING_LOST_AFTER
#define SAT_RING_LOST_AFTER 3u // missed or wrong SCAN replies in a rowSAT_SOF
#define SAT_SOF 0xA5u // protocol v1SAT_HEADER_LEN
#define SAT_HEADER_LEN 4uSAT_SLOT_LEN
#define SAT_SLOT_LEN 4uSAT_MAX_SLOTS
#define SAT_MAX_SLOTS 64uSAT_FRAME_LEN
#define SAT_FRAME_LEN(n) (SAT_HEADER_LEN + SAT_SLOT_LEN * (n))SAT_MAX_FRAME_LEN
#define SAT_MAX_FRAME_LEN SAT_FRAME_LEN(SAT_MAX_SLOTS) // 260 bytesSAT_DESCRIPTOR
#define SAT_DESCRIPTOR 0x80uThe one slot kind DISCOVER carries.
SAT_RESET_GUARD
#define SAT_RESET_GUARD 0x5AuSAT_NUM_KINDS
#define SAT_NUM_KINDS 16uSAT_KIND_TOLERANCE_X100
#define SAT_KIND_TOLERANCE_X100 30 // 0.30 of a kind stepHow far a reading may sit from a kind number and still count as that kind. 1 % resistors land within 0.1 (§1.6); a reading further out than this is a fault, not a guess.
SAT_H_SOF … SAT_H_HOP
Header byte offsets.
SAT_H_SOF = 0SAT_H_TYPE = 1SAT_H_N = 2SAT_H_HOP = 3
SAT_SCAN … SAT_COMMAND
Frame types (header byte 1).
SAT_SCAN = 0x01SAT_DISCOVER = 0x02SAT_COMMAND = 0x03
sat_slot
struct sat_slotOne slot: a kind, two data bytes, and a CRC over the first three.
Members
uint8_t kinduint8_t d0uint8_t d1uint8_t crc
SAT_IN_EMPTY … SAT_IN_FAULT
Input slots, satellite to Seed. They report state, not events.
SAT_IN_EMPTY = 0x00SAT_IN_POT = 0x01— position 0..65535, little-endianSAT_IN_BUTTON = 0x02— D0 bit 0 pressed; D1 press counter (wraps at 256)SAT_IN_ENCODER = 0x03— D0 detent counter; D1 bit 0 pressed, bits 4-7 press counterSAT_IN_SWITCH = 0x04— D0 position indexSAT_IN_FAULT = 0x7F— D0 fault code, D1 detail
SAT_OUT_NONE … SAT_OUT_RGB
Output slots, Seed to satellite.
SAT_OUT_NONE = 0x00— keep the current output stateSAT_OUT_LED = 0x10— D0 brightness 0-255; D1 a SAT_LED_ modeSAT_OUT_RGB = 0x11— D0-D1 an RGB565 colour, little-endian (sat_rgb565())
SAT_LED_STEADY … SAT_LED_FAST_BLINK
SAT_LED_STEADY = 0SAT_LED_SLOW_BLINK = 1— 1 HzSAT_LED_FAST_BLINK = 2— 4 Hz
SAT_CMD_CLOSE_RING_HERE … SAT_CMD_IDENTIFY
Command slots, in a COMMAND frame. Position 0 addresses every satellite.
SAT_CMD_CLOSE_RING_HERE = 0x40— D0 target positionSAT_CMD_RING_AUTO = 0x41— D0 target position, or 0SAT_CMD_RESET = 0x42— D0 target or 0; D1 must be SAT_RESET_GUARDSAT_CMD_IDENTIFY = 0x43— D0 target position; D1 seconds to blink
SAT_FAULT_ID_BETWEEN_BANDS … SAT_FAULT_SENSOR
Fault codes, D0 of a FAULT slot (§7).
SAT_FAULT_ID_BETWEEN_BANDS = 0x01— D1: the reading as kind x 10SAT_FAULT_KIND_UNSUPPORTED = 0x02— D1: the kindSAT_FAULT_SENSOR = 0x03
SAT_KIND_TEST_FIXTURE … SAT_KIND_NO_ID
SAT_KIND_TEST_FIXTURE = 0SAT_KIND_POT = 1SAT_KIND_BUTTON = 2SAT_KIND_ENCODER = 3SAT_KIND_SWITCH = 4SAT_KIND_POT_BANK_4 = 5SAT_KIND_ENC_BANK_4 = 6SAT_KIND_BUTTON_BANK_4 = 7— four buttons, an RGB LED beside eachSAT_KIND_CUSTOM = 14— a board on its own firmware: slots as DISCOVER saysSAT_KIND_NO_ID = 15— no ID resistor fitted: sends EMPTY
sat_kind_slots
static inline uint8_t sat_kind_slots(uint8_t kind);Slots a board kind owns: 4 for the banks, 1 for everything else. A custom board (SAT_KIND_CUSTOM) declares its own in its DISCOVER descriptor, which is what the Seed goes by for every kind.
sat_kind_input
static inline uint8_t sat_kind_input(uint8_t kind);The input slot kind a board kind reports.
sat_kind_supported
static inline bool sat_kind_supported(uint8_t kind);Whether the stock satellite firmware runs this board kind. Not SAT_KIND_CUSTOM: those boards bring their own firmware.
sat_kind_from_adc
static inline int sat_kind_from_adc(uint16_t reading, uint16_t full_scale, uint8_t* tenths);Board kind from the ID divider’s ADC reading: kind = round(15 x reading / full scale). Returns the kind, or -1 if the reading sits between bands; then *tenths (if given) holds the reading as kind x 10, the FAULT slot’s detail.
sat_crc8
static inline uint8_t sat_crc8(const uint8_t* p, size_t n);CRC-8/SMBUS: polynomial 0x07, init 0, no reflection, no final XOR.
sat_slot_make
static inline sat_slot sat_slot_make(uint8_t kind, uint8_t d0, uint8_t d1);sat_slot_ok
static inline bool sat_slot_ok(sat_slot s);sat_slot_read
static inline sat_slot sat_slot_read(const uint8_t* p);sat_slot_write
static inline void sat_slot_write(uint8_t* p, sat_slot s);sat_pot
static inline sat_slot sat_pot(uint16_t position);sat_pot_position
static inline uint16_t sat_pot_position(sat_slot s);sat_button
static inline sat_slot sat_button(bool pressed, uint8_t presses);sat_encoder
static inline sat_slot sat_encoder(uint8_t detents, bool pressed, uint8_t presses);sat_switch
static inline sat_slot sat_switch(uint8_t position);sat_fault
static inline sat_slot sat_fault(uint8_t code, uint8_t detail);sat_delta8
static inline int sat_delta8(uint8_t now, uint8_t before);How far an 8-bit counter moved, from the last good value to this one: -128..127, so a counter that wrapped still gives the right direction.
sat_delta4
static inline int sat_delta4(uint8_t now, uint8_t before);The same for the encoder’s 4-bit press counter: 0..15 presses since.
sat_led
static inline sat_slot sat_led(uint8_t brightness, uint8_t mode);sat_none
static inline sat_slot sat_none(void);sat_rgb565
static inline uint16_t sat_rgb565(uint8_t r, uint8_t g, uint8_t b);8-bit red, green and blue packed as RGB565: 5 bits red, 6 green, 5 blue.
sat_rgb
static inline sat_slot sat_rgb(uint16_t rgb565);An RGB output slot: one LED’s colour (§3.2).
sat_rgb_unpack
static inline void sat_rgb_unpack(sat_slot s, uint8_t* r, uint8_t* g, uint8_t* b);An RGB slot’s colour back to 8 bits a channel. Each channel’s top bits fill its low ones, so full scale stays 255 and 0 stays 0.
sat_descriptor
struct sat_descriptorMembers
uint8_t kind— board kind, 0..15uint8_t slots— slots owned, 1..4bool last— PRESENT high: nothing downstreamuint8_t fw_major— 0..15uint8_t fw_minor— 0..15
sat_descriptor_slot
static inline sat_slot sat_descriptor_slot(sat_descriptor d);sat_descriptor_read
static inline sat_descriptor sat_descriptor_read(sat_slot s);sat_command
static inline sat_slot sat_command(uint8_t command, uint8_t target, uint8_t arg);sat_command_for
static inline bool sat_command_for(sat_slot s, uint8_t position);Whether the satellite at position should act on this command slot.
sat_header_write
static inline void sat_header_write(uint8_t* p, uint8_t type, uint8_t n, uint8_t hop);sat_header_ok
static inline bool sat_header_ok(const uint8_t* p);A header a receiver accepts: v1 SOF, a known type, at most 64 slots.
sat_header_onward
static inline void sat_header_onward(const uint8_t* in, uint8_t slots_owned, uint8_t* out);The header a satellite sends on, given the one it received and the slots it owns. SCAN keeps N (it consumes s slots and appends s), DISCOVER adds the descriptor, COMMAND forwards everything; every type adds 1 to HOP. A SCAN with fewer than s slots is forwarded unchanged apart from HOP.
sat_satellite_pass
static inline size_t sat_satellite_pass(const uint8_t* in, size_t in_len, sat_descriptor me, const sat_slot* inputs, sat_slot* taken, bool* took, uint8_t* position, uint8_t* out);What one satellite does with one whole frame: the reference model of §2.3.
The satellite firmware streams a frame through with a 4-byte lag rather than buffering it, but what it sends must equal this, byte for byte. Host tests chain calls to this to simulate a whole ring.
in/in_len the frame received; out gets the frame to send on, of the
returned length (0: the frame is dropped)
me this satellite’s descriptor (its kind and slots owned)
inputs its current input slots, me.slots of them
taken if not NULL, receives the output slots consumed from a SCAN
(
me.slotsof them); *took says whether there were any
position if not NULL, receives this satellite’s position, HOP_in + 1
sat_build_discover
static inline size_t sat_build_discover(uint8_t* out);Build a DISCOVER frame; returns its length.
sat_build_scan
static inline size_t sat_build_scan(uint8_t* out, const sat_slot* outputs, uint8_t n);Build a SCAN frame from n output slots in chain order; returns its length.
sat_build_command
static inline size_t sat_build_command(uint8_t* out, const sat_slot* commands, uint8_t n);Build a COMMAND frame from n command slots; returns its length.
sat_result
enum sat_resultWhy a reply was refused.
SAT_OK = 0SAT_ERR_SHORT— fewer bytes than the header says, or no headerSAT_ERR_HEADER— bad SOF, type or N, or not the type sentSAT_ERR_HOP— HOP isn’t what the chain says it should beSAT_ERR_CRC— a slot failed its CRCSAT_ERR_NOT_DESCRIPTOR— a DISCOVER slot that isn’t a descriptorSAT_ERR_LAST— the “last” bit isn’t on the last satellite, and only thereSAT_ERR_VERSION— a satellite’s major version isn’t the one supportedSAT_ERR_TOO_MANY— more satellites than the caller has room for
sat_read_discover
static inline sat_result sat_read_discover(const uint8_t* in, size_t len, uint8_t fw_major, sat_descriptor* out, uint8_t max, uint8_t* count);Read a DISCOVER reply into descriptors, in chain order (§4).
Checks that HOP equals the number of descriptors, every CRC is good, exactly
the last satellite has the “last” bit, and every satellite’s major firmware
version is fw_major. *count gets the number of satellites. An empty chain
(nothing plugged in, the ring closed at the motherboard) returns SAT_OK with
*count 0.
sat_check_scan
static inline sat_result sat_check_scan(const uint8_t* in, size_t len, uint8_t n, uint8_t satellites);Check a SCAN reply’s header against the chain the Seed expects: the same N it sent, and HOP equal to the number of satellites. Slot CRCs are checked one by one by the caller, so one bad slot doesn’t cost the whole frame.
sat_lag_bytes
static inline uint8_t sat_lag_bytes(uint8_t type, uint8_t n, uint8_t slots_owned);How many byte times a satellite holds a frame back (§2.3): the header and, in a SCAN, the slots it consumes, plus the byte in flight. It can’t send the first slot it forwards until that slot has arrived, so a one-slot satellite lags 5 bytes (50 us) and a four-slot bank 17 (170 us).
sat_ring_time_us
static inline uint32_t sat_ring_time_us(uint8_t n, uint8_t satellites);Expected ring time in microseconds (§6.2): the frame’s bytes at 10 us, plus each satellite’s lag. For a SCAN, n is the total of the slots the satellites own, so the lags add up to (satellites + 4n) byte times. The timeout is twice this, and at least 1 ms.
sat_timeout_us
static inline uint32_t sat_timeout_us(uint8_t n, uint8_t satellites);