FREE Reverse Engineering Self-Study Course HERE
VIDEO PROMO HERE
Difficulty: Beginner
Date: Day 1 of 365
Components: LED, Resistor
Concepts: GPIO, Digital Output, Async Programming
This is the first project in the 365 Pico2 RP2350 Project Ideas series. We're implementing the classic "Hello World" of embedded systems: blinking an LED. This simple project introduces you to Embassy Rust on the Raspberry Pi Pico 2 and establishes the foundation for all future projects.
- Setting up an Embassy Rust project for RP2350
- Configuring GPIO pins as digital outputs
- Using Embassy's async/await for timing
- Understanding the embedded development workflow
- Using defmt for efficient embedded logging
- Working with probe-rs for flashing and debugging
Raspberry Pi Pico 2 w/ Header BUY
USB A-Male to USB Micro-B Cable BUY
Raspberry Pi Pico Debug Probe BUY
Complete Component Kit for Raspberry Pi BUY
10pc 25v 1000uF Capacitor BUY
| Quantity | Component | Notes |
|---|---|---|
| 1 | Raspberry Pi Pico 2 (RP2350) | |
| 1 | LED (any color) | Red, Yellow, Green, or White from kit |
| 1 | 100Ω Resistor | Current-limiting resistor |
| 1 | Breadboard | For prototyping |
| 2 | Jumper Wires | Male-to-Male |
┌─────────────────────────┐
│ Raspberry Pi Pico 2 │
│ │
│ ┌────────┐ │
│ │ GP16 ├─────┐ │ (or use GP15 for external LED)
│ └────────┘ │ │
│ │ │
│ ┌────────┐ │ │
│ │ GND ├──┐ │ │
│ └────────┘ │ │ │
└──────────────┼──┼───────┘
│ │
│ └──[100Ω]──┬──[LED]──┐
│ │ + │
└─────────────┴─────────┘
│ - │
└─────────┘
Connection Steps:
1. GP16 (Pin 16) → 100Ω Resistor → LED Anode (longer leg, +)
2. LED Cathode (shorter leg, -) → GND
Note: The Pico 2 has a built-in LED on GP16, so you can skip
the external circuit initially and just run the code!
- GP16: Built-in LED on Pico 2 (perfect for testing)
- GP15: Alternative GPIO pin for external LED
- GND: Ground connection (any GND pin works)
- Rust Toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- ARM Cortex-M Target
rustup target add thumbv8m.main-none-eabihf
- probe-rs (for flashing and debugging)
cargo install probe-rs-tools --locked
- flip-link (stack overflow protection)
cargo install flip-link
All dependencies are specified in Cargo.toml:
- embassy-executor: Async task executor for embedded systems (git version)
- embassy-time: Time and timer abstractions (git version)
- embassy-rp: Hardware Abstraction Layer (HAL) for RP2350 with
rp235xachip feature (git version for full RP2350 support) - cortex-m: Low-level Cortex-M utilities
- defmt: Efficient logging framework for embedded systems (version 1.0)
- defmt-rtt: RTT transport for defmt logging (version 1.0)
- panic-probe: Panic handler for debugging
Important Note: We're using git versions of the Embassy framework because the crates.io releases don't yet have full RP2350 support. The RP2350 uses ARMv8-M architecture with different MPU registers than earlier chips. We specifically enable the
rp235xafeature for Pico 2 (RP2350-A revision) andcritical-section-implfor proper interrupt handling.
DAY001/
├── Cargo.toml # Project dependencies and configuration
├── build.rs # Build script for linker configuration
├── memory.x # Memory layout for RP2350
├── .cargo/
│ └── config.toml # Target and runner configuration
├── src/
│ └── main.rs # Main application code
└── README.md # This file
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let mut led = Output::new(p.PIN_16, Level::Low);
let mut controller = LedController::new();
loop {
let state = controller.toggle();
if led_state_to_level(state) {
led.set_high();
} else {
led.set_low();
}
Timer::after_millis(controller.delay_ms()).await;
}
}- Hold the BOOTSEL button on the Pico 2
- Connect the Pico to your computer via USB
- Release the BOOTSEL button
- The Pico appears as a USB drive (RPI-RP2)
cd DAY001
cargo build --releaseThis compiles the code for the RP2350 target.
cargo run --releaseThis will:
- Compile the code
- Flash it to the Pico 2 using probe-rs
- Start RTT logging to show debug output
The LED will blink: ON for 0.5 second, OFF for 0.5 second, repeating forever.
make testThis will run all of the unittests to ensure our project is functioning correctly and that recent changes haven't introduced regressions.
Solution: Install probe-rs tools
cargo install probe-rs-tools --lockedSolution:
- Ensure BOOTSEL was pressed during connection
- Try a different USB cable (some are power-only)
- Check USB permissions on Linux:
sudo usermod -a -G plugdev $USER
Solution: Add the ARM target
rustup target add thumbv8m.main-none-eabihfSolutions:
- Check the wiring (resistor and polarity)
- Verify you're using the correct GPIO pin
- Try the built-in LED first (GP16, no wiring needed)
- Check if the LED is functional (test with a battery)
Solution: Install flip-link
cargo install flip-linkEmbassy is a modern async framework for embedded Rust. It provides:
- Async/await: Write concurrent code that looks sequential
- Efficient timers: Hardware-accelerated timing
- Type safety: Rust's compile-time guarantees for embedded systems
- Low power: Efficient task scheduling and sleep modes
Traditional embedded code uses blocking delays:
delay_ms(1000); // CPU does nothing for 1 secondEmbassy's async approach:
Timer::after_millis(1000).await; // CPU can do other tasksThis allows multiple tasks to run concurrently on a single core!
- Digital Output: Pin is either HIGH (3.3V) or LOW (0V)
- Current Limit: RP2350 pins source ~12mA max
- Resistor: Limits LED current to safe levels
- LED forward voltage: ~2V
- Desired current: ~10mA
- Resistor: (3.3V - 2V) / 0.01A = 130Ω (100Ω works fine)
defmt is a highly efficient logging framework:
- Zero-cost abstractions
- Formatting happens on the host computer, not the microcontroller
- Minimal flash and RAM usage
- Perfect for embedded systems
Modify the delay values in main.rs:
// Fast blink (0.2 seconds)
Timer::after_millis(200).await;
// Slow blink (3 seconds)
Timer::after_millis(3000).await;Create an SOS pattern:
// S: three short blinks
for _ in 0..3 {
led.set_high();
Timer::after_millis(200).await;
led.set_low();
Timer::after_millis(200).await;
}
// O: three long blinks
for _ in 0..3 {
led.set_high();
Timer::after_millis(600).await;
led.set_low();
Timer::after_millis(200).await;
}
// S: three short blinks
for _ in 0..3 {
led.set_high();
Timer::after_millis(200).await;
led.set_low();
Timer::after_millis(200).await;
}
Timer::after_millis(2000).await; // Pause between SOSChange the pin in main.rs:
let mut led = Output::new(peripherals.PIN_16, Level::Low);Wire an external LED to GP16.
In .cargo/config.toml, change:
DEFMT_LOG = "debug" # More verbose
# or
DEFMT_LOG = "warn" # Less verbose- Heartbeat Pattern: Make the LED pulse like a heartbeat
- Multiple Speeds: Use a pattern that cycles through different blink speeds
- Binary Counter: Use 4 LEDs to count in binary (preview of Day 7)
- Morse Code: Transmit your name in Morse code (preview of Day 10)
- Hardware assembled correctly
- Rust toolchain installed
- probe-rs and flip-link installed
- Project builds without errors
- LED blinks at 1Hz (1 second on, 1 second off)
- RTT logging displays in terminal
- Experimented with different blink speeds
- Understood async/await in Embassy
- Ready to move to DAY002
Congratulations! 🎉 You've completed your first Embassy Rust project on the Pico 2. This simple blink is the foundation for all 365 projects ahead. The skills you learned today—GPIO control, async programming, and the build/flash workflow—will be used throughout this journey.
⭐ DAY002: Blink LEDs in Sequence HERE
MIT License - Copyright (c) 2025