Porting to a New Platform
How to bring the MCU-agnostic KNX core to a new microcontroller by implementing the peripheral interfaces and rebuilding the static library for your target.
What porting means
The core of kbox-stack is MCU-agnostic: it depends only on the I<X> peripheral
interfaces, never on any register, HAL, or vendor SDK. Porting means implementing those
interfaces for your MCU and HAL — and nothing more. You do not modify, fork, or even
read core source; you supply the hardware layer the core plugs into.
The prebuilt archive shipped today targets Cortex-M3 / STM32F103xB
(libkbox-stack---cortexm3---STM32F103xB.a). The design is portable, but a different
CPU or ABI needs the core rebuilt for that target — the shipped .a will not link
against, say, a Cortex-M0+ or Cortex-M4F ABI. A rebuilt binary for your target is
required in addition to your peripheral implementations.
Interfaces to implement
Implement one class per peripheral. Where a Base<X> exists, subclass it and fill only
the pure hooks (see Reusing Base<X> below).
IBusDriver — the KNX TP1 bus. Subclass BaseBusDriver (it gives you TX/RX queues,
the Work() pump and inter-byte timing) and implement Init(), AvailableForTx() and
Transmit() against your UART and transceiver.
IClock — a monotonic millisecond time base. Subclass BaseClock and call Tick()
from a 1 ms timer interrupt; the counter logic is already done.
IWatchDog — the independent hardware watchdog. Subclass BaseWatchDog and
implement Reset() to kick your IWDG; Enable/Disable/IsEnabled are provided.
IFlash — non-volatile storage for the tables and stack state. Subclass BaseFlash
(address bookkeeping and page size are provided) and implement the Write / Put /
ErasePages / ReadUintN / ReadBit / Lock / Unlock methods against your flash
controller.
ILed — the programming LED. Subclass BaseLed (the blink state machine runs off
IClock) and implement the two GPIO hooks writePin(bool) and togglePin().
IButton — the programming button. Subclass BaseButton (callback storage is done)
and wire your GPIO interrupt to call OnInterupt().
ITransmitter — sets the device's individual address on the transceiver. Subclass
BaseTransmitter (it holds the IBusDriver&) and implement Init() and
SetIndividualAddress().
All method signatures are documented in Peripheral Interfaces.
Reusing Base<X>
Every peripheral ships a Base<X> companion that implements the platform-independent
logic once, so your port only writes the hardware-touching parts:
| Interface | Base companion | What Base gives you | What you still implement |
|---|---|---|---|
IBusDriver | BaseBusDriver | TX/RX ring queues, Work() pump, inter-byte timing | Init, AvailableForTx, Transmit |
IClock | BaseClock | full ms/second counter | call Tick() from a 1 ms ISR |
IWatchDog | BaseWatchDog | enabled flag + Enable/Disable/IsEnabled | Reset() (the kick) |
IFlash | BaseFlash | address bookkeeping + 1024-byte page size, Get* | read/write/erase/lock |
ILed | BaseLed | blink state machine off IClock | writePin, togglePin |
IButton | BaseButton | callback storage + dispatch | wire GPIO IRQ to OnInterupt() |
ITransmitter | BaseTransmitter | holds the IBusDriver& | Init, SetIndividualAddress |
Subclassing Base<X> is the recommended path — it keeps your port small and inherits the
behavior the reference platform is validated with.
Timing & flash constraints
- UART line timing.
BaseBusDriverenforces an inter-byte wait after the last data byte (awaitAfterLastDataBytegap) and a post-transmit delay before the next frame. YourTransmit()must honor the physical TP1 line timing; driveTick()/Work()at a 1 ms cadence so these gaps are measured correctly. - Flash page / erase semantics.
BaseFlashassumes a fixed page size (default1024bytes) and word-addressed writes. Erase is page-granular viaErasePages, and writes are in 32-bit words (Write/Puttakeuint32_t*and a word count). Map these onto your controller's real page size and program width; if your flash differs, override the page-size accessor and honor your erase granularity.
Validating your port
Because the shipped .a is ARM-only, validate the portable application logic with host
(PC) tests rather than on-target only. The examples follow this pattern:
- Each example ships an
<example>/tests/folder with host tests built as a separate native CMake project using GoogleTest. - Prefer testing your logic by injecting mocks / fakes for the
I<X>interfaces — a fakeIClockandIFlashlet you exercise timing and persistence deterministically on the host. - Host tests compile the small set of portable stack source files they need; they are a developer tool and are not part of the customer's dist-only package.
Study the examples under examples/<PLATFORM-HAL>/<example-name>/ (for example
examples/STM32F103-HAL/led-indicator/). Each is a complete, buildable, flashable app
that uses only the public <Stack/...> API — copy one as your port's skeleton.
Reference implementation
The STM32F103 + NCN5130 platform is the fully worked port. Read it as your template, then document your own platform section alongside it.