Rust port Stage 3 milestone 1: pm-kit boot-proof blink (RP2350)

First per-board binary. rust/pm-kit/ is a minimal rp235x-hal firmware that blinks
GP25 on the Pico 2 — proves the toolchain, RP2350 boot block (ImageDef), memory
layout, and flash before we add any drivers.

- src/main.rs + memory.x + build.rs + .cargo/config.toml: rp235x-hal blink, builds
  for thumbv8m.main-none-eabihf.
- build.sh + uf2.py: one command builds the ELF in the container, objcopies to a raw
  image, and packs pm-kit.uf2 (rp2350-arm-s family). Drag onto the Pico 2 in BOOTSEL.

Verified: builds clean; produces a valid 6-block UF2. Runtime (does it blink?) is the
on-device check. Next: drivers (display first) + link pm-core.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Me Here 2026-05-31 20:34:46 -05:00
parent 400d896518
commit 0e224393f7
9 changed files with 199 additions and 1 deletions

View file

@ -81,7 +81,13 @@ this timeline against the wall clock.
**Also done:** the crate is now `#![no_std]` + `alloc` and **builds for the RP2350 target**
(`cargo build --lib --target thumbv8m.main-none-eabihf`) — the codec + scheduler are firmware-ready.
### Stage 3 — drivers (hardware)
### Stage 3 — drivers (hardware) 🔧 IN PROGRESS (`rust/pm-kit/`)
**Milestone 1 (boot-proof) built:** `rust/pm-kit/` is a minimal RP2350 binary (rp235x-hal) that
blinks GP25 — compiles for the target and packs to `pm-kit.uf2` via `./rust/pm-kit/build.sh`.
Confirms toolchain + RP2350 boot block + flash before any drivers. Once it blinks on the Pico 2
we add drivers (display first) and link `pm-core`. HAL choice (rp235x-hal vs embassy) finalizes
with the first real driver.
On `embassy` / `rp-hal`:
- ST7789 240×320 display → `mipidsi` + `embedded-graphics` (mature; the parts are well-supported).
- I²S to the PCM5102A → RP2350 PIO.

View file

@ -0,0 +1,8 @@
[build]
target = "thumbv8m.main-none-eabihf"
[target.thumbv8m.main-none-eabihf]
rustflags = [
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Tlink.x",
]

4
rust/pm-kit/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
target/
Cargo.lock
*.uf2
*.bin

16
rust/pm-kit/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "pm-kit"
version = "0.1.0"
edition = "2021"
description = "PM_K-1 firmware (RP2350 / Pico 2). Stage 3 bring-up: boot-proof blink, then drivers + pm-core."
[dependencies]
rp235x-hal = { version = "0.3", features = ["binary-info", "critical-section-impl", "rt"] }
cortex-m-rt = "0.7"
panic-halt = "1"
embedded-hal = "1"
[profile.release]
opt-level = "s"
lto = true
debug = 2

16
rust/pm-kit/build.rs Normal file
View file

@ -0,0 +1,16 @@
//! Put `memory.x` on the linker search path (cortex-m-rt's link.x INCLUDEs it).
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=memory.x");
println!("cargo:rerun-if-changed=build.rs");
}

23
rust/pm-kit/build.sh Executable file
View file

@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Build pm-kit for the RP2350 and produce pm-kit.uf2.
# Flash: hold BOOTSEL on the Pico 2, plug in USB, drag pm-kit.uf2 onto the RP2350 drive.
#
# ./build.sh
#
# Override the runtime with RUNTIME=docker ./build.sh
set -euo pipefail
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # rust/pm-kit
REPO="$(cd "$DIR/../.." && pwd)" # repo root
RUNTIME="${RUNTIME:-podman}"
IMG="pm-rust:1"
"$RUNTIME" run --rm -v "$REPO":/work:Z -w /work/rust/pm-kit "$IMG" bash -c '
set -e
rustup component add llvm-tools-preview >/dev/null 2>&1 || true
cargo build --release
OBJCOPY="$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n "s/host: //p")/bin/llvm-objcopy"
"$OBJCOPY" -O binary target/thumbv8m.main-none-eabihf/release/pm-kit pm-kit.bin
'
python3 "$DIR/uf2.py" "$DIR/pm-kit.bin" "$DIR/pm-kit.uf2"
echo "$DIR/pm-kit.uf2 (hold BOOTSEL on the Pico 2, drag this onto the RP2350 drive)"

41
rust/pm-kit/memory.x Normal file
View file

@ -0,0 +1,41 @@
/* RP2350 (Pico 2) memory layout for rp235x-hal + cortex-m-rt.
The bootrom requires the IMAGE_DEF (.start_block) right after the vector table. */
MEMORY {
FLASH : ORIGIN = 0x10000000, LENGTH = 4096K
RAM : ORIGIN = 0x20000000, LENGTH = 512K
}
SECTIONS {
/* ### RP2350 image definition block — the bootrom scans for this to boot the image. */
.start_block : ALIGN(4)
{
__start_block_addr = .;
KEEP(*(.start_block));
KEEP(*(.boot_info));
} > FLASH
} INSERT AFTER .vector_table;
/* move .text after the start block */
_stext = ADDR(.start_block) + SIZEOF(.start_block);
SECTIONS {
/* picotool 'Binary Info' entries */
.bi_entries : ALIGN(4)
{
__bi_entries_start = .;
KEEP(*(.bi_entries));
. = ALIGN(4);
__bi_entries_end = .;
} > FLASH
} INSERT AFTER .text;
SECTIONS {
.end_block : ALIGN(4)
{
__end_block_addr = .;
KEEP(*(.end_block));
} > FLASH
} INSERT AFTER .bi_entries;
PROVIDE(start_to_end = __end_block_addr - __start_block_addr);
PROVIDE(end_to_start = __start_block_addr - __end_block_addr);

57
rust/pm-kit/src/main.rs Normal file
View file

@ -0,0 +1,57 @@
//! PM_K-1 firmware — Stage 3 bring-up milestone 1: prove our Rust boots on the Pico 2 (RP2350).
//!
//! Blinks the onboard LED (GP25). If it blinks, the whole path works: toolchain, RP2350 boot
//! block, memory layout, flash, and the cortex-m-rt entry. Drivers (display / audio / inputs /
//! USB-MIDI) and the `pm-core` engine come next, once boot is confirmed on hardware.
#![no_std]
#![no_main]
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use panic_halt as _;
use rp235x_hal as hal;
/// Image definition block — the RP2350 bootrom looks for this to boot the image.
#[link_section = ".start_block"]
#[used]
pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe();
const XTAL_FREQ_HZ: u32 = 12_000_000; // Pico 2 crystal
#[hal::entry]
fn main() -> ! {
let mut pac = hal::pac::Peripherals::take().unwrap();
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
let clocks = hal::clocks::init_clocks_and_plls(
XTAL_FREQ_HZ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks);
let sio = hal::Sio::new(pac.SIO);
let pins = hal::gpio::Pins::new(pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut pac.RESETS);
let mut led = pins.gpio25.into_push_pull_output();
loop {
led.set_high().unwrap();
timer.delay_ms(250);
led.set_low().unwrap();
timer.delay_ms(250);
}
}
/// picotool metadata (visible via `picotool info`).
#[link_section = ".bi_entries"]
#[used]
pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 1] =
[hal::binary_info::rp_program_name!(c"pm-kit blink")];

27
rust/pm-kit/uf2.py Normal file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env python3
"""Pack a raw RP2350 flash image (objcopy -O binary output) into a UF2.
Family rp2350-arm-s (0xe48bff59), flash base 0x10000000. Usage:
python3 uf2.py pm-kit.bin pm-kit.uf2
"""
import struct
import sys
BASE = 0x10000000
FAMILY = 0xE48BFF59 # rp2350-arm-s
src = sys.argv[1] if len(sys.argv) > 1 else "pm-kit.bin"
out = sys.argv[2] if len(sys.argv) > 2 else "pm-kit.uf2"
data = open(src, "rb").read()
chunks = [data[i:i + 256] for i in range(0, len(data), 256)] or [b""]
n = len(chunks)
with open(out, "wb") as f:
for i, c in enumerate(chunks):
c = c.ljust(256, b"\x00")
blk = struct.pack("<IIIIIIII", 0x0A324655, 0x9E5D5157, 0x00002000,
BASE + i * 256, 256, i, n, FAMILY)
blk += c + b"\x00" * (476 - 256) + struct.pack("<I", 0x0AB16F30)
assert len(blk) == 512
f.write(blk)
print(f"{out}: {n} blocks, {len(data)} bytes payload")