RP2040 rust template showcase
Let’s build the embedded equivalent of “Hello World” - a blinking LED program for the RP2040 using Rust. We’ll use the rp2040-project-template
which comes with defmt logging, panic handling, and probe-rs debugging ready to go.
Template Features
- defmt logging - Fast, compact logging over RTT
- Stack overflow detection -
flip-link
will catch stack overflows - Better panic handling - Get actual panic info over the debug probe
- Fast iteration - probe-rs gives you quick flash/debug cycles
Setup
You’ll need:
- Rust toolchain (get it from rustup)
- An RP2040 board (like the Raspberry Pi Pico)
- Optionally: A debug probe (another Pico flashed with probe-rs firmware works fine)
Install the required tools:
# ARM Cortex-M0+ compiler target
rustup target install thumbv6m-none-eabi
# Stack overflow detection
cargo install flip-link
Debugging tools
-
probe-rs-debugger
Step 1 - Install Visual Studio Code from https://code.visualstudio.com/Step 2 - Install
probe-rs
$ cargo install --locked probe-rs-tools
Step 3 - Open this project in VSCode
Step 4 - Install
debugger for probe-rs
via the VSCode extensions menu (View > Extensions)Step 5 - Launch a debug session by choosing
Run
>Start Debugging
(or press F5)
Or if you don’t want to use a debugger. You can upload it over USB after holding down the BOOTSEL
button when attaching the usb cable.
-
Loading a UF2 over USB
Step 1 - Installelf2uf2-rs
:$ cargo install elf2uf2-rs --locked
Step 2 - Modify
.cargo/config
to change the default runner[target.`cfg(all(target-arch = "arm", target_os = "none"))`] runner = "elf2uf2-rs -d"
Grab the template:
git clone https://github.com/rp-rs/rp2040-project-template
The Code
Here’s our blink program (src/main.rs
):
#![no_std]
#![no_main]
use bsp::entry;
use defmt::*;
use defmt_rtt as _;
use embedded_hal::digital::v2::OutputPin;
use panic_probe as _;
use rp_pico as bsp;
use bsp::hal::{
clocks::{init_clocks_and_plls, Clock},
pac,
sio::Sio,
watchdog::Watchdog,
};
#[entry]
fn main() -> ! {
// Grab singleton instances of peripherals
let mut pac = pac::Peripherals::take().unwrap();
let core = pac::CorePeripherals::take().unwrap();
let mut watchdog = Watchdog::new(pac.WATCHDOG);
let sio = Sio::new(pac.SIO);
// Configure system clocks
// External crystal on the Pico is 12MHz
let clocks = init_clocks_and_plls(
12_000_000u32, // External xtal frequency
pac.XOSC, // Crystal oscillator
pac.CLOCKS, // Clock configuration
pac.PLL_SYS, // System PLL (can go up to 133MHz)
pac.PLL_USB, // USB PLL
&mut pac.RESETS,
&mut watchdog,
).ok().unwrap();
// Set up the delay provider using SYST
let mut delay = cortex_m::delay::Delay::new(
core.SYST,
clocks.system_clock.freq().to_Hz()
);
// GPIO initialization
let pins = bsp::Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
// LED is on pin 25 (onboard LED for Pico)
let mut led_pin = pins.led.into_push_pull_output();
loop {
info!("high"); // This shows up in defmt output
led_pin.set_high().unwrap();
delay.delay_ms(500);
info!("low");
led_pin.set_low().unwrap();
delay.delay_ms(500);
}
}
Code Breakdown
Let’s break down the important bits:
-
#![no_std]
- We’re running on bare metal, no standard library -
#![no_main]
- Using our own entry point, not the standard Rust one - Peripherals - We get single-instance access to hardware:
-
pac::Peripherals
- All RP2040-specific peripherals -
CorePeripherals
- ARM Cortex-M0+ core peripherals -
Watchdog
- Required for clock setup -
Sio
- Single-cycle I/O (fast GPIO access)
-
- Clock Setup - Configure system from the 12MHz crystal to:
- System clock at default speed (125MHz)
- USB clock at 48MHz
- Other peripherals get their required clocks
- GPIO - Set up pin 25 (the onboard LED) as a push-pull output
- Main Loop - Toggle LED every 500ms and log state changes
Running It
- Connect your debug probe
- Hook up the target board
- Run:
cargo run --release
Expected behavior: You’ll see the LED blink and get debug output:
└─ high
└─ low
└─ high
└─ low
Acknowledgements
This project uses the rp2040-project-template from the rp-rs organization. The template includes:
probe-rs for debugging and flashing defmt for efficient debug logging flip-link for stack overflow detection panic-probe for better panic handling
Special thanks to creators and maintainers of the tools above as well as to the contributors who built and maintain this template. Their work has created an excellent foundation for RP2040 development in Rust. The template is dual-licensed under MIT/Apache-2.0.
Enjoy Reading This Article?
Here are some more articles you might like to read next: