Skip to content

Build, test and check the libraries on their own - #2

Open
GabrielCosme wants to merge 606 commits into
mainfrom
refactor/restructure
Open

GabrielCosme wants to merge 606 commits into
mainfrom
refactor/restructure

Conversation

@GabrielCosme

Copy link
Copy Markdown
Member

The Micras libraries as their own repository: micras_core, micras_nav, micras_comm, micras_hal and micras_proxy, with the host backend of the HAL and the SPI chip models, so other robots can build on them. The files keep their history from MicrasFirmware and micras-simulation.

Consuming it

A consumer adds the repository as a submodule and defines its board target before add_subdirectory:

  • On the robot, the board target is CubeMX's stm32cubemx. The HAL reads the family, H7 or G4, from its compile definitions.
  • On a host, the board target is a small fake Cube layer. The consumer sets MICRAS_LIB_HAL_BACKEND=host and MICRAS_LIB_CUBE_TARGET.

Options (MICRAS_LIB_NAV, _COMM, _HAL, _PROXY) leave out what a consumer does not need: a consumer of the navigation alone compiles core and nav and downloads nothing. A second add_subdirectory of the repository is a no-op. The README gives the full contract.

The HAL

  • One header set, three implementations, selected by CMake: stm32 for the H7, stm32 for the G4, and host.
  • Chip-specific code lives in stm32/<family>/, behind a few functions in micras::hal::family: ADC calibration, the flash driver and its geometry, caches, the watchdog, the timer clock and the cycle counter. The shared code names no chip, and scripts/check_no_chip_macros.sh enforces this in CI.
  • The G4 flash driver writes double words into bank 2 of a 512 KB part in dual-bank mode. It refuses any other layout rather than risk erasing code.
  • The host backend routes SPI transactions to chip models by bus and chip select, completes DMA at the time the bus clock implies, computes the FMAC's IIR in software, and can cut power mid-write to test the storage.

References

reference/h7 (STM32H725) and reference/g4 (STM32G474) are the smallest CubeMX projects that use every peripheral the HAL needs, with nothing of Micras in them. Each builds and links a bench program. The bench starts only peripherals that drive no pin, so it can be flashed on any board with the part. It checks:

  • storage save and reload across a reset;
  • a watchdog reset and its reported cause;
  • the timer clock against the cycle counter;
  • the CRC check value;
  • the FMAC filter against a software copy;
  • the ADC's internal reference against its factory calibration.

Tests

The tests use doctest over a fake board that mirrors the references, and run under ASan and UBSan. There are 123 cases across core, comm, hal, proxy, the chip models and a first navigation suite. The navigation suite plans the ten contest mazes against recorded route times, builds the turn tables at runtime and runs the route compiler.

Standards

This repository holds the canonical copy of the files the three Micras repositories share: .clang-format, .clang-tidy, tests/.clang-tidy, the warnings module, the clang-tidy script and the host stage of the Dockerfile. The firmware's CI checks that its copies and the simulator's are identical. The standards are GCC 15, clang-format and clang-tidy 22, CMake 3.28 to 4.4, and C++23.

CI runs on every push: the host job, and one stm32 job per family.

Before merging

  • Merge together: this pull request goes in with Take the libraries and the simulator as submodules MicrasFirmware#59, which pins this branch.
  • Bench on the boards: run micras_lib_bench_h7 on Micras v1 and micras_lib_bench_g4 on the old board or any STM32G474xE board. Every *_passed global and bench_done should read true. The G4 bench is the first run of the page-based flash driver.

GabrielCosme and others added 30 commits April 27, 2025 17:15
Co-authored-by: dardengo <dardengo.cj@gmail.com>
Co-authored-by: Pedro de Santi <62271285+PedroDeSanti@users.noreply.github.com>
Co-authored-by: Pedro de Santi <62271285+PedroDeSanti@users.noreply.github.com>
WallSensorsIndex names the wall sensors of one robot, so the robot that has them defines it. The
communication README keeps the protocol; the HM-19 module, its wiring and its AT setup belong to the
firmware of the board that carries them.
reference/h7 is the smallest CubeMX project that exercises every peripheral the HAL and the proxies
use, derived from Micras v1's with nothing of Micras left: the internal oscillator through the PLL at
400 MHz, no main() and no callback registration. It glues the generated tree in as a consumer does,
adds micras-lib with warnings as errors, and links micras_lib_bench_h7, which proves that the vendor
callbacks micras-lib defines win over the weak ones and checks, on any board with the part, a storage
restored across a watchdog reset, the reset cause, the timer clock against the cycle counter, the CRC
unit, the FMAC and the internal reference over DMA. Its lint target covers the stm32 backend against
the ARM headers. The Dockerfile generates its Cube files and adds the ARM toolchain to the host stage,
the reference-h7 preset builds it from the root, and the tests' host board mirrors its pins and clock
functions.
The README is the consumers' guide: how a project adds micras-lib, its options, the contracts of the
stm32 and host backends, the reference project and its bench, the design of the host backend with its
SPI device slot and chip models, and how to build, test, lint and document it. The Doxyfile covers
the five libraries and both READMEs, and the docs target passes it the version, the theme and the
output directory through the environment.
The host job builds the host preset with warnings as errors in the shared host image, runs the tests
under the sanitizers, checks the formatting, lints and builds the docs, with the fetched dependencies
cached. The stm32-h7 job generates the reference project's Cube files once per .ioc and Dockerfile,
builds the reference and links its bench, and lints the stm32 backend. Both run on a push to any
branch and on pull requests, so the restructure branch is checked before anything is merged.
STM32CubeMX writes the reference projects' .ioc files with the line endings of the platform it runs
on, so without normalization a regeneration on Windows would show as a rewrite of the whole file.
FetchContent keeps the record of what it fetched under CMakeFiles/fc-stamp, and without it a
restored _deps is cloned again.
The frequency fields of reference.ioc are the values STM32CubeMX 6.18.1 computes from the clock tree
the file configures (HSI / 4 x 50 / 2), as it saves them. They are cached values only: the generated
project is the same.
The shared stm32 sources name no chip any more. The ADC calibration, the cache, the watchdog reset
flag, debug freeze and registers, the core frequency boost, the timer clock and the cycle counter are
the functions of micras/hal/family.hpp, and the flash driver and its geometry are in stm32/h7, which
CMake compiles for an STM32H7 board target, with the H7 behavior unchanged. The host backend
implements the same functions and reads its flash geometry from the board's macros, as before.
An STM32G4 board target, such as the STM32G474 of the old Micras board, selects stm32/g4: ADC
calibration with the single-ended argument only, no cache to enable, IWDG, no boost option byte,
RCC_HCLK_DIV1 as the undivided APB prescaler, a cycle counter without a lock, and a page-based flash
driver that programs double words into bank 2 of a 512 KB part in dual bank mode and refuses any
other layout rather than erase what may be code.
scripts/check_no_chip_macros.sh fails when a conditional directive in the code every STM32 family
shares (the HAL headers, stm32/src, the proxies, core, nav and comm) names a chip, a core or a
peripheral only some parts have, so chip-specific code stays in the family folders CMake selects.
reference/common holds what every family's reference project runs and builds: the bench checks, the
toolchain and flag setup read before project(), and the generated tree, micras-lib, bench and lint
glue read after it. A family's bench.cpp only names its UART and converts the internal reference with
its low-layer ADC header, and the timer check compares against the clock family::timer_clock reports,
the one Pwm uses.
reference/g4 is the G4 counterpart of reference/h7: the same peripherals on the STM32G474RETx, the
part of the old Micras board, at 170 MHz from the internal oscillator through the PLL, with USART3 as
its UART. The Dockerfile generates its Cube files with the G4 image of STM32CubeMX, the reference-g4
preset builds it, and the CI builds, links and lints both references in one job per family, each
with its generated tree cached on its .ioc and the Dockerfile.
The README names what each family needs from a consumer, above all the flash region Storage takes
on each, where the family code lives and how the chip macro check keeps it there, and both
reference projects with their bench.
The storage region's address follows storage_first_sector and sector_size, as on the G4, so the family
header is the single source of the region the driver reads, writes and erases.
The G4 HAL flushes the flash caches at the end of every programming operation.
The rounding to double words and the check of the part's layout are facts of the family's flash, so
the family header documents them; the driver's remaining file-local constants live in an anonymous
namespace, which the documentation does not extract, as the host backend's do.
The family source carries no comments, so its file-local constant lives in an anonymous namespace,
which the documentation does not extract, as the host backend's do.
…ard's pins

Every declaration carries Doxygen: the SPI device's modes, the fields of the host SPI registers, init
structure and handle, the FMAC handle states and the GPIO groups of the test board had none.
The HAL only returns the peripheral to ready from the transmission complete interrupt of the UART, which the board leaves disabled so that a reception error never aborts the circular transfer. A transfer is over once its DMA stream is, and the next one closes the state the HAL left open.
Their transfers are observed by polling, and the interrupt callbacks still work on a board that keeps them. The ADCs restart after an overrun or a stopped stream, a snapshot is never taken once an overrun misaligned the buffer, and the UART closes a finished DMA transfer before starting the next.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants