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:
parent
400d896518
commit
0e224393f7
9 changed files with 199 additions and 1 deletions
|
|
@ -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**
|
**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.
|
(`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`:
|
On `embassy` / `rp-hal`:
|
||||||
- ST7789 240×320 display → `mipidsi` + `embedded-graphics` (mature; the parts are well-supported).
|
- ST7789 240×320 display → `mipidsi` + `embedded-graphics` (mature; the parts are well-supported).
|
||||||
- I²S to the PCM5102A → RP2350 PIO.
|
- I²S to the PCM5102A → RP2350 PIO.
|
||||||
|
|
|
||||||
8
rust/pm-kit/.cargo/config.toml
Normal file
8
rust/pm-kit/.cargo/config.toml
Normal 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
4
rust/pm-kit/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
target/
|
||||||
|
Cargo.lock
|
||||||
|
*.uf2
|
||||||
|
*.bin
|
||||||
16
rust/pm-kit/Cargo.toml
Normal file
16
rust/pm-kit/Cargo.toml
Normal 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
16
rust/pm-kit/build.rs
Normal 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
23
rust/pm-kit/build.sh
Executable 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
41
rust/pm-kit/memory.x
Normal 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
57
rust/pm-kit/src/main.rs
Normal 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
27
rust/pm-kit/uf2.py
Normal 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")
|
||||||
Loading…
Reference in a new issue