board
Board manifest for FlintOS.
Each supported board is a submodule that exports:
BOARD— the board as one [Board] value, the facts an application asks about gathered withOptionfields (see [Board])TARGET_BUSES— physical bus definitionsTARGET_DEVICES— logical device attachmentsTARGET_PERIPHERALS— direct peripheral mappings (bus controllers are not repeated here)
What belongs in a manifest
Section titled “What belongs in a manifest”Every fact about the board that an application would otherwise have to
look up in a datasheet: pins, base addresses, IRQ numbers, and the shape of
anything attached. A pin without the count of what is on it is half a fact
— RGB_LED_GPIO alone let an application drive one LED of a 25-LED panel
and look correct while 24 stayed dark.
Board selection
Section titled “Board selection”The active board is chosen at compile time via Cargo features, one per supported board. Exactly one must be enabled — the guards below turn “zero selected” and “more than one selected” into build failures instead of a silently wrong manifest, which on real hardware shows up as a very confusing bring-up bug (wrong pins, wrong IRQ, etc).
Downstream crates (namely kernel) never name a board module directly;
they use board::active, which this crate re-exports to whichever board
module was selected.
cargo build -p kernel --no-default-features --features board-m5-atom-matrixcargo build -p kernel --no-default-features --features board-esp32-devkitccargo build -p kernel # default: board-esp32-wroverAdding a new board: run make new-board NAME=<name> FROM=<board>, which
clones an existing manifest to src/<name>.rs and does the wiring — the
board-<name> feature in this crate and kernel, the pub mod and pub use … as active lines below, the SELECTED count, and (for an RP2040 board)
the Makefile’s RP2040_BOARDS. You then edit only what differs (a pin). The
device accessors (user_led, console, …) are keyed on the driver bundle
(rp2040-drivers / esp32-drivers), not on board names, so a cloned board
of a known family gets them for free. See the “Add a board” tutorial.
Modules
Section titled “Modules”Structs
Section titled “Structs”struct Board { pub name: &'static str, pub imu: Option<I2cAttachment>, pub pmic: Option<PmicAttachment>, pub touch: Option<I2cAttachment>, pub display: Option<DisplayAttachment>, pub rgb_led: Option<RgbLed>, pub grove: Option<GrovePort>, pub selftest: SelftestPads, pub console: ConsolePins,}Everything an application asks a board manifest about, as one value.
Implements: Clone, Copy, Debug
References: ConsolePins, DisplayAttachment, GrovePort, I2cAttachment, PmicAttachment, RgbLed, SelftestPads
ConsolePins
Section titled “ConsolePins”struct ConsolePins { pub tx: u8, pub rx: u8, pub baud: u32,}The board’s console pins and baud.
Every board FlintOS runs puts its console on UART0, so the controller is
implied; only the pins and baud are a board fact. Held as plain numbers so
the one struct serves both SoC families — each board’s console_init builds
the SoC-specific port from these.
Implements: Clone, Copy, Debug
DisplayAttachment
Section titled “DisplayAttachment”struct DisplayAttachment { pub port: soc_esp32::SpiPort, pub dc: u8, pub cs: u8,}An SPI display panel wired onto the board: the SPI port it is driven over and
the two GPIOs — data/command and chip-select — the transport toggles. The
panel’s own reset (an AXP192 GPIO on the Core2) is the driver’s init
closure, not a manifest pin.
Implements: Clone, Copy, Debug
References: SpiPort
GrovePort
Section titled “GrovePort”struct GrovePort { pub sda: u8, pub scl: u8,}A Grove/expansion port’s two free pins, if the board exposes one. The
pwm example drives one of these; a board without a port sets None.
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
I2cAttachment
Section titled “I2cAttachment”struct I2cAttachment { pub port: soc_esp32::I2cPort, pub addr: u8,}An I2C device wired onto the board: which controller and pins bring it up
(an I2cPort, so “which controller” is no longer
half-declared), and the 7-bit address it answers at.
Implements: Clone, Copy, Debug
References: I2cPort
LoopbackPads
Section titled “LoopbackPads”struct LoopbackPads { pub scratch: u8, pub aux: (u8, u8),}The two spare pads a folded single-pad loopback needs, plus the pad the fold
routes onto. See [loopback_pads].
Implements: Clone, Copy, Debug
PmicAttachment
Section titled “PmicAttachment”struct PmicAttachment { pub port: soc_esp32::I2cPort, pub addr: u8, pub rails: &'static [RailSetup],}A power-management IC wired onto the board: which controller and pins reach it, its address, and the ordered list of rails to bring up at boot.
This is the manifest fact that finally gives the board a power ordering to
express — which rail must be up before the peripherals that sit on it. The
system rail (DCDC1 on the Core2, powering the ESP32 itself) must never
appear in rails; power_init refuses it rather than brown the CPU out.
Implements: Clone, Copy, Debug
References: I2cPort, RailSetup
RailSetup
Section titled “RailSetup”struct RailSetup { pub rail: axp192::Rail, pub millivolts: u16,}One rail to bring up at boot: which Rail and at what
voltage. A board’s [PmicAttachment::rails] is an ordered list of these.
The order is the inter-rail ordering the PMIC introduces — a peripheral rail
up before the rail that depends on it. power_init walks the list in order,
setting each rail’s voltage then enabling it.
Implements: Clone, Copy, Debug
References: Rail
RgbLed
Section titled “RgbLed”struct RgbLed { pub gpio: u8, pub count: usize, pub layout: Option<led_matrix::Layout>,}A board-owned addressable-LED strip or panel.
layout is None for a single LED (no geometry to fold) and Some for a
panel — the same fact the RGB_LED_LAYOUT const carries, gathered here with
the pin and the count so an application takes all three as a unit.
Implements: Clone, Copy, Debug
References: Layout
SelftestPads
Section titled “SelftestPads”struct SelftestPads { pub scratch: Option<u8>, pub aux: Option<(u8, u8)>, pub spi_slave: Option<[u8; 4]>, pub pcnt: Option<u8>, pub touch: Option<u8>, pub mcpwm: Option<[u8; 3]>, pub adc_external_high: Option<u8>,}Electrically-free pads a board offers the on-chip loopback self-tests and the bus/stream porting examples.
Each mirrors a loose *_GPIO const; gathered here so BOARD.selftest is one
value. None on any field means “no free pad for that test on this board”,
and the test or example skips and says so. Plain pin numbers, so the struct
is arch-neutral.
Implements: Clone, Copy, Debug, Default
Functions
Section titled “Functions”console
Section titled “console”fn console() -> Option<&'static dyn hal::stream::ByteStream>The board’s console as a ByteStream, or None
if this board has no console driver.
console_init
Section titled “console_init”fn console_init() -> boolBring up the console. Called once, first thing in startup::init.
Returns whether it came up at the board’s configured framing. false means
the port rejected the config and this fell back to the bootloader’s settings
(usually 115200 8N1, so still readable) — the kernel logs a warning on
false. A board with no console driver returns true (nothing to warn
about) and [console] returns None.
display_interface
Section titled “display_interface”fn display_interface() -> hal::Result<Esp32DisplayInterface>A ready DisplayInterface for the board’s
panel: the shared SPI controller plus its data/command and chip-select pins,
with DMA pixel streaming. Error::Other if this board declares no display.
Wrap the returned interface in a panel driver (ili9342c::Ili9342c::new).
Allocates DMA buffers, so call it from the task that will draw. The caller
must also connect the SPI controller’s interrupt for DMA completion — see
[Esp32DisplayInterface].
Runs the panel at the board manifest’s declared speed. An application that
wants a different clock uses [display_interface_at].
References: Result
display_interface_at
Section titled “display_interface_at”fn display_interface_at(speed: hal::bus::BusSpeed) -> hal::Result<Esp32DisplayInterface>Like [display_interface], but runs the panel at speed — clamped to the
board manifest’s declared maximum.
The split is deliberate. The manifest’s max_speed is a hardware fact the
board author owns: how fast this panel and its wiring can be clocked without
glitching. An application owns a different question — whether to trade some
of that speed for margin (long jumper wires, a stacked module, EMI headroom)
— so it may ask for less. It may not ask for more: a request above the
manifest ceiling is capped at the ceiling, never granted, because the board,
not the app, is what knows the wire.
fold_spi_loopback
Section titled “fold_spi_loopback”fn fold_spi_loopback() -> hal::Result<()>Fold SPI2’s MOSI and MISO onto the loopback scratch pad, making the on-chip
loopback the spitxrx example runs on. Error if this board declares no
loopback pads.
Kept separate from [loopback_spi] because init refuses two signals on
one pad: the bus is brought up on three distinct pins first, then this folds
MISO onto the MOSI pad. Routing is safe — ownership of the pad is the proof
— so the app calls this instead of reaching for Esp32PinMux.
References: Result
imu_bus
Section titled “imu_bus”fn imu_bus() -> hal::Result<&'static i2c_bus::I2cController<esp32_i2c::Esp32I2c>>The onboard IMU’s I2C bus. Error::Other if this board declares no IMU
(BOARD.imu is None).
The returned I2cController is shared with any
other device on the same controller (see [i2c_controller]); take a device
handle with .device(addr) for the address to talk to.
References: Esp32I2c, I2cController, Result
lcd_spi
Section titled “lcd_spi”fn lcd_spi() -> hal::Result<&'static esp32_spi::Esp32Spi>The display panel’s SPI controller, opened once and cached.
Returned as the concrete Esp32Spi rather than a
bus, because the display transport streams pixels by DMA (exchange_async),
which the byte-capped Bus layer does not expose — and because the DMA
completion interrupt’s top-half needs this same shared controller to
acknowledge it. Error::Other if this board declares no display.
fn led() -> Option<RgbLed>The board’s addressable LED, if it has one.
This returns the LED’s identity — its pin, count and fold — not a driven
strip. Driving a WS2812 over the RMT needs an interrupt handler and a frame
buffer sized to the LED count, which an application owns (see
apps/examples/blink); the board’s job is to say which LED is there. An
accessor that returned a live strip would have to move that ISR into the
board crate, which no board consumer wants today.
References: RgbLed
led_rmt
Section titled “led_rmt”fn led_rmt(divider: u8) -> hal::Result<rmt::Rmt>Open RMT channel 0 on the board’s addressable-LED pin, with divider as the
clock divider (the caller sizes it from the LED’s pulse timing). Error if
this board declares no addressable LED.
The board gates the clock, routes the signal and enables the pad’s output;
the app keeps the frame buffer and the interrupt handler. The claim inside
[rmt::Rmt::on_pin] proves single ownership, so there is no static mut.
loopback_pads
Section titled “loopback_pads”fn loopback_pads() -> Option<LoopbackPads>The free pads for the loopback bus/stream porting examples, or None if
this board declares none.
scratch is the single data pad the fold routes onto; aux are the two
spare pads init needs to see distinct pins before the fold. See the
DevKitC manifest for the electrical story.
References: LoopbackPads
loopback_spi
Section titled “loopback_spi”fn loopback_spi() -> hal::Result<&'static spi_bus::SpiBus<esp32_spi::Esp32Spi>>A SPI bus opened on the board’s loopback pads (SPI2, mode 0, 4 MHz), for the
spitxrx porting example. Error::Other if this board declares no loopback
pads.
The bus is brought up on three distinct pads; the caller then folds MISO
onto the MOSI pad to make the on-chip loopback (init refuses two signals
on one pad, so the fold cannot be part of bring-up).
References: Esp32Spi, Result, SpiBus
pmic_bus
Section titled “pmic_bus”fn pmic_bus() -> hal::Result<&'static i2c_bus::I2cController<esp32_i2c::Esp32I2c>>The PMIC’s I2C bus: opens the controller on the board’s PMIC pins and caches
it. Error::Other if this board declares no PMIC (BOARD.pmic is None).
Returns the I2cController; take a device with
.device(axp192::ADDR) and wrap it in [axp192::Axp192]. Shares the
controller with any other device on the same bus (the Core2’s IMU sits on the
same I2C0) via [i2c_controller], so the two never fight to open it.
References: Esp32I2c, I2cController, Result
power_init
Section titled “power_init”fn power_init() -> boolBring the board’s power rails up, in the manifest’s order. Called once by
startup::init, after the console and before any rail-dependent device.
Returns true if every declared rail came up (or the board has no PMIC —
nothing to do). A false means at least one rail write failed, or the
manifest listed the system rail (DCDC1), which this refuses to touch: moving
it under the running CPU browns the board out, so a board that lists it is a
manifest bug caught here rather than a dead board.
Also switches on the battery ADC so a later [pmic_bus] caller can read
charge state; a failure there is not fatal to bring-up and is ignored.
fn pwm(pin: u8, freq_hz: u32, res_bits: u8) -> hal::Result<PwmChannel>Open LEDC high-speed channel 0 on pin at freq_hz/res_bits, with the
pad left readable so the caller can measure its own output.
The claim inside the constructors proves single ownership, so there is no
unsafe and no static mut. pin is passed in rather than read from the
manifest so this serves any board’s free pad; pwm supplies its Grove-port
SDA pin.
References: PwmChannel, Result
read_pwm_pin
Section titled “read_pwm_pin”fn read_pwm_pin(pin: u8) -> boolRead the level of pin back through the GPIO controller. true is high.
The counterpart to [pwm]: sampling the pad LEDC drives is how the example
measures its own duty cycle with no instrument attached.
touch_bus
Section titled “touch_bus”fn touch_bus() -> hal::Result<&'static i2c_bus::I2cController<esp32_i2c::Esp32I2c>>The touch controller’s I2C bus. Error::Other if this board declares no
touch panel (BOARD.touch is None).
Shares the controller with any other device on the same bus (the Core2’s
touch, IMU and PMIC are all on I2C0) via [i2c_controller]; take a device
handle with .device(addr).
References: Esp32I2c, I2cController, Result
uart_loopback
Section titled “uart_loopback”fn uart_loopback() -> hal::Result<&'static dyn hal::stream::ByteStream>Open UART2 in internal loopback on the board’s free pads and return it as a
byte stream. Error if this board declares no loopback pads.
The port is cached in an api::Once, so repeated calls hand back the same
stream. The pins are routed for real (bring-up is exercised) but the data
travels the on-chip TX→RX path, so no pad edge can mis-frame it.
References: Result
Constants
Section titled “Constants”ESP32_CONSOLE
Section titled “ESP32_CONSOLE”const ESP32_CONSOLE: ConsolePins;The ESP32 console: UART0 on GPIO1 (TXD0) / GPIO3 (RXD0) at 115200 8N1 — the
same on every ESP32 board here. The one source of truth for both a manifest’s
console field and its UART0 BusConfig::uart_8n1(..), so the pins and baud
cannot drift between the two.
References: ConsolePins
LED_RMT_IRQ
Section titled “LED_RMT_IRQ”const LED_RMT_IRQ: u8;The chip’s RMT interrupt source, for wiring the channel to a CPU interrupt
with api::interrupt::connect.