Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting Started With Embedded Rust 📟🦀

This is a free book about Embedded Rust on the Raspberry Pi Pico 2 (with the RP2350 microcontroller). We primarily use this internally at Systemscape, but we hope it can be useful to others as well and invite you to use it as well and contribute to it.

Introduction

Embedded Rust on the Raspberry Pi Pico 2 (RP2350) – Workshop Guide

Welcome to this workshop guide on Embedded Rust using the Raspberry Pi Pico 2 (with the RP2350 microcontroller). This guide is structured as an mdBook (a collection of chapters), meant to provide a comprehensive, hands-on learning experience. We will start from the basics – getting Rust set up and blinking an LED – and work up to more advanced topics like using hardware abstraction layers (HALs), interacting with sensors, and even writing asynchronous embedded code with the Embassy framework. Along the way, we’ll emphasize three key messages (our learning outcomes):

  • “Flashing Rust is easier than brewing coffee. ☕” – Getting Rust onto a chip is dead simple.
  • “Embedded Rust feels like Rust — not like C. 🤮” – Embedded Rust lets you use familiar Rust concepts and patterns.
  • “Scale without pain: async & tooling to keep you sane. 📈” – Modern Rust tooling (async runtimes, debuggers, CI) helps your projects grow without becoming a nightmare.

Who should use this guide?

This book is for anyone interested in embedded programming with Rust, from Rust developers who have never touched a microcontroller to embedded veterans curious about Rust. In particular, it’s written with two audiences in mind:

  • Experienced Rust developers (e.g. web/backend developers) who have little or no embedded systems experience. If you know Rust’s basics and have written and debugged Rust code on desktop, this guide will show you that you can quickly apply Rust to microcontrollers without “unlearning” anything.
  • Embedded systems developers who may be new to Rust. If you have familiarity with concepts like cross-compilation, microcontroller peripherals (GPIO, I2C, etc.), and tools like debuggers, this guide will demonstrate how Rust’s abstractions and tooling can make embedded development more robust and productive.

No prior embedded knowledge is strictly required – we will introduce concepts as needed – but basic Rust language knowledge (ownership, traits, async, etc.) will help you get the most out of it.

What you'll need (Prerequisites)

  • Hardware: For hands-on exercises, you will need a Raspberry Pi Pico 2 board (featuring the RP2350 microcontroller). This board was chosen for its modern capabilities and ease of use. You’ll also need some basic accessories: a USB cable to connect the Pico to your computer, and optionally a sensor or display module for later chapters (more on this below). A suggested sensor is the MCP9808 I²C temperature sensor (a cheap, widely available sensor) or a small I²C OLED display like the SSD1306 – we’ll use these to demonstrate hardware interactions. If you have a different I²C sensor or display, that’s fine too – the concepts will generalize.
  • Software: You should have a recent stable version of Rust installed (with rustup). We will install a few additional Rust targets and tools for cross-compiling to the ARM Cortex-M33 processor in the RP2350 and for flashing the device. All needed software is free and open-source.
  • Time & Effort: Working through the entire guide (including examples and exercises) will take around 3-4 hours. The guide is designed so you can follow at your own pace, and you can break it into multiple sessions. The first section (up to getting an LED blinking) can be done in about 30 minutes (this mimics the short in-person workshop version). Subsequent sections diving into HAL usage, sensors, and async will add a few more hours of exploration.

How to Use This Guide

This guide is organized as a progression of chapters that build on each other. It is recommended to go in order, since later chapters assume knowledge and setup from earlier ones. Each chapter includes explanations, code examples, and often an exercise or challenge for you to try. These exercises are typically short (a few minutes each) and give you hands-on practice – don’t skip them! You’ll learn more by doing. By the end of the guide, you’ll have two versions of a small project (one using a traditional HAL, one using async Embassy) that blink an LED and communicate over serial, plus familiarity with how to extend these to real-world tasks (like reading sensors or handling multiple tasks concurrently).

Throughout the text, we include citations to external sources and documentation for deeper reference.1 If you’re reading this in a medium that supports it, you can click those to see the source. We also include occasional images or diagrams to illustrate points (with captions and attribution).

Alright, let’s dive in!


  1. https://rust-lang.github.io/mdBook/format/markdown.html

Flashing Rust is Easier Than Brewing Coffee. ☕

Our first goal is to show that getting Rust code running on a real microcontroller is surprisingly simple – often just a few commands. We’ll set up the toolchain for the RP2350, compile a “blinky” program, and flash it to the board. By the end of this chapter, you’ll have an LED blinking on actual hardware, and you’ll see that putting Rust on a microchip is literally easier than making a cup of coffee.

Setting Up Your Rust Embedded Toolchain

Before writing any code, we need to configure our development environment for cross-compiling to the RP2350 microcontroller. The RP2350 is a dual-core ARM Cortex-M33 microcontroller (with some special features we’ll discuss later). Rust, via rustup, can target this architecture easily.

  1. Install Rust (stable) if you haven’t already, via rustup. Ensure you also have Cargo, which comes with Rust.
  2. Update Rust to the latest stable version and self-update rustup (to avoid any surprises):
    rustup self update  
    rustup update stable
    
  3. Add the target for RP2350: The Cortex-M33 core uses the ARMv8-M architecture with hardware floating-point. The correct Rust target triple is thumbv8m.main-none-eabihf. We’ll add that, and for completeness also add the ARMv6-M target used by older Pico (RP2040) and the RISC-V target (the RP2350 has a RISC-V mode, as we’ll mention). Run:
    rustup target add thumbv6m-none-eabi # (for RP2040, if needed)
    rustup target add thumbv8m.main-none-eabihf # RP2350 Arm Cortex-M33
    rustup target add riscv32imac-unknown-none-elf # (RP2350 RISC-V mode)
    

    Note: The RP2350 is unique in that each core can actually run as either an ARM Cortex-M33 or a RISC-V core (Hazard3) – a novel feature where the core type can be swapped at runtime . In this guide we’ll focus on the standard Cortex-M (ARM) mode. The RISC-V target is added just in case and for future exploration.

With these tools you will already be able to turn valid Rust code into a .elf file, which you can then flash onto the RP2350 using the methods described in the next chapter.

RP2350 MCU Flashing Methods

There are three main ways to flash firmware onto an RP2350 microcontroller, each with different prerequisites and use cases. You can either flash it using the RP2350-built-in bootloader by dragging & dropping the .uf2 file to the mounted drive, by using the picotool tool to talk to the bootloader directly, or by using a hardware debugger or second Pico for programming and debugging over an Serial-Wire-Debug (SWD) connection. The methods are detailed below.

Depending on your preference, you will then learn how to install the tools for these methods in the next chapter. We recommend trying Method 1 first, as it is the easiest to use and doesn't require any installation and setup the other methods later if you need to.

Method 1: Online Conversion

This method requires no software installation and works entirely through a web browser and your system's file manager.

Prerequisites: None

Steps:

  1. Take your .elf file
  2. Go to elf2uf2.yrust.de1
  3. Upload and convert to .uf2
  4. Put RP2350 into boot mode:
    • Hold BOOTSEL button
    • Power on
    • Release BOOTSEL
  5. Drag & drop the .uf2 file to the mounted drive
  6. Device will automatically reset and run

Method 2: Bootloader and picotool

This method uses the official Raspberry Pi tool for direct flashing.

Prerequisites: picotool installed

Steps:

  1. Put RP2350 into boot mode:
    • Hold BOOTSEL button
    • Power on
    • Release BOOTSEL
  2. Flash directly with picotool:
    picotool load -u -v -x -t elf your_file.elf
    
  3. Device will automatically reset and run

Method 3: Debug Probe

This method uses a hardware debugger or second Pico for programming and debugging.

Prerequisites: Debugger or second Pico + probe-rs installed

Steps:

  1. Connect debugger or second Pico to target RP2350
  2. Flash and run with probe-rs:
    probe-rs run --chip RP235x --protocol swd your_file.elf
    
  3. Device will be programmed and start running automatically

Additional Notes

To erase the flash, you can use picotool erase or drag & drop the flash nuke utility: flash_nuke.uf2 (direct download, 96 kB)


  1. Our online tool elf2uf2.yrust.de simply executes picotool uf2 convert your_file.elf your_file.uf2 - it's equivalent to having picotool installed locally but runs in the browser.

Install tools for flashing

Method 1: RP2350-built-in bootloader via drag & drop

As described in the previous chapter, you don't need to install any tools to flash the RP2350 using the RP2350-built-in bootloader if you use our online conversion method at elf2uf2.yrust.de. If you prefer to convert it locally though, you will need to install picotool, as described in the next paragraph.

Method 2: RP2350-built-in bootloader with picotool

The RP2040 and RP2350 have a built-in bootloader that presents the device as a USB drive when you plug it in with the BOOTSEL button pressed. You can copy a .uf2 file to that drive to flash new firmware . For this, it’s helpful to install picotool, a utility from Raspberry Pi that can convert binaries to UF2 and upload them. You can download a pre-built picotool binary from the releases page for your OS or build it from source. Their docs state that You cannot just copy the binary into your PATH, else the Pico SDK will not be able to locate it., however, if you only want to use the RP2350 with Rust code and don't plan on using the SDK, you absolutely can just copy the picotool binary to your PATH.

With picotool installed, you can now flash the RP2350 (given that the bootsel button is pressed when plugging it in) using:

picotool load -u -v -x -t elf your_file.elf

you can also convert .elf files to .uf2 files using:

picotool uf2 convert your_file.elf your_file.uf2

Method 3: Probe-based flashing (SWD)

Using an external debug probe or a second Pico as a probe, you can flash and debug the chip directly via the SWD interface. For this, we use probe-rs, a Rust project that replaces OpenOCD/GDB. We recommend installing using the recommended install scripts, which provides the probe-rs CLI and associated tools:

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-tools-installer.sh | sh

This gives you commands like probe-rs flash and also includes probe-run (for running programs with semi-hosted debugging). (If you’re curious: probe-rs is a pure-Rust implementation of the ARM debugging protocol and works with CMSIS-DAP, J-Link, etc. It makes flashing and even debuggingfirmware straightforward.)

(optional) Install cargo-generate

We will use cargo-generate to quickly bootstrap projects from templates. Install it with:

cargo install cargo-generate

Starting a New Project – Blinky (RP2350 Template)

Next, we’ll start a new Rust project for the Raspberry Pi Pico 2 board. The community provides ready-to-go templates for Rust on these boards, which set up all the necessary bits (memory layout, linker script, HAL dependencies, and so on). We will use the official rp-rs project template for RP2350.