Register Definitions
A board’s register layout lives in config/<project>.yaml, where <project> matches the firmware
directory name under src/.
Schema
Section titled “Schema”| Key | Required | Meaning |
|---|---|---|
struct_name | yes | name of the generated C++ struct, e.g. abs_config_t |
chip | yes | part number, used for the flash geometry. |
regs | yes | ordered list of registers. Order determines addresses |
can_filtering | yes | FDCAN hardware filter and timing settings |
| Key | Required | Meaning |
|---|---|---|
name | yes | register name, uppercased for the C++ member |
type | yes | see the type table below |
fields | no | bit 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.
can_filtering
Section titled “can_filtering”| Key | Required | Meaning |
|---|---|---|
id_reg | yes | register holding this node’s CAN ID. Must name a declared register |
id_type | yes | ext is the only supported value |
delay_compensation | yes | transceiver delay compensation, needed for bit rate switching |
tdc_offset | yes | transmitter delay compensation offset |
tdc_filter | yes | transmitter delay compensation filter window |
can_subs | no | additional subscribe filters, each with can_id and id_type |
| YAML type | C++ type | Bytes | Generates header | Can be flashed |
|---|---|---|---|---|
uint8 | uint8_t | 1 | yes | yes |
uint16 | uint16_t | 2 | yes | yes |
uint32 | uint32_t | 4 | yes | yes |
float32 | float | 4 | yes | yes |
Size Limit
Section titled “Size Limit”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.
Example
Section titled “Example”config/abs.yaml:
struct_name: abs_config_tchip: STM32G431CBTxregs:- 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: float32can_filtering: id_reg: can_id id_type: ext delay_compensation: true tdc_offset: 13 tdc_filter: 1Addresses fall out of the order: can_id at 0x0, host_can_id at 0x1, sys_cfg at 0x2
(two bytes), output_scalar at 0x4.
Generated Header
Section titled “Generated Header”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.
Adding a Register
Section titled “Adding a Register”- Append it to
regsinconfig/<board>.yaml. Append, do not insert, since inserting shifts every later address and invalidates every already-configured board. - Rebuild the firmware so the header regenerates.
- Add the key to each device file under
rover/that needs a non-default value. - Reflash and reconfigure the affected boards.