Bare-metal (no_std) Rust on a Raspberry Pi Pico 2W microcontroller with USB Serial Communication
This project is a bare-metal Rust implementation for the Raspberry Pi Pico 2W (RP2350 processor) featuring:
- USB Serial Communication: Custom Modbus-inspired framing protocol for reliable device communication
- Async/Await Programming: Using Embassy async runtime for efficient embedded systems
- GPIO Control: LED chase pattern demonstration with configurable timing
- Zero Standard Library: Complete
no_stdimplementation optimized for embedded constraints
The project showcases advanced embedded Rust concepts including async executors, USB device communication, and hardware abstraction layers while maintaining memory safety without runtime overhead.
- ๐น Bare-metal Programming โ Master
no_stdRust and embedded systems fundamentals - ๐น Async Embedded Development โ Learn Embassy framework for async/await in embedded contexts
- ๐น Hardware Abstraction โ Understand GPIO, USB, and peripheral control using
embassy-rpHAL - ๐น Communication Protocols โ Implement custom framing protocol with CRC-16 error checking
- ๐น Memory Safety โ Leverage Rust's ownership system in resource-constrained environments
| Category | Tools / Technologies |
|---|---|
| Language | Rust (Edition 2024, no_std) |
| Frameworks | embassy (async executor), embassy-rp (RP2350 HAL) |
| Tools | probe-rs, cargo-embed, defmt (logging) |
| Hardware | Raspberry Pi Pico 2W (RP2350A), USB Serial |
| Protocol | Custom Modbus-inspired framing with CRC-16/Modbus |
src/
โโโ main.rs # Entry point with command loop and USB handling
โโโ protocol.rs # Frame parser and builder (Modbus-inspired protocol)
โโโ serial_usb.rs # USB Serial communication layer
โโโ chase.rs # LED chase pattern demo
โโโ sys.rs # System initialization helpersThe project implements a custom framing protocol for reliable transport-agnostic communication:
Frame Structure:
[ STX | LEN | ADDR | CMD | <PAYLOAD...> | CRCL | CRCH ]- STX: Start marker (
0xA5) for frame synchronization - LEN: Payload length (includes ADDR + CMD + data)
- ADDR: Device address (1 byte)
- CMD: Command/function code (1 byte)
- PAYLOAD: Variable data (0-253 bytes)
- CRC: CRC-16/Modbus checksum (little-endian)
Supported Commands:
0x01โ PING: Device health check0x02โ CHASE: Trigger LED chase pattern0x20โ GET_DEVICE_ID: Query unique device identifier
Response Types:
- ACK: Success response with status byte
- ERROR: Error response with error code
- DATA: Data response with byte count and payload
- USB Serial: Full-duplex communication over USB CDC-ACM
- GPIO Control: 5-pin LED chase sequence (pins 0-4)
- Async Runtime: Embassy executor enables concurrent tasks without blocking
-
Rust Toolchain
Install from https://rustup.rs/ -
Target Architecture
Add RP2350 ARM Cortex-M33 target:rustup target add thumbv8m.main-none-eabihf
-
Debugging Tools (Optional)
- Install
probe-rs:cargo install probe-rs-tools - Install
cargo-embed: Built into probe-rs suite
- Install
The project uses a custom build script that automatically configures the target based on .pico-rs file:
# Build for RP2350 (Pico 2W default)
cargo build --release
# Build for RP2040 (legacy Pico)
echo "rp2040" > .pico-rs
cargo build --release
# Build for RP2350 RISC-V core
echo "rp2350-riscv" > .pico-rs
cargo build --releaseBuild Profiles:
devโ Fast compilation, larger binaries, debug symbolsreleaseโ Optimized for size and speed (recommended for deployment)
-
Hold BOOTSEL button while connecting USB
-
Device mounts as mass storage
-
Copy
.uf2binary to mounted drive:# Convert ELF to UF2 (if needed) elf2uf2-rs target/thumbv8m.main-none-eabihf/release/embedded-systems firmware.uf2
# Flash and attach debugger
cargo embed --release
# Or use probe-rs directly
probe-rs run --chip RP2350 target/thumbv8m.main-none-eabihf/release/embedded-systemsThe device communicates over USB Serial (CDC-ACM). Use the Python client in tools/serial_client/:
cd tools/serial_client
conda env create -f serial_client_env.yml
conda activate serial_clientSend PING (0x01):
python serial_client.py -c 0x01Trigger Chase Pattern (0x02):
python serial_client.py -c 0x02Get Device ID (0x20):
python serial_client.py -c 0x20For the LED chase demo, connect LEDs (with appropriate resistors) to:
- GPIO 0-4 โ Anode (positive)
- Ground โ Cathode (negative)
Chase Pattern: Each LED illuminates sequentially for 100ms with 100ms intervals.
Bare-metal systems require removal of std library To ensure proper global initialisation, Rust's default 'main' call must also be overwriten by:
- #[embassy_executor::main] for embassy (async environment) OR
- #[hal::entry] for bare-metal (blocking)
- Heapless Data Structures: All buffers use compile-time fixed sizes (
heapless::Vec) - Zero Dynamic Allocation: No heap allocator required
- Static Resources: USB buffers and peripherals use
static_cellfor lifetime management
- CRC-16 Error Detection: Modbus polynomial ensures data integrity
- Frame Resynchronization: Parser recovers from transmission errors by scanning for STX markers
- Type-Safe Peripherals: Rust ensures exclusive access to hardware resources at compile time
- Non-blocking I/O: USB reads/writes don't block the executor
- Concurrent Tasks: Spawner enables multiple async tasks (USB, timers, GPIO)
- Zero-cost Abstractions: Embassy compiles to efficient state machines
EmbeddedRustSystems/
โโโ .cargo/ # Cargo configuration (target defaults, runner)
โโโ docs/ # Documentation (build guides, etc.)
โโโ embassy_examples/ # Example code from Embassy framework (66 files)
โโโ src/ # Main source code
โ โโโ main.rs # Application entry point
โ โโโ protocol.rs # Frame protocol implementation
โ โโโ serial_usb.rs # USB Serial abstraction
โ โโโ chase.rs # LED chase pattern
โ โโโ sys.rs # System initialization
โโโ tools/ # Development tools
โ โโโ serial_client/ # Python USB Serial client
โโโ build.rs # Build script for linker configuration
โโโ Cargo.toml # Dependencies and build profiles
โโโ Embed.toml # probe-rs configuration
โโโ rp2350.x # Linker script for RP2350 ARM
โโโ rp2350_riscv.x # Linker script for RP2350 RISC-V
โโโ rp2040.x # Linker script for RP2040- Stepper Motor Control: Implement PWM-based stepper motor driver
- WiFi Integration: Enable CYW43 driver for wireless communication
- Advanced Protocols: Add support for I2C/SPI peripheral communication
- C++ Comparison: Port implementation to C++ for performance benchmarking
- Flash Storage: Persistent configuration using RP2350 flash memory
- Multi-device addressing (use ADDR field for bus communication)
- Interrupt-driven GPIO with debouncing
- Watchdog timer for fault recovery
- Power management and sleep modes
- The Rust Book โ Core Rust programming concepts
- The Embedded Rust Book โ Embedded development guide
- Embassy Documentation โ Async embedded framework
Olly Bayley
GitHub: @ombayley
This project is licensed under the GNU General Public License (GPL) โ See the LICENSE file for details. The GPL License is a copyleft license, that requires any derivative work to also be released under the GPL License. This means any derivative software that uses this code remains open-source and freely available to the public.