LED on Button Press
Let's build a simple project that turns on an LED whenever the button is pressed. This combines everything we've learned about GPIO input and output.
Hardware Setup
| Component | Pico Pin | Notes |
|---|---|---|
| Button (one side) | GPIO 15 | Input pin |
| Button (other side) | GND | Ground connection |
| LED Anode (via 330Ω) | GPIO 13 | Output pin |
| LED Cathode | GND | Ground connection |

Complete button + LED circuit
Button as Input
We configure GPIO 15 as an input with internal pull-up enabled. One side of the button connects to GPIO 15, the other to ground. When pressed, the pin is pulled LOW.
let button = Input::new(p.PIN_15, Pull::Up);
// Pull-up means:
// - Button not pressed = HIGH (pulled up to 3.3V)
// - Button pressed = LOW (connected to GND)LED as Output
We configure GPIO 13 as an output, starting in the LOW state (off). You can also use the onboard LED on the Pico 2 W, but that requires the cyw43 driver.
// External LED on GPIO 13
let mut led = Output::new(p.PIN_13, Level::Low);
// For Pico 2 W onboard LED, you need cyw43 driver
// (covered in WiFi chapter)Main Loop Logic
In the main loop, we constantly check if the button is pressed. When LOW (pressed), we turn on the LED for 3 seconds. Otherwise, the LED stays off. A small 5ms delay prevents overwhelming the system.
loop {
if button.is_low() {
// Button is pressed (LOW with pull-up)
defmt::info!("Button pressed");
led.set_high();
Timer::after_secs(3).await;
} else {
// Button not pressed
led.set_low();
}
Timer::after_millis(5).await;
}Debouncing: If you reduce the delay, you might notice that a single button press triggers multiple detections. This is called "button bounce" - the metal contacts briefly bounce when pressed, creating multiple electrical signals.
In this example, the 3-second LED delay effectively masks bounce issues. For applications where you need to count individual presses accurately, you'll need debouncing logic (typically 10-50ms delay after detecting a press).
Complete Code
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_rp::gpio::{Input, Level, Output, Pull};
use embassy_rp::{self as hal, block::ImageDef};
use embassy_time::Timer;
use panic_probe as _;
use defmt_rtt as _;
#[unsafe(link_section = ".start_block")]
#[used]
pub static IMAGE_DEF: ImageDef = hal::block::ImageDef::secure_exe();
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
// Button on GPIO 15 with pull-up
let button = Input::new(p.PIN_15, Pull::Up);
// LED on GPIO 13 (or use GPIO 25 for non-W Pico)
let mut led = Output::new(p.PIN_13, Level::Low);
loop {
if button.is_low() {
defmt::info!("Button pressed");
led.set_high();
Timer::after_secs(3).await;
} else {
led.set_low();
}
Timer::after_millis(5).await;
}
}Running the Program
# Create new project
cargo generate --git https://github.com/ImplFerris/pico2-template.git
# Build and flash
cargo run --release
# With debug probe
cargo embed --releasePress the button and the LED should turn on for 3 seconds. This demonstrates basic interactive embedded programming where hardware input controls hardware output.