From 1f88150d038bb228015b85da56a1a763d908eace Mon Sep 17 00:00:00 2001 From: Mathew Lewis Date: Tue, 25 Aug 2026 13:00:00 -0700 Subject: [PATCH] Rust blinky for the RP2040 Pico, flashing the digits of pi Flashes the on-board LED as 3, 1, 4, 1, 5 with pauses between digits and a longer pause before repeating. The pattern is stepped from the hardware timer rather than blocking sleeps, so the USB serial port stays responsive and can reset the board back into BOOTSEL on request. Includes the original plain 250 ms blink under alt/ and prebuilt .uf2 images. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FrCPgv2NGSEcyuN9KKQKHc --- .cargo/config.toml | 5 ++ .gitignore | 1 + Cargo.toml | 19 ++++++ README.md | 75 ++++++++++++++++++++++ alt/simple-blink.rs | 47 ++++++++++++++ build.rs | 14 +++++ memory.x | 12 ++++ src/main.rs | 146 +++++++++++++++++++++++++++++++++++++++++++ uf2/pi-blink-usb.uf2 | Bin 0 -> 25088 bytes uf2/simple-blink.uf2 | Bin 0 -> 4608 bytes 10 files changed, 319 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 alt/simple-blink.rs create mode 100644 build.rs create mode 100644 memory.x create mode 100644 src/main.rs create mode 100644 uf2/pi-blink-usb.uf2 create mode 100644 uf2/simple-blink.uf2 diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..fe4afce --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +target = "thumbv6m-none-eabi" + +[target.thumbv6m-none-eabi] +rustflags = ["-C", "link-arg=--nmagic", "-C", "link-arg=-Tlink.x"] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e9e1166 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "blinky" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1.0" +panic-halt = "1.0" +rp-pico = "0.9" +usb-device = "0.3" +usbd-serial = "0.2" + +[profile.release] +codegen-units = 1 +lto = true +opt-level = "z" +debug = 2 diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d0a072 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# pico-blinky + +Rust firmware for the Raspberry Pi Pico. Flashes the on-board LED with the +digits of pi: three flashes, a pause, one flash, a pause, four, one, five, +then a longer pause before it repeats. + +## Board + +This targets the **RP2040**, the original Pico. It will not run on a Pico 2. +Check which one you have by holding BOOTSEL while plugging it in and looking +at the volume that mounts: + +| Volume label | Chip | This firmware | +|--------------|---------|---------------| +| `RPI-RP2` | RP2040 | yes | +| `RP2350` | RP2350 | no | + +## Build + +``` +rustup target add thumbv6m-none-eabi +cargo install elf2uf2-rs --locked +cargo build --release +elf2uf2-rs target/thumbv6m-none-eabi/release/blinky pi-blink.uf2 +``` + +## Flash + +Hold the BOOTSEL button down **before** the cable goes in, keep it held for a +second after, then release. The button is only read at power-on, so pressing +it while the board is already running does nothing. + +The board mounts as `RPI-RP2`. Copy the `.uf2` onto it. The volume ejects +itself once the write completes, which is the bootloader confirming it took +the image and jumped to it. + +Prebuilt images are in `uf2/` if you just want to drag one across. + +## Getting back into the bootloader + +The firmware exposes a USB serial port, so after the first flash you do not +need the button again. Either: + +- send `b` to the serial port, or +- open the port at 1200 baud and drop DTR, the convention `picotool` and the + Arduino IDE use + +Both call `reset_to_usb_boot`, and the board comes back up as `RPI-RP2`. + +The LED pattern is driven from the hardware timer rather than blocking sleeps, +so USB stays responsive while the sequence runs. + +## Layout + +``` +src/main.rs pi digits + USB reboot-to-bootloader +alt/simple-blink.rs plain 250 ms on/off blink, the first version +memory.x flash/RAM layout, reserves 0x100 for the boot2 stage +build.rs puts memory.x on the linker search path +uf2/ prebuilt images +``` + +## Timing + +Edit the constants at the top of `src/main.rs`: + +```rust +const FLASH_ON_US: u64 = 150_000; +const FLASH_OFF_US: u64 = 200_000; +const DIGIT_GAP_US: u64 = 800_000; +const CYCLE_GAP_US: u64 = 3_000_000; +``` + +`DIGIT_GAP_US` is the one worth raising if the sequence is hard to count by +eye, since 1 and 4 sit next to each other and can run together. diff --git a/alt/simple-blink.rs b/alt/simple-blink.rs new file mode 100644 index 0000000..8bf009b --- /dev/null +++ b/alt/simple-blink.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use cortex_m::delay::Delay; +use embedded_hal::digital::OutputPin; +use panic_halt as _; +use rp_pico::entry; +use rp_pico::hal::{clocks::init_clocks_and_plls, pac, watchdog::Watchdog, Clock, Sio}; + +#[entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let clocks = init_clocks_and_plls( + rp_pico::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + let sio = Sio::new(pac.SIO); + let pins = rp_pico::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let mut led = pins.led.into_push_pull_output(); + + loop { + led.set_high().unwrap(); + delay.delay_ms(250); + led.set_low().unwrap(); + delay.delay_ms(250); + } +} + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..8293639 --- /dev/null +++ b/build.rs @@ -0,0 +1,14 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + let out = &PathBuf::from(env::var_os("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"); +} diff --git a/memory.x b/memory.x new file mode 100644 index 0000000..e70f434 --- /dev/null +++ b/memory.x @@ -0,0 +1,12 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} +EXTERN(BOOT2_FIRMWARE) +SECTIONS { + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3983dac --- /dev/null +++ b/src/main.rs @@ -0,0 +1,146 @@ +#![no_std] +#![no_main] + +use embedded_hal::digital::OutputPin; +use panic_halt as _; +use rp_pico::entry; +use rp_pico::hal::{ + clocks::init_clocks_and_plls, pac, rom_data, timer::Timer, usb::UsbBus, watchdog::Watchdog, + Sio, +}; +use usb_device::class_prelude::UsbBusAllocator; +use usb_device::prelude::*; +use usbd_serial::SerialPort; + +/// 3.1415, flashed one digit at a time. +const DIGITS: [u8; 5] = [3, 1, 4, 1, 5]; + +const FLASH_ON_US: u64 = 150_000; +const FLASH_OFF_US: u64 = 200_000; +const DIGIT_GAP_US: u64 = 800_000; +const CYCLE_GAP_US: u64 = 3_000_000; + +/// Where the blink pattern currently is. Stepped from the timer rather than +/// slept through, so USB stays responsive while the LED runs. +enum Phase { + On, + Off, + DigitGap, + CycleGap, +} + +#[entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let clocks = init_clocks_and_plls( + rp_pico::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let timer = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + let sio = Sio::new(pac.SIO); + let pins = rp_pico::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + let mut led = pins.led.into_push_pull_output(); + + let usb_bus = UsbBusAllocator::new(UsbBus::new( + pac.USBCTRL_REGS, + pac.USBCTRL_DPRAM, + clocks.usb_clock, + true, + &mut pac.RESETS, + )); + let mut serial = SerialPort::new(&usb_bus); + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x2e8a, 0x000a)) + .strings(&[StringDescriptors::default() + .manufacturer("Raspberry Pi") + .product("Pi Blinker") + .serial_number("PIBLINK")]) + .unwrap() + .device_class(usbd_serial::USB_CLASS_CDC) + .build(); + + let mut digit = 0usize; + let mut flashes_left = DIGITS[0]; + let mut phase = Phase::On; + let mut next_change = timer.get_counter().ticks() + FLASH_ON_US; + let mut was_dtr = false; + led.set_high().unwrap(); + + loop { + // USB first, so a reboot request is never more than a poll away. + if usb_dev.poll(&mut [&mut serial]) { + let mut buf = [0u8; 64]; + if let Ok(n) = serial.read(&mut buf) { + for &b in &buf[..n] { + if b == b'b' || b == b'B' { + rom_data::reset_to_usb_boot(0, 0); + } + } + } + } + + // 1200-baud touch, the convention picotool and the Arduino IDE use: + // host opens the port at 1200 baud then drops DTR. + let dtr = serial.dtr(); + if was_dtr && !dtr && serial.line_coding().data_rate() == 1200 { + rom_data::reset_to_usb_boot(0, 0); + } + was_dtr = dtr; + + let now = timer.get_counter().ticks(); + if now < next_change { + continue; + } + + match phase { + Phase::On => { + led.set_low().unwrap(); + phase = Phase::Off; + next_change = now + FLASH_OFF_US; + } + Phase::Off => { + flashes_left -= 1; + if flashes_left > 0 { + led.set_high().unwrap(); + phase = Phase::On; + next_change = now + FLASH_ON_US; + } else if digit + 1 < DIGITS.len() { + phase = Phase::DigitGap; + next_change = now + DIGIT_GAP_US; + } else { + phase = Phase::CycleGap; + next_change = now + CYCLE_GAP_US; + } + } + Phase::DigitGap => { + digit += 1; + flashes_left = DIGITS[digit]; + led.set_high().unwrap(); + phase = Phase::On; + next_change = now + FLASH_ON_US; + } + Phase::CycleGap => { + digit = 0; + flashes_left = DIGITS[0]; + led.set_high().unwrap(); + phase = Phase::On; + next_change = now + FLASH_ON_US; + } + } + } +} diff --git a/uf2/pi-blink-usb.uf2 b/uf2/pi-blink-usb.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..29bcf04d54cd7e38147d7fd0f372d57ab6c2d366 GIT binary patch literal 25088 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 Op8Wsa|MeQc`2Pn;{PcPN literal 0 HcmV?d00001 diff --git a/uf2/simple-blink.uf2 b/uf2/simple-blink.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..abdb0fec4109efc763bf6092bea3d7e688d18e95 GIT binary patch literal 4608 zcmd^CU2Gdg5T3m|+h?~Fn}%Sg3Ea7@6w?9?|2!a_?Q%IM1*H;!76DzS#l0jb2~ZG1 zAUUl-p#@=skZebQMIYz`QlP2}4+LC<00Bbh&_a2Ei&T*Zgg8iroz&%+J>yWR65S?{;>sU%lRU@Ad~?2MJj5192pHQQTu6Is>Kadu3?{&FP#x5zo=nG??6x zytWtQJmut_Ng`X}eBY)-h!SkMNdjwYjSFXDErweTrNFwe78)U}CIwS%sBcD{{aiTm zx*Czi*g}QbT89=u-bvdM>onrn`Wa^x9UGU`*of9d%ilfZ1Ly<;x-Zk#CPd1Ao}L#c z#BRQn9}*-IJ7*C5uT}VIt`tlKQ+D-tJ2(;?;idsN1-BIh@Br|;Kn??(MLjG*5%=vE ze*qKwm&(4QcRjVFEW^r{(RY3Q7xIr7F#g#qu*5$I(g|#zb%URX*#9qujcInX`v2hp zE4wIa-UNilW5f{3=jJ?`Cn5HX$2dd$LM$)Nfpya&{DAA01bmoekA!;-^}I5Q^<`Tw z;-72q&*6@n{7;JFj6bzKWzRK!+eiJQW8Gt=wIg?r^pCcV+EvS5o4PyIzqfVo*mPN4 zjhL}HnN;OJ&T-&1(j}NW_zp;C?n-yMjIN*+ChHINK}Qw&d1kSE^v7+6EdDz0Pd|EGc3O|jimz@QOV9f!zSX`do{PP#^ z&+zz6`y4TyUtpL%KD%1}M1w3?^#vj0oec6H_XPCucz|Z~vQR(uk#>#DX9@M>qm*2= ziaryLQBId7F0()WzQ$3X4p-Mtb;$|7)RUzFU6e?M%{KMi68=r>neQXo+jmA(u#P$2 zZ=Mawov3}d;Ldmpef;6x5c&?`?UY8}Z)$wL>Dfp3IG(x5LcHnKmG|=Fl<1_8jT8E! zM$ppX1%h{pZ#^$2hoj{)NtZg`qO8f%T$;)mc-> zG31_ev|mhVkRc}x%1Lj*)H6#gn!35(^$KIv)csB?hql-_AqUo8XZF|TC92!8w#(5! zHGOJvh}kO+y*WUKMiZh!QM&RIaXR45o#5z@kQK4c`rWyJ%D)U&bb&807T+@I@P5YP z@W5_qnBrUsC7a8nkQY(1zWWmH+(Zm!UNYUdlCb0Xc~g4W*~i239&>nmk6$r&o5QZc ziaP(U2LG-L_(whw2cdur?v@Z6hL{%-d!*8-vUAB}eYjE_+=_@5laM2~o=449$i$1u z;vkz>OtKUEYpv|L*SK9Hx)Ao^iNwgk-LOs8NnEC~E`0slAD$|@#x1~y9~%m3EB2cU2Yyt4?K(=KI88k82+ex1G(%Ag2o?0ez4 zZlTO6cGb4oaK8zrP$tS(3Q^cSZ&$@XnFLF%HT0U@l=YHpYFvaYkxAAkJiDe68F=S~xp3@3uh}P0)=I(@*?ry$h%jY&O$C2IVBV05nsWr6um|a#GM@EU8B4L*6 zT_SruUj56XuqmK&2HDhF;pEl5?N5F*zw4zJ&@U$$(|$$ldGvUT4x9N3VD>O@ygC;e zBeJF#X!8!ViCdGNCg9|oCysIv(Qx6nfp1Jmkg$*9t8WY(Ph%e29EWn*mW%Q~c8>9& ni@4)v{@*wgCr>J|OQAe_#=#