Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Worked Example — STM32F4 Out of Tree

This chapter takes one real board all the way through the customization ladder: an STM32F429/F407 with Ethernet, built and maintained entirely outside the nano-ros repository.

It exists because that is the honest arrangement for this board. nano-ros carried nros-board-stm32f4 and nros-board-rtic-stm32f4 in-tree for a long time, and no CI lane ever booted either of them — the hardware is not in the test rack and QEMU does not model the STM32 MAC. An in-tree crate reads as a support promise, so the two crates left the supported matrix (RFC-0064, phase-337 W7.a) and became this page instead. The hardware works exactly as well as it did; what changed is who verifies it, and that is now you.

If you want the general shape rather than this specific board, read Custom Board Package and Vendor Overlay Board Crate first — this chapter assumes them and only shows the STM32F4-specific decisions.

What this board actually needs

Four facts drive everything below. They came from the retired crate’s own descriptor:

FactValue
Targetthumbv7em-none-eabihf
Link-C link-arg=-Tlink.x, plus a memory map for the chip
Net stack ownershipnanoros-owned — nano-ros supplies both the MAC driver and the IP stack
TransportsEthernet (STM32 MAC + smoltcp) or serial (USART)

The third row is the expensive one, and it is why this board costs more than a Zephyr or NuttX port. On an RTOS-owned board the host ecosystem already brings up the NIC and the IP stack, and nano-ros only asks it for a socket. Here, nothing else brings up the NIC — so the driver and the stack are part of your board, not part of the platform. Budget for that before anything else: RFC-0064 measures a MAC driver at roughly 500 lines when one does not already exist.

Which rung you are on

The ladder has three rungs, and this board sits on different ones depending on what you are changing.

What you wantRungWhat you write
A different chip in the F4 family (F407 vs F429)1 — declareone chip value + its memory map
A different PHY, same MAC2 — hookthe board’s Ethernet init function
A proprietary link instead of Ethernet1 + 2link.custom + the transport vtable
An IP stack that is not smoltcp3 — escapethe platform ABI’s net functions directly

Rung 3 always exists, and it carries a rule worth stating plainly: if you ship an artifact by hand, the generator does not overwrite it and the drift gate does not flag it. You can adopt generated glue one file at a time and keep the files you have reason to own.

The shape of the crate

Your board crate depends on nano-ros; nano-ros does not depend on it. That is the whole point of taking it out of tree, and it is what lets you pin a nano-ros release and upgrade on your own schedule.

my-stm32f4-board/
  Cargo.toml          # depends on nros + the bare-metal platform
  nros-board.toml      # the descriptor the `nros` CLI reads
  build.rs             # emits the linker script / memory map
  memory.x             # FLASH + RAM origin and length for your chip
  src/lib.rs           # the board ZST, its trait impls, and `run()`

Compose your Config on nros_board_common::BaseConfig rather than declaring the network fields again — it carries {mac, ip, netmask, gateway, zenoh_locator, domain_id} and the DeployOverlay merge, and keeps your board aligned with every other one when those fields change. Board-specific settings (a UART base address, a PHY address) stay on your own struct beside it:

#![allow(unused)]
fn main() {
pub struct Config {
    pub base: nros_board_common::BaseConfig,
    pub phy_addr: u8,
}
}

BaseConfig stores the netmask; prefix() and with_prefix() convert if you think in CIDR.

The descriptor

nros-board.toml is what makes the nros CLI able to generate an entry for your board. The retired crate’s descriptor is a working template — the fields that matter for a bare-metal Cortex-M board with its own stack:

[[board]]
names               = ["my-stm32f4"]
platform            = "stm32"
toolchain           = "stable"
platform_feature    = "platform-bare-metal"
link_kind           = "none"
entry_kind          = "board-run"
supported_netstacks = ["smoltcp"]
chip                = "stm32f429"
board_crate         = "my-stm32f4-board"

# entry_kind = "board-run" needs the matching [board.entry] block —
# copy the one from packages/boards/nros-board-mps2-an385/nros-board.toml
# and swap the crate name.
[board.entry]
crate_name = "my_stm32f4_board"
signature  = "#[my_stm32f4_board::entry]\nfn main() -> !"

cargo_config = '''
[build]
target = "thumbv7em-none-eabihf"

[target.thumbv7em-none-eabihf]
rustflags = ["-C", "link-arg=-Tlink.x"]
'''

supported_netstacks names who brings up the link (here your crate’s smoltcp glue — nothing outside it). Beware unknown keys: the descriptor parser ignores what it does not know rather than erroring, so a typo’d key silently does nothing.

What you take on

Be clear-eyed about the trade, because it is the reason this page exists rather than a crate:

  • You own verification. No nano-ros lane boots this board. A nano-ros release being green says nothing about your board; only your hardware does.
  • You own the driver. A MAC or PHY change is yours to make and yours to test.
  • You gain release control. Your board no longer moves when nano-ros refactors its board tree — you upgrade when you choose, and a breaking change in the seam is visible as a version bump instead of a surprise.

The seam you depend on is deliberately narrow: the platform ABI is 92 C functions, and no rung of the ladder requires nano-ros to know what your stack is. That is what makes an out-of-tree board sustainable rather than a fork.

If you want it back in tree

The bar is a witness: a lane that boots the board and asserts real delivery. That means hardware in the test rack or a QEMU machine that models the MAC. A board with no Runtime cell cannot be tier 1 or 2 — scripts/check-board-tiers.py enforces exactly that, which is what moved these crates out in the first place.