Problem
board::Board::Init() returns std::expected<void, common::Error>, and
common::Error is a plain 12-value enum with no payload
(src/libs/common/error.hpp). So a board with several off-chip devices can
report that bring-up failed, but not which device failed or why.
With one hardware board and no off-chip peripherals this is invisible. It stops
being invisible as soon as stage 2 has real occupants — an I2C sensor, a SPI
flash, a UART-attached radio. A kTimeout from Init() would then mean "one of
four things did not answer", which is not much better than a hang for someone
holding the board.
docs/BOOT_FLOW.md records this under Known gaps, and it is the first of those
gaps to land: it is a prerequisite for off-chip bring-up being useful, not a
consequence of it.
Made worse by having nowhere to report to
Even a rich error has no destination during bring-up. main.cpp stashes the
error where a debugger can read it and halts, because the console is a stage 3
resource that the application brings up — mcu::Uart::Init is called by the
app, deliberately, so the board cannot have a console while it is initialising.
So this issue is really two questions, and they may want different answers:
- What does a failing
Board::Init() carry?
- Where does that go on a board whose only output device is not up yet?
Options
None obviously right, which is why this is an issue.
- Widen
common::Error with peripheral-specific values (kI2CSensorAbsent,
kFlashIdMismatch, ...). Cheap, no signature change, and it does not scale:
the enum becomes a registry of every device any board might carry.
- A richer error type for bring-up only — a small struct carrying which
peripheral and an underlying common::Error. Keeps common::Error clean,
but introduces a second error vocabulary, and the peripheral identifier has
to be nameable without allocation.
- Per-peripheral accessors that can fail, i.e. the
board::Board narrowing
already deferred to Milestone 3 — I2C1() -> std::expected<mcu::I2CController&, Error>.
Then a device that did not come up is reported at the point of use, by the
thing that knows what it wanted. Largest change, and it is already on the
roadmap for other reasons.
- A bring-up log rather than a return value — record what failed into a
buffer the application can print once it has a console. Solves question 2
directly, and is the only option here that does.
Options 3 and 4 are close to orthogonal and might both be right.
Not this
Widening common::Error device by device as devices are added. That is option 1
arrived at by accident rather than on purpose, and the enum is shared with every
non-board caller.
Anchored in the code
src/libs/common/error.hpp — the enum
src/libs/board/board.hpp — Init()'s signature and the two-phase contract
src/libs/board/stm32f767zi_nucleo/main.cpp:19-27 — the halt-with-error-in-a-register path
docs/BOOT_FLOW.md — Known gaps
docs/PROJECT_PLAN.md — Milestone 3, alongside the board::Board narrowing
Problem
board::Board::Init()returnsstd::expected<void, common::Error>, andcommon::Erroris a plain 12-value enum with no payload(
src/libs/common/error.hpp). So a board with several off-chip devices canreport that bring-up failed, but not which device failed or why.
With one hardware board and no off-chip peripherals this is invisible. It stops
being invisible as soon as stage 2 has real occupants — an I2C sensor, a SPI
flash, a UART-attached radio. A
kTimeoutfromInit()would then mean "one offour things did not answer", which is not much better than a hang for someone
holding the board.
docs/BOOT_FLOW.mdrecords this under Known gaps, and it is the first of thosegaps to land: it is a prerequisite for off-chip bring-up being useful, not a
consequence of it.
Made worse by having nowhere to report to
Even a rich error has no destination during bring-up.
main.cppstashes theerror where a debugger can read it and halts, because the console is a stage 3
resource that the application brings up —
mcu::Uart::Initis called by theapp, deliberately, so the board cannot have a console while it is initialising.
So this issue is really two questions, and they may want different answers:
Board::Init()carry?Options
None obviously right, which is why this is an issue.
common::Errorwith peripheral-specific values (kI2CSensorAbsent,kFlashIdMismatch, ...). Cheap, no signature change, and it does not scale:the enum becomes a registry of every device any board might carry.
peripheral and an underlying
common::Error. Keepscommon::Errorclean,but introduces a second error vocabulary, and the peripheral identifier has
to be nameable without allocation.
board::Boardnarrowingalready deferred to Milestone 3 —
I2C1() -> std::expected<mcu::I2CController&, Error>.Then a device that did not come up is reported at the point of use, by the
thing that knows what it wanted. Largest change, and it is already on the
roadmap for other reasons.
buffer the application can print once it has a console. Solves question 2
directly, and is the only option here that does.
Options 3 and 4 are close to orthogonal and might both be right.
Not this
Widening
common::Errordevice by device as devices are added. That is option 1arrived at by accident rather than on purpose, and the enum is shared with every
non-board caller.
Anchored in the code
src/libs/common/error.hpp— the enumsrc/libs/board/board.hpp—Init()'s signature and the two-phase contractsrc/libs/board/stm32f767zi_nucleo/main.cpp:19-27— the halt-with-error-in-a-register pathdocs/BOOT_FLOW.md— Known gapsdocs/PROJECT_PLAN.md— Milestone 3, alongside theboard::Boardnarrowing