v0.4.0
[0.4.0] - 2026-06-14
BSP code absorbed from the alternator-regulator application (its altreg-*
board crates), toward the goal of applications containing only task wiring.
Relicensed from GPL-3.0-only to MIT OR Apache-2.0 on import (same copyright
holder).
Added
- BSP↔binary boundary — the binary collapses to an entry shell. New surfaces
so per-board boilerplate lives here, not in the application:mem::init_heap(HeapProfile, Option<PSRAM>)(newheapfeature, implied
bypsram): the BSP owns the global heap — declares the esp-alloc DRAM
regions forHeapProfile::{Default,Lvgl,Coex}(HIL-proven per-board sizes),
so a binary never callsesp_alloc::heap_allocator!. (heappulls
esp-bootloader-esp-idffor the reclaimed-ROM region.)io::console::install(spawner, Config)(#31): one-call logging — register
thelogbackend + (whenConfig::serialisSome) bring up the chip
transport (UART0 / USB-Serial-JTAG CDC) and spawn the drain; returns
Console { rx }forserial_cmd(log TX + command RX on one port, no probe).
Plusconsole::markers(PANIC/CONSOLE_DROP/PREV_PANIC, a stable HIL
contract) and the newconsole-serialfeature (off = R9 production
backstop: no serial symbols; panic still breadcrumbs + halts).- RTC panic breadcrumb (#31 R8):
console::on_panicrecords{location, reason-digest}to RTC-fast persistent RAM before halt;take_panic_breadcrumb()
reads it once at boot — a crash survives the RWDT reset and is reported as the
PREV_PANICline, identical on both targets. panic-handlerfeature +app_desc!macro: the BSP exports
#[panic_handler](→on_panic) and wraps the esp-idf app descriptor, so a
binary opts in instead of hand-rolling them.board::run_app_core(newmulticorefeature, pullsesp-rtos): parks- starts the APP core on an
InterruptExecutor(encapsulating thepark_core
JTAG-reset workaround), running a caller closure with theSendSpawner.
- starts the APP core on an
io::InputCaps+io::input_caps(): the board's input model (Keypad
vsPointer), so a UI installs the matching indev without hardcoding the
board.ButtonEventaffirmed as a positional, app-vocabulary-free contract.
board::display(newdisplayfeature, pullslcd-async): ILI9342C
panel bring-up shared by both boards —init_ili9342c(CoreS3, no reset
pin / AW9523B + SPI SoftReset) andinit_ili9342c_with_reset(Fire27),
SCREEN_W/SCREEN_H, theIli9342ctype alias. De-dupes the builder
config previously copied in every example and in the application.board::spi2: the shared SPI2 display + SD-card bus. Per-board
Spi2Resources(pins + DMA channel) →into_parts(dma_rx, dma_tx)→
Spi2Parts::finish(card_cs)which shares the bus, initialises the display
(unconditionally — a dead/absent SD card must never cost the UI) and returns
a generic-CS SDSpiDevice. The SD driver stays with the app (sdspiis
not on crates.io); the module docs spell out the bounded-retry pre-init
pattern and the chip-specific display/SD join-order asymmetry.board::cores3::Gpio35Dc+gpio35_disable_output: register-level
GPIO35 MISO/DC muxing (the CoreS3 shares GPIO35 between SPI2 MISO and
display DC).Spi2Parts::finishre-muxes to MISO after display init — the
ordering that otherwise costs ansd_card.init()that never completes.board::cores3::Board/board::fire27::Board(Board::split): the
boards' pin wiring as data — SPI2/display/SD pins, the internal I2C0 bus
(hardened config: 400 kHz,BusTimeout::BusCycles(20), and on the S3 a
25 ms transactionSoftwareTimeout— the HIL-proven fix for the
stuck-transactionyield_nowspin that wedges anInterruptExecutor),
buttons, radio, UART0/USB-Serial-JTAG, the SK6812 LED pin, PSRAM, free M5-Bus
pins, andboard::SystemResources(timers, SW interrupts,CPU_CTRL,
LPWR). Plusboard::init()(esp-hal at max CPU clock; heap stays with the
app).board::spi2::Spi2Resources::into_display_only(featuredisplay): the
display on the descriptor-backedSpiDmaBuswith no SD path, for DMA
display-only apps (thelvglexample). DC is a plainOutputon both boards
(on CoreS3 a configured output routes GPIO35's pad, unlikeGpio35Dcwhich
needswith_miso). Returns aDisplayBus { display, backlight (Fire27) }.io::buttons: unifiedButtonEvent/ButtonId/ButtonActioninput
events for the whole Core family, plus (newbuttonsfeature, pulls
async-button) the Fire27 front-panel A/B/C driver
(ButtonResources::into_buttons→Buttons::next_event).io::touch_buttons: CoreS3 touch→button emulation overft6336u—
three zones in the bottom strip, short/multi-tap/long-press state machine,
emitting the sameButtonEventas the physical buttons.io::watchdog::watchdog_feed_loop: RWDT hardware-reset backstop, armed
and fed from the executor whose wedging it guards.must_spawn!: replacement for embassy-executor'sSpawner::must_spawn
(dropped in 0.10), panicking with call-site context.
Changed — breaking
board::cores3is now gated behind thecores3feature (it was
unconditionally public; using it from afire27build was meaningless).
Examples
- Unified the per-board example crates into one
examples/demoscrate.
The board is selected by afire27(default) /cores3cargo feature; each
bin (display,i2c_scan,m5go,wifi_sta,onewire,lvgl,coex)
builds for both boards from a single source, leaning onBoard::split+
board::display+ theioloops, with the per-board glue concentrated in
examples/demos/src/board.rs. The LVGL bin'smainis now ~50 lines (the
flush glue / view / keypad indev moved toexamples/demos/src/ui/).
examples/common(pure chip-agnostic drawing helpers) is unchanged. Build
per board:cargo build -p demos --bin <name>(Fire27, default) or add
--no-default-features --features cores3 --target xtensa-esp32s3-none-elf;
lvgl/coexare gated byrequired-features,onewireis Fire27-only.
Each bin keeps its panic-handler /esp_app_desc!()top-matter inline (no
macro), so it reads as a self-contained, copy-pasteable starting point. The
shared esp-hal-fork example deps are hoisted into[workspace.dependencies]. - The
coexbin is gated byrequired-features = ["coex"]and built on its
own (--bin coex --features coex): esp-radio's coexist blob is a crate-global
link dependency that only the BLE-initialising bin can satisfy, so thecoex
feature must stay off while the non-BLE bins are built (it is, for every
normalcargo build/--workspace/-p demosinvocation). - Input is now unified across boards via the new
io::buttons::ButtonEvent
(thedisplaybin reads Fire27 buttons / CoreS3 touch through one loop), and
logging is unified on thelogfacade (CoreS3 over RTT atInfo). - The sensor/peripheral demos (
i2c_scan,m5go,wifi_sta,coex,
onewire) now render through one sharedcommon::draw_panel(a cyan
board/title header + body lines) so they look alike, and each shows
everything it has on screen — not the console alone:onewiregained a
display (sensor count + per-sensor ROM + °C), andwifi_sta/coexnow show
the nearby-AP scan (via the shareddemos::net, which also de-dups the
net_demotask). Thedisplay/lvgldemos keep their own rendering. - The
displaybin is now a clear input-capabilities demo: a per-position
(Left/Center/Right) readout of the lastButtonEvent—tap/tap x2…/
HELD (long)— so multi-tap count and long-press are legible, identical on
Fire27 buttons and CoreS3 touch. - The
lvglbin is now interactive: three focusable LVGL buttons navigated
from the front panel (PREV/NEXT/ENTER), identical on both boards — the
unifiedButtonEventis mapped to LVGL keys feeding an oxivglKeypadState
(run_app_nav_keypad_events), routed to the view's focus group. Per-board
bring-up isboard::lvgl_bringup(display + the rightInput); on CoreS3 the
one I2C bus resets the panel and drives touch. The previous raw-FFI
Fire27-only keypad glue is replaced by this unified, both-boards path.
CoreS3 additionally runs a direct-touch POINTER indev (oxivgl 0.5
PointerIndev, fed by an async FT6336U poll task bridging the I2C read into
the indev's sync read callback), so on-screen widgets can be tapped by
coordinate — the bottom-strip keys stay on the BSP button API
(TouchButtons, multi-tap / long-press,multi_tap_mstuned to 150 ms for
snappy focus-nav) and taps land anywhere on screen (#32 I3). - Adopted oxivgl 0.5 (from crates.io, no git pin) for the
lvglbin:
KeypadIndev/KeypadState+ the newPointerIndev/PointerState. - New
sdbin (gated by--features sd): the one demo exercising
board::spi2::finish()— the display + SD shared bus including the CoreS3
GPIO35 MISO/DC mux. Mounts the FAT filesystem read-only and lists the root
dir; handles MBR-partitioned cards (mounts the first FAT partition via a
StreamSlice) and superfloppies (FAT at sector 0). Pulls thesdspi+
embedded-fatfsfork (not on crates.io → example-only). - Bumped the esp-hal fork pin (example/local builds only — the published
library uses stock esp-hal=1.1.1) tob7a4c74a, which scopes the I2C
NACK-recovery skip to ESP32 so the ESP32-S3 shared I2C bus recovers after a
NACK again. Without it a single NACK (e.g. probing an absent address) could
poison every later transaction on the S3 bus — manifesting as dead CoreS3
touch or a cold-boot black screen. Surfaced downstream inalternator-regulator.