diff --git a/docs/rust-port.md b/docs/rust-port.md index 89c2368..295143c 100644 --- a/docs/rust-port.md +++ b/docs/rust-port.md @@ -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. diff --git a/rust/pm-kit/.cargo/config.toml b/rust/pm-kit/.cargo/config.toml new file mode 100644 index 0000000..910819a --- /dev/null +++ b/rust/pm-kit/.cargo/config.toml @@ -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", +] diff --git a/rust/pm-kit/.gitignore b/rust/pm-kit/.gitignore new file mode 100644 index 0000000..76f1255 --- /dev/null +++ b/rust/pm-kit/.gitignore @@ -0,0 +1,4 @@ +target/ +Cargo.lock +*.uf2 +*.bin diff --git a/rust/pm-kit/Cargo.toml b/rust/pm-kit/Cargo.toml new file mode 100644 index 0000000..cf2f974 --- /dev/null +++ b/rust/pm-kit/Cargo.toml @@ -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 diff --git a/rust/pm-kit/build.rs b/rust/pm-kit/build.rs new file mode 100644 index 0000000..b1d971f --- /dev/null +++ b/rust/pm-kit/build.rs @@ -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"); +} diff --git a/rust/pm-kit/build.sh b/rust/pm-kit/build.sh new file mode 100755 index 0000000..2cb19d1 --- /dev/null +++ b/rust/pm-kit/build.sh @@ -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)" diff --git a/rust/pm-kit/memory.x b/rust/pm-kit/memory.x new file mode 100644 index 0000000..8b694ec --- /dev/null +++ b/rust/pm-kit/memory.x @@ -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); diff --git a/rust/pm-kit/src/main.rs b/rust/pm-kit/src/main.rs new file mode 100644 index 0000000..f8bbd13 --- /dev/null +++ b/rust/pm-kit/src/main.rs @@ -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")]; diff --git a/rust/pm-kit/uf2.py b/rust/pm-kit/uf2.py new file mode 100644 index 0000000..02df88d --- /dev/null +++ b/rust/pm-kit/uf2.py @@ -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("