Architecture¶
How the Vortex V1 HAL is put together. The short version: your code talks to one object, Vortex; Vortex holds one
manager per board function; managers own hf-core handlers and get every bus and MCU pin from the board binding;
handlers wrap chip drivers; drivers reach the hardware only through the interface classes of the internal interface
wrap. Only the board binding names an ESP32 type.
Figure description and sources
From top to bottom, each layer only calls the one under it. Your application asks the facade singleton for the managers. The managers (one per board function) are MCU-free: they own the hf-core handlers, which wrap one chip driver each, and they get every bus, MCU pin, NVS namespace and the status LED from the board binding. The binding is the only HAL code that names an ESP32 type; it constructs the ESP32 implementations of the interface classes, which sit on ESP-IDF. Drivers talk to hardware only through those interface classes. Every name in a band is read from the code: the managers from the facade's member references, the binding's classes from its #include lines, the handlers, drivers and interface classes from the managers' and handlers' #include lines.
Drawn from: lib/api/Vortex.h Vortex (manager references); lib/managers #includes; lib/core/handlers #includes; lib/managers/bsp_bind/esp32c6 #includes; figure module docs/diagrams/figures/hal-layers.py.
The layers¶
| Layer | Where | What it does | You use it when |
|---|---|---|---|
| Application | your project, examples/esp32 |
Tasks, control loops, protocols | Always |
| Facade | lib/api/Vortex.h |
One singleton; brings the managers up in order; system diagnostics | Always: Vortex::GetInstance() |
| Managers | lib/managers |
One per board function (motors, encoders, IMU, ADC, ...); know the board's wiring from the pin map; MCU-free | Almost always: vortex.motors, vortex.encoders, ... |
| Board binding | lib/managers/bsp_bind |
Creates the buses, MCU pins, NVS namespaces, die sensor and status LED for one MCU (esp32c6/), or mocks them for host tests (host/) |
When porting to another MCU |
| hf-core handlers | lib/core/handlers |
Wrap one chip driver each and adapt it to the interface classes | Rarely; for chip features a manager does not expose |
| Chip drivers | lib/core/hf-core-drivers/external |
Register-level drivers, one repository per chip | Almost never from application code |
| Interface wrap | lib/core/hf-core-drivers/internal/hf-internal-interface-wrap |
BaseSpi, BaseGpio, BaseAdc, ... and their ESP32 implementations (EspSpi, EspGpio, ...) |
When porting to another MCU |
The rule that holds the design together: a layer only calls the layer below it. The managers are the only code that knows Vortex V1 exists; handlers and drivers are shared with other HardFOC boards. Why the line is drawn there is recorded in ADR 0001.
The board binding¶
VortexBoardBind.h is a set of free functions (UartOpen(),
SpiOpen(), SpiAddDevice(), I2cOpen(), McuGpioOpen(), Nvs(), McuTemperature(), StatusLedShow(), ...)
with one implementation per platform; CMake links exactly one. The ESP-IDF component builds
bsp_bind/esp32c6/, and the
host tests build bsp_bind/host/, which hands out hf-core's mocks. Every object
the binding creates lives in static storage inside it, so a manager holds plain pointers and indices.
From VortexBoardBind.h,
the vortex::bsp:: calls in the managers and the #include lines of
lib/managers/bsp_bind/esp32c6; do not edit.
| Manager | Binding functions it calls |
|---|---|
CommChannelsManager |
CanClose(), CanOpen(), I2cAddDevice(), I2cClose(), I2cDevice(), I2cOpen(), I2cProbe(), I2cRemoveDevice(), I2cReset(), I2cScan(), SpiAddDevice(), SpiClose(), SpiDevice(), SpiOpen(), UartClose(), UartOpen() |
GpioManager |
McuGpioCloseAll(), McuGpioOpen() |
LedManager |
StatusLedClose(), StatusLedOpen(), StatusLedShow() |
MotorController |
MonotonicMicros() |
NvsManager |
Nvs() |
TemperatureManager |
McuTemperature() |
The ESP32-C6 implementation constructs EspCan, EspGpio, EspI2c, EspNvs, EspSpi, EspTemperature, EspUart,
and drives the status LED through the ws2812 handler. No other HAL source names an ESP32 type.
tools/lint/lint_hal_boundary.sh keeps it that way: an ESP-IDF, FreeRTOS, mcu/ or WS2812 RMT include anywhere in
lib/api or lib/managers outside bsp_bind/<platform>/ fails CI. RTOS primitives come from hf-utils-rtos-wrap
(RtosMutex, SignalSemaphore, os_delay_msec()).
Read on¶
- Initialisation: the order the managers come up and what happens when one fails.
- Ownership: which manager creates which handler, and how memory is allocated.
- Threading: which calls are safe from several tasks.
- Errors and diagnostics: error enums,
GetLastError(), health snapshots.