Skip to main content

Getting started

This article goes over the steps to get set up to write embedded rust for the Raspberry Pi Pico.

1. Dependencies #

Before you install any dependencies, make sure everything is up to date with

rustup self update
rustup update stable

If you’re using Nix or NixOS you can use this flake instead.

To compile your code for the pico, you’ll need the thumbv6m-none-eabi toolchain and stack overflow protection, so for that run

rustup target add thumbv6m-none-eabi
cargo install flip-link

Depending on what you use to flash your pico, you’ll need different tools installed:

Flashing via usb (only one pico needed) #

You’ll need the tool that converts the elf binary to uf2 for the pico to be able to run it:

cargo install elf2uf2-rs --locked

Flashing via probe (two picos or a pico and a picoprobe needed) #

If you have a second pico you want to use as a probe, you can flash the picoprobe firmware. If you have a picoprobe, the firmware comes pre-installed. To flash and communicate with the probe, you’ll need to install probe-rs. Look at how to install it here: https://probe.rs/docs/getting-started/installation/

Creating a udev rule #

For your PC to be able to identify the probe, you’ll need to create a udev rule. You’ll need to know the probe’s identifier. for that run the following with the probe connected.

lsusb

This will output a list of USB devices, and you have to find the line that’s similar to the following:

Bus 001 Device 005: ID 2e8a:000c Raspberry Pi Picoprobe CMSIS-DAP

You’ll notice it says ID AAAA:BBBB. For me, this code is 2e8a:000c, but it could be different for you. Write down the code you got, you’ll need for the udev rule.

You’ll also need to know what group the probe is owned by for the udev rule, so for that run

grep 'uucp\|dialout' /etc/group

It will output a line cotaining either uucp or dialout. Write down which one of the two it is. If both come up, pick one. And if nothing copes up, you’ll have to create the group uucp with

sudo groupadd uucp

Before adding yourself to the group, you can check if you’re already in it, so you can skip the step. Check with

groups

If you’re not in the group, you can add yourself with

sudo usermod -a -G GROUP_NAME YOUR_USERNAME

and apply the changes with

newgrp GROUP_NAME

(or rebooting)

And now, to finally create the udev rule. You can either dowload the official multi-purpose probe-rs rule file or create your own.

If you dowloaded the rule file, move it to the following directory. If you wish to make your own, you’ll need to create a file on the following directory:

/etc/udev/rules.d/

A udev rules file name should be formatted the following way:

[priority]-[name].rules

  • [priority] has to be a two digit number, from 01 to 99. Make sure it’s lower than 73 though, otherwise it can cause issues.
  • [name] is the name you want to give the file so it is easily identifiable.
  • With this in mind, I will name the file 50-pico.rules, but if for any reason a file with that exact name already exists, you can change it up a little

The file has to contain the following:

ATTRS{idVendor}=="AAAA", ATTRS{idProduct}=="BBBB", MODE="664", GROUP="YOUR_GROUP"

Remember to open it as root, otherwise you won’t be able to save. You replace AAAA with the first part of the code you got when running lsusb and BBBB with the second. You’ll also have to change YOUR_GROUP to uucp or dialout depending on what you got earlier.

You can apply the rule changes with the following command:

sudo udevadm control --reload-rules; sudo udevadm trigger

Connecting the probe #

Connect the probe to the target according to the following diagram:

wiring
The USB on the target is not necessary if you’re not using a pico W and aren’t pulling a lot of power from the picos. The purple and blue connestions are optional.

2. Creating a project #

To create a project you’ll need a template. Starting from a regular cargo project and adding the extra stuff yourself is possible, but it’s tedious, error prone and time consuming.

There are several options, here I list two:

Both of them use cargo-generate, which means it walks you through the creation of a project specifically tailored to you. This means you have to install cargo-generate first though. Here’s how to generate a project:

cargo generate rp-rs/rp2040-project-template # If you want to use the official template
cargo generate gl:slusheea/rp2040-template # If you want to use mine

3. Looking at the template #

The first thing you’ll see when you open main.rs is #[no_std] and #[no_main]. That’s because we’re on a microcontroller, so we won’t be using the standard library and we’ll use a never ending loop in main.

Next up are use rp_pico as bsp, use bsp::entry and some other imports from the BSP. BSP stans for Board Support Package, it is what contains all the abstractions for the different features on the pico. You’ll see that from the BSP we import the system clocks, the PAC (the pico’s peripherals), the Watchdog and the SIO. From the generic embedded HAL, we take OutputPin, which lets us blink the LED. I explain this more in depth in the next article, since it’s about GPIO. panic_probe lets the pico panic, and if you’re using a probe, you’ll see defmt and defmt-rtt, which handle communications with the probe on the background.

Just before the main function, there’s the entry macro that tells the compiler where the program starts, since we used #[no_main]. Now finally inside main, there’s a block that we’ll need 99% of the time, and that basically lets us access the pico’s features. After that the system clock gets initialized. In this case we need it for the delay, but even if we were to not use it, it’s recommended to still initalize it. That will give a compiler warning though, so you can add an underscore in front of the name to get rid of it.

Next is the delay object that lets us call .delay_ms() and the initialization of the LED’s pin. Since we’re making the built in LED, we take led from the pins struct.

And that’s all the ‘boilerplate’ done! We call .set_high() on the LED to turn it on and .set_low() to turn it off.

4. Flashing #

USB flashing #

cargo run --release

Or the following if you have just installed and are using my template

just flash

Probe flashing #

cargo build --target thumbv6m-none-eabi
probe-rs run target/thumbv6m-none-eabi/debug/PROJECT_NAME --chip RP2040

Where PROJECT_NAME is replaced by the project name in the Cargo.toml, or if you’re using my template and have just installed:

just build flash

5. Troubleshooting #

Errors flashing with a probe #

If you get a similar error when flashing with a probe

 Error failed to demangle defmt symbol `{"package":"firmware","tag":"defmt_debug","data":"Booted","disambiguator":"18423169058307175441"}`: missing field `crate_name` at line 1 column 97

or

ERROR probe_rs::run: defmt wire format version mismatch: firmware is using 3, `probe-run` supports 4
suggestion: use a newer version of `defmt` or `cargo install` a different version of `probe-run` that supports defmt 3 Continuing without RTT...     

Try first force-reinstalling probe-rs, and if that doesn’t work, try adding this to the bottom of the Cargo.toml:

[patch.crates-io]
defmt = { git = "https://github.com/knurling-rs/defmt" }

The LED on the Raspberry Pi Pico W is connected to the WiFi module, and the pin 25 (the one that’s connected to the LED on the non-W) is connected to the WiFi module. For this reason, it won’t blink. You can try to change the pin on the code to a physical one and use an LED connected to that pin and ground. You can also:

  • Try the code on a non-W pico (Where the LED should blink)
  • Select “Raspberry Pi Pico W” when being prompted for the developement board if you’re using my template.