Hardware porting
A port is a set of C files that implement the functions declared in
hal/hal.h for one specific platform. The build system selects the correct port
directory based on TARGET; there is exactly one port per build and no runtime
fallback. Adding a target never requires touching shared code.
The port contract
Section titled “The port contract”A port is conformant if and only if it:
- implements all the functions in the HAL API — none may be a silent no-op without explicit documentation;
- introduces no unapproved dependencies (no vendor SDK, except where architecturally unavoidable, e.g. ESP-IDF for the esp32 target);
- uses no dynamic allocation (
malloc/calloc/reallocare forbidden); - modifies no files in
hal/outside its ownhal/<target>/directory; - exposes no port header to application code — internal headers (e.g.
sim_bus.h) are private to the port directory.
POSIX is the primary target
Section titled “POSIX is the primary target”The POSIX port is always complete, always tested in CI, and never gated on the physical hardware you happen to have. It is the reference against which every other port is measured.
The stub-first rule for MCU ports
Section titled “The stub-first rule for MCU ports”The STM32L4 port (and future MCU ports) follows a strict rule for each new peripheral, so CI never breaks for lack of an MCU implementation:
- Compiling stub in the same PR. When a peripheral is added to the HAL and implemented on POSIX, the STM32L4 port gets a stub that compiles cleanly but does nothing (or returns a neutral value). CI stays green.
- Real implementation as a
deferredissue. The actual STM32 driver (registers, RCC clocks, polling) is tracked as afeature+hal+deferredissue, planned for when physical hardware is available. - Hardware tests as a
deferredissue. Tests on a physical Nucleo are tracked separately as atest+hal+deferredissue. They are never a prerequisite for merging.
STM32L4 port status
Section titled “STM32L4 port status”Target board: STM32L452RE / Nucleo-L452RE, direct register access, no STM32Cube.
| Peripheral | Status |
|---|---|
| GPIO | ✅ implemented (MODER, BSRR, IDR, ODR) |
| Timer | ✅ implemented (SysTick, sub-ms timestamp) |
| UART | ✅ implemented (USART2, PA2/PA3, polling) |
| ADC | ⚠️ stub — pending hardware |
| SPI | ⚠️ stub — pending hardware |
| I2C | ⚠️ stub — pending hardware |
Adding a new target
Section titled “Adding a new target”Each toolchain lives in a single make/<target>.mk file that defines the
compiler, flags, and a handful of variables consumed by the root Makefile and
the example Makefiles:
| Variable | Purpose |
|---|---|
CC, AR, OBJCOPY | toolchain |
PORT | port directory name (posix, stm32l4, …) |
CFLAGS / LDFLAGS | compile / link flags (linker script, -nostdlib, …) |
PORT_EXTRA_SRCS | extra sources beyond hal_<port>.c (e.g. sim_bus.c, startup.c) |
LINK_LIBS | libraries for linking executables (e.g. -lpthread, -lgcc) |
POST_BUILD | optional post-link command (e.g. objcopy -O binary to produce a .bin) |
Adding a port — say AVR — means adding make/avr.mk and a hal/avr/ directory.
Nothing else changes. There are no #ifdef TARGET blocks anywhere in the source.
Supported targets, by priority: posix (first class), stm32l4, esp32
(wraps ESP-IDF), avr (avr-libc, mind the 8-bit constraints).