Skip to content

Register Definitions

A board’s register layout lives in config/<project>.yaml, where <project> matches the firmware directory name under src/.

KeyRequiredMeaning
struct_nameyesname of the generated C++ struct, e.g. abs_config_t
chipyespart number, used for the flash geometry.
regsyesordered list of registers. Order determines addresses
can_filteringyesFDCAN hardware filter and timing settings
KeyRequiredMeaning
nameyesregister name, uppercased for the C++ member
typeyessee the type table below
fieldsnobit fields within the register

Each entry under fields has a name and a pos (bit position, counting from the LSB). A register with fields is addressed only through those fields; one without gets a whole-register accessor instead.

KeyRequiredMeaning
id_regyesregister holding this node’s CAN ID. Must name a declared register
id_typeyesext is the only supported value
delay_compensationyestransceiver delay compensation, needed for bit rate switching
tdc_offsetyestransmitter delay compensation offset
tdc_filteryestransmitter delay compensation filter window
can_subsnoadditional subscribe filters, each with can_id and id_type
YAML typeC++ typeBytesGenerates headerCan be flashed
uint8uint8_t1yesyes
uint16uint16_t2yesyes
uint32uint32_t4yesyes
float32float4yesyes

The whole config must fit in one flash page. On the STM32G431CB that is 2048 bytes, and the generator raises an error if the registers exceed it.

config/abs.yaml:

struct_name: abs_config_t
chip: STM32G431CBTx
regs:
- name: can_id
type: uint8
- name: host_can_id
type: uint8
- name: sys_cfg
type: uint16
fields:
- name: continuous_mode
pos: 0
- name: bounded_mode
pos: 1
- name: invert
pos: 2
- name: output_scalar
type: float32
can_filtering:
id_reg: can_id
id_type: ext
delay_compensation: true
tdc_offset: 13
tdc_filter: 1

Addresses fall out of the order: can_id at 0x0, host_can_id at 0x1, sys_cfg at 0x2 (two bytes), output_scalar at 0x4.

config_gen.py renders this into src/abs/Inc/abs_config.hpp, which defines:

struct abs_config_t {
reg_t<uint8_t> CAN_ID{0x0};
reg_t<uint8_t> HOST_CAN_ID{0x1};
reg_t<uint16_t> SYS_CFG{0x2};
reg_t<float> OUTPUT_SCALAR{0x4};
using continuous_mode = field_t<&abs_config_t::SYS_CFG, 0>;
using bounded_mode = field_t<&abs_config_t::SYS_CFG, 1>;
using invert = field_t<&abs_config_t::SYS_CFG, 2>;
using output_scalar = field_t<&abs_config_t::OUTPUT_SCALAR>;
template<typename F> auto get() const;
template<typename F> void set(auto value);
auto set_raw(uint8_t address, uint32_t raw) -> bool;
auto get_raw(uint8_t address, uint32_t& raw) const -> bool;
};

Firmware reads and writes it by field:

if (config.get<abs_config_t::invert>()) {
position = -position;
}

set_raw and get_raw dispatch by address and are what the CAN handler calls. Values persist in the last flash page of the chip: a write reads the page into RAM, patches the register, erases the page and reprograms it.

A compile-time validator checks that no two registers overlap, so a malformed layout fails the build rather than corrupting flash.

  1. Append it to regs in config/<board>.yaml. Append, do not insert, since inserting shifts every later address and invalidates every already-configured board.
  2. Rebuild the firmware so the header regenerates.
  3. Add the key to each device file under rover/ that needs a non-default value.
  4. Reflash and reconfigure the affected boards.