Integration Contract
The portable obligations a consumer project must honour when hosting the kbox-stack — object lifetimes, mandatory flash, boot order, and the error contract.
The stack is a portable library: its public surface is MCU-agnostic and every
hardware dependency sits behind an I<X> interface (IFlash&, IClock&,
IBusDriver&, …). This page describes the contract a consumer must honour on
any platform. Concrete flash addresses, ISR wiring, and HAL classes live on
the STM32 (HAL) platform pages
and are linked where they apply.
The current prebuilt .a targets Cortex-M3 / STM32F103xB. The API and contract
below are platform-neutral; only the shipped binary is target-specific.
Object lifetimes
MakeStack() does not own its dependencies. It placement-news the concrete
stack into an opaque KnxStackStorage you provide, and returns an IKnxStack&
that points into that buffer. Every reference you pass in — the
StackParameters, every I<X> peripheral, the KnxFlashLayout& (and, in
Secure mode, the security object it feeds into), sizing, buffers and clock —
must outlive the returned handle.
All peripherals, parameters, and the stack itself are long-lived: construction order is dependency order. Construct the peripherals first, then the flash layout, then the stack (and, in Secure mode, the security object) that borrow it, then the logic that borrows the stack.
Give KnxFlashLayoutStorage, KnxStackStorage (and SecureStackStorage)
static / file-scope lifetime. If the storage is a stack-local, the
IKnxStack& you get back dangles the moment the enclosing function returns.
The same rule applies to a FlashView handed out as an IFlash& (e.g. via
layout.StackStateView()): keep the view alive for as long as its consumer.
// Static lifetime — MANDATORY. The handle points into this buffer.
static KnxFlashLayoutStorage layoutStorage;
KnxFlashLayout& layout = MakeFlashLayout(
layoutStorage, baseFlash, stackParameters, /*userDataPageCount=*/1u);
static KnxStackStorage stackStorage;
IKnxStack& stack = MakeStack(
stackStorage, stackParameters, busDriver, watchDog, knxLed,
clock, transmitter, button, layout);Flash responsibilities
One thing is non-negotiable before the device runs: a base persistence
region must be reserved and exposed as a single IFlash&, and split into the
stack's own named sub-regions with MakeFlashLayout() — before either
MakeSecure() or MakeStack() runs. MakeFlashLayout() derives the
stack-state, user-data, and (Secure mode) counter/SIO-key regions from that one
base flash; the stack and, in Secure mode, the security object each read their
region through the returned KnxFlashLayout&.
static KnxFlashLayoutStorage layoutStorage;
KnxFlashLayout& layout = MakeFlashLayout(
layoutStorage, baseFlash, stackParameters, /*userDataPageCount=*/1u);
// DeviceParameter::SetFlash(...) is called for you, inside MakeStack()'s
// DeviceModel construction — there is nothing further to wire here.DeviceParameter::SetFlash(...) used to be a manual call the consumer made
before Init(). It is now automatic: DeviceModel's constructor calls it
with the flash view it owns (DeviceModel.cpp:37), and the shipped examples
(examples/STM32F103-HAL/led-indicator{,-secure}/Core/Src/main.cpp) do not
call it themselves. Calling it yourself is unnecessary and, if done with the
wrong flash view, would fight the stack's own wiring.
Skipping the persistence region, or passing an invalid flash layout, is not
silent: an invalid KnxFlashLayout (misaligned base tail, or a region window
overflowing the 64KB tail-address space) is reported through onStackError
as StackErrorTypeEnum_FLASH_LAYOUT_INVALID at Init(), and every
flash-dependent step (machine-state load, secure bootstrap, SaveUserData()/
LoadUserData()) is a no-op from then on. See the error contract below — the
consumer must still halt/reset on this error; the no-ops are a corruption
backstop, not a substitute for that.
Concrete flash addresses, the FLASH_USER window, region tables, and the
HalFlash implementation are platform-specific. See
STM32 (HAL) · Project Setup
for the reference layout and its compile-time / linker / runtime guards.
Boot sequence
The portable boot order is the same on every platform. ISR wiring — connecting
the bus-driver interrupt to stack.Process() — is the one platform-specific
step; see
STM32 (HAL) · Peripherals.
Construct the peripherals (IFlash, IClock, IBusDriver, ILed,
ITransmitter, IWatchDog, IButton) at static / long lifetime.
Call MakeFlashLayout(...) with the base flash to derive the stack's named
regions. Do this before MakeSecure()/MakeStack() — both consume the
returned KnxFlashLayout&. DeviceParameter::SetFlash(...) does not need
a manual call — it happens automatically inside MakeStack()'s DeviceModel
construction.
Build StackParameters, then (Secure mode) MakeSecure(...), then
MakeStack(...) (add the ISecurity& argument for Secure mode). Derive your
logic from KnxLogicBase so it auto-registers with the stack — no manual
SetLogic needed.
Wire the bus-driver ISR to stack.Process() — platform-specific.
Call stack.Init(), then run stack.Work() on every iteration of the main loop.
Use stack.IsAllMachineStateLoaded() to confirm the stored tables loaded.
stack.Init();
for (;;) {
stack.Work(); // drives logic.Work() and the whole state machine
}Error contract
The stack reports fatal problems through the onStackError callback you
install in StackParameters. Most fire when the storage sizes declared in
StackParameters cannot hold what an ETS download tried to write; two more
(below) fire when the flash layout or secure storage itself is invalid. The
correct reaction in every case is a safe stop — halt (or, with the
watchdog enabled, let it reset) rather than continue with a corrupt machine
state.
void onStackError(StackErrorTypeEnum errorType) {
if (errorType == StackErrorTypeEnum_MACHINE_STATE_SIZE_ERROR) {
while (1) { /* safe stop — sizes in StackParameters are too small */ }
}
if (errorType == StackErrorTypeEnum_SEGMET1_SIZE_ERROR) { while (1) {} }
if (errorType == StackErrorTypeEnum_SEGMET2_SIZE_ERROR) { while (1) {} }
if (errorType == StackErrorTypeEnum_SEGMET3_SIZE_ERROR) { while (1) {} }
if (errorType == StackErrorTypeEnum_SEGMET4_SIZE_ERROR) { while (1) {} }
if (errorType == StackErrorTypeEnum_FLASH_LAYOUT_INVALID) { while (1) {} }
if (errorType == StackErrorTypeEnum_SECURE_STORAGE_INVALID) { while (1) {} }
}A size error almost always means a table size in StackParameters is smaller
than the ETS project needs. See
Database · Table sizing vs ETS download.
FLASH_LAYOUT_INVALID (an invalid KnxFlashLayout — misaligned base tail, or
a region window overflowing the 64KB tail-address space) and
SECURE_STORAGE_INVALID (caller-owned SIO buffers mis-sized) share the same
consumer contract:
After either error fires, the consumer must perform an explicit safe-stop
(an LED error code + while(1), or a controlled reset) — do not start any new
work. The stack narrows the blast radius on its own side (Process() stops
pumping the bus/watchdog once the layout is invalid, and
SaveUserData()/LoadUserData()/UserDataCapacity() become no-ops), but this
is a corruption backstop, not a substitute for the consumer's own
halt/reset decision — a device that ignores the error and keeps looping will
simply do nothing useful (and, with the watchdog enabled, reset-loop, which
does not fix the underlying misconfiguration).
layout.IsValid() validates only the derived region table (page-aligned base,
additive offsets, 64KB block bound) — it does not cross-check the physical
linker FLASH_USER length. If a future StackParameters/SioSizing/
userDataPageCount change makes the derived page total exceed the linker
window, IsValid() still returns true; the only guard against that case is
the .ld's own ASSERT(LENGTH(FLASH_USER)==N*0x400), which must be kept in
sync by hand.