44

Getting Started with Rust on a Raspberry Pi Pico

 2 years ago
source link: https://reltech.substack.com/p/getting-started-with-rust-on-a-raspberry
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Getting Started with Rust on a Raspberry Pi Pico

Post #1 in a series

This is the first in a series of posts exploring the highly productive and exciting world of Raspberry Pi Foundation’s first microcontroller (RP2040) + board (Pico) with firmware written in Rust. This first one will cover how to use a Raspberry Pi Picoprobe (RPi Pico + debugger firmware) to flash and debug embedded Rust firmware on another Pico board.

True to the original charter of this Substack, I’ll also be discussing some of the human relational aspects of these guides. That might sound confusing or even a bit of a stretch, but trust me when I say there’s more than just a technical reason I’m posting a guide like this. Publishing a technical guide is inherently a human relational act. I’ll explain more about this in a future post in this series.

This guide covers using two Raspberry Pi Pico boards, one as the target and one as a hardware programmer and debugger. You may purchase these boards from several places, but I ordered mine with pre-soldered headers from Elektor’s store.

Note You may find the latest source version of Picoprobe firmware from the Raspberry Pi Foundation.

If you’re not already familiar with Rust, it’s the first language that has truly gotten me excited to work with for embedded applications since I first started working with C/C++ many years ago. There have been others that have tried to bring performance, safety and convenience to embedded programming, but none have delivered on this quite as well as Rust. It truly is the first language that I believe can fully replace C/C++ without sacrificing much of anything while giving you many things that C/C++ will never provide. This blog post covering some of C vs Rust is well worth a read.

Hardware Setup

Before getting the required software installed, flashed and configured, we must first get our hardware wired and ready to be flashed. You need two identical Raspberry Pi Picos ideally with pre-soldered headers attached.

Note: For the skilled solderer you may attach your own headers, but do know it’s not very easy to get them soldered without accidentally melting or damaging the board in some way. When I attempted it, even with some prior soldering experience, I melted the plastic button top without realizing it until after.

Pico to Pico Wiring

Figure 1 - Pico A and Pico B (source)

Figure 2 - Pico A and Pico B

As you can see in Figure 1, having the soldered headers attached to both Pico boards makes it much easier to wire together for flashing and debugging because you can use a breadboard to make all of the pin connections very straightforward.

If you’re looking for some jumper wires like what I used you may order them from SparkFun here:

Here’s a written summary of how to wire the two Pico boards to each other, referring to the Pico board on the left as Pico A and the one on the right as Pico B:

  1. Connect Pico B GND (Pin 38) to Pico A GND (Pin 38)

  2. Connect Pico B VSYS (Pin 39) to Pico A VSYS (Pin 3)

  3. Connect Pico B UART0_TX (Pin 1) to Pico A UART1_RX (Pin 6)

  4. Connect Pico B UART0_RX (Pin 2) to Pico A UART1_TX (Pin 7)

  5. Connect Pico B SWCLK to Pico A I2C1 SDA (Pin 4)

  6. Connect Pico B GND to Pico A GND (Pin 3)

  7. Connect Pico B SWDIO to Pico A I2C1 SCL (Pin 5)

Note: See this pin diagram that shows a Pico board pin diagram containing both pin numbers and pin names.

Pico to Development Host Wiring

Connect the included micro USB cable from Pico B to your development host computer which will allow the host computer to use Pico B as the programmer/debugger as well as provide power to both Pico B and Pico A.

Software Setup

Ubuntu Linux

These instructions were tested with Ubuntu Linux 21.04 and will likely continue to work correctly for several newer versions of Ubuntu. Make sure that your installed software is fully up to date via the Software Updater utility before following this guide.

macOS

These instructions were tested with macOS Big Sur v11.5.x and will likely continue to work correctly for newer versions of macOS. Make sure that your base macOS is fully up to date via the Software Update utility under System Preferences before following this guide.

Note to complete the installation of the toolchain on Mac please make sure you have Homebrew installed before proceeding.

Installing the Toolchain

In order to flash your Rust application onto your Pico target board, you must first install the GNU Debugger (gdb) and OpenOCD. The debugger is a widely used piece of software that provides a way to flash a binary onto many different targets, set breakpoints in your code on specific lines, and step through your code helping you better understand exactly what’s happening.

To install gdb, open a terminal on your development machine and enter the following:

$ sudo apt install git gdb-multiarch

If you’re on a Mac:

$ brew install git armmbed/formulae/arm-none-eabi-gcc

OpenOCD is a piece of software that exists to translate between specific debugging protocols on many different types of microprocessors and SoCs, and then presents a unified protocol interface for gdb to connect to.

At the time of writing support for the RP2040 (the microprocessor on the Pico board) has not been upstreamed and released in Ubuntu’s openocd package yet, so it must be built and installed from the Raspberry Pi Foundation’s GitHub repository:

$ git clone https://github.com/raspberrypi/openocd.git --branch picoprobe --depth=1
$ cd openocd
$ ./bootstrap
$ ./configure --enable-ftdi --enable-sysfsgpio --enable-bcm2835gpio --enable-picoprobe
$ make -j4
$ sudo make install

On a Mac:

$ brew install automake libtool libusb pkg-config texinfo wget
$ git clone https://github.com/raspberrypi/openocd.git --branch picoprobe --depth=1
$ export PATH="/usr/local/opt/texinfo/bin:$PATH"
$ cd openocd
$ ./bootstrap
$ ./configure --enable-picoprobe --disable-werror
$ make -j4
$ sudo make install

Note If your development host is Ubuntu on a full Raspberry Pi (e.g. v3 or v4) the first three options allow for bitbanging over the SWD port on the Pico. These flags aren’t required nor useful if you’re working from a regular laptop machine. 

Checking for the version of openocd should verify a successful build and installation looking something like:

$ openocd -v       
Open On-Chip Debugger 0.10.0+dev-g18b4c35-dirty (2021-08-17-22:32)

On a Mac:

$ openocd -v       
Open On-Chip Debugger 0.10.0+dev-g18b4c35 (2021-09-03-13:32)

And now install a program that can connect to the UART (serial) port on the Pico allowing you to see console log messages that your application prints out.

$ sudo apt install minicom

On a Mac:

$ brew install minicom

For the latest information as well as some great tips and tricks for working with the Pico see the official Raspberry Pi Getting Started Guide.

Flashing Picoprobe Firmware

In order to use one Pico to flash and debug another one, you must first flash firmware created by the Raspberry Pi Foundation onto one Pico of your choice.

  1. To do that, first download this UF2 binary onto your development machine

  2. Then, holding down the BOOTSEL (short for boot select) button on the Pico that will become the Picoprobe, plug in the USB cable into your development machine

  3. A window of the flash storage of the Pico should pop up no matter what operating system you’re running on your development machine

  4. Drag and drop the picoprobe.uf2 onto this window

  5. The Pico should now be fully flashed and ready to be used as a Picoprobe

Setting Picoprobe User Device Access

Note: This section is for Ubuntu Linux only and does not apply to macOS.

Unless you set up special user level access to the Picoprobe, you’ll be required to use sudo every time you try to connect OpenOCD to it. Instead, we can create a udev rule that’ll allow you to connect as a non-privileged user.

To get the necessary parameters required to set up the udev rule (i.e. the probe’s USB PID/VID), run the following after plugging in the Pico acting as a debug probe into your host development machine:

$ lsusb | grep -i pico

You’ll see output that looks like the following:

Bus 001 Device 018: ID 2e8a:0004 Raspberry Pi Picoprobe

In this case the PID is 2e8a and the VID is 0004.

Next, open up an editor creating the following udev rules file if it doesn’t already exist. If it does exist, just add to the bottom.

$ sudo vim /etc/udev/rules.d/99-openocd.rules

Adding the following two lines:

# Raspberry Pi Picoprobe
ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0004", MODE:="0666"

Installing Rust

Of course you can’t do much in the way of writing applications for the Pico without a language and toolchain. So let’s review how to get Rust installed on your development machine.

Even though this is based on the Embedded Rust book and the standard Rust installation guide, and you could technically follow those instructions there, bringing everything into one guide ensures that everything is convenient and the instructions have all been tested together as a set.

To begin, in your terminal, start by installing rustup which will manage your entire Rust toolchain:

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

rustup will attempt to automatically configure your path. If it fails to do so automatically you can manually add your Rust installation to your $PATH environment variable if you’re using Bash:

$ echo ‘~/.cargo/bin’ >> ~/.bashrc
$ source ~/.bashrc

Or to add it if you’re using Zsh:

$ echo ‘~/.cargo/bin’ >> ~/.zshrc
$ source ~/.zshrc

Verify your Rust toolchain installation:

$ rustc --version

Should output something like:

rustc 1.54.0 (a178d0322 2021-07-26)

Updating Rust

Note that for the future, you will manage updating Rust via rustup. You can update to the latest stable version of your Rust toolchain via:

$ rustup update

Embedded Rust Example Application

Now that we’ve got a full development and target device setup, let’s move on to how to use it to compile, flash and debug an example Rust application. This example comes from an open source GitHub repository providing all of the needed pieces written in Rust to target a Raspberry Pi Pico and onboard peripherals. You may find the original source code here which continues to evolve, so it may or may not look the same as what’s listed here.

This example simply blinks the onboard LED on the Pico, cycling every ½ second. It does this by writing a high (1) or a low (0) to the I/O port GP25.

#![no_std]
#![no_main]

use cortex_m_rt::entry;
use defmt::*;
use defmt_rtt as _;
use embedded_hal::digital::v2::OutputPin;
use embedded_time::fixed_point::FixedPoint;
use panic_probe as _;
use rp2040_hal as hal;

use hal::{
    clocks::{init_clocks_and_plls, Clock},
    pac,
    io::Sio,
    watchdog::Watchdog,};

#[link_section = ".boot2"]
#[used]
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;

#[entry]
fn main() -> ! {
    info!("Program start");
    let mut pac = pac::Peripherals::take().unwrap();
    let core = pac::CorePeripherals::take().unwrap();
    let mut watchdog = Watchdog::new(pac.WATCHDOG);
    let sio = Sio::new(pac.SIO);

    // External high-speed crystal on the pico board is 12Mhz
    let external_xtal_freq_hz = 12_000_000u32;
    let clocks = init_clocks_and_plls(
        external_xtal_freq_hz,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();

    let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer());

    let pins = hal::gpio::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );

    let mut led_pin = pins.gpio25.into_push_pull_output();

    loop {
        info!("on!");
        led_pin.set_high().unwrap();
        delay.delay_ms(500);
        info!("off!");
        led_pin.set_low().unwrap();
        delay.delay_ms(500);
    }
}

Figure 3 - Rust source that blinks the onboard LED

This example makes use of several Pico-specific abstractions that are also a part of the same open source project.

The first is the RP2040 microcontroller hardware abstraction layer (HAL). What’s a HAL used for exactly? 

Start by cloning the git repository for the example Rust application from above:

$ git clone https://github.com/rp-rs/rp2040-project-template

Next we’ll build the Rust application into a binary image. To do this, use cargo to build:

$ cd rp2040-project-template

To install Rust development dependencies needed to compile run:

$ rustup target install thumbv6m-none-eabi
$ cargo install --git https://github.com/rp-rs/probe-run --branch main
$ cargo install flip-link

Now to actually build the binary:

$ cargo build

Loading the App onto the Target with gdb

Change into the root directory where you built openocd in, for example:

$ cd ~/Projects/openocd

In one terminal run and keep open:

$ src/openocd -f interface/picoprobe.cfg -f target/rp2040.cfg -s tcl

In another terminal run:

$ cd ~/Projects/rp2040-project-template

Ubuntu:

$ gdb-multiarch -q -ex "target extended-remote :3333" target/thumbv6m-none-eabi/debug/rp2040-project-template

Or on Mac:

$ arm-none-eabi-gdb -q -ex "target extended-remote :3333" target/thumbv6m-none-eabi/debug/rp2040-project-template

Now to load the example application onto the target Pico A, run the following from the gdb shell:

(gdb) load

To test that the application binary loaded onto the target flash successfully, run it from the gdb shell:

(gdb) continue

Note: if this is your first time using gdb, you can issue abbreviated forms of almost every command. For example, instead of typing continue I could simply type c.

At this point you should see the main LED on the Pico board flashing on and off in an infinite loop. This application is loaded onto the flash, so even if you exit out of gdb, unplug the USB cable and re-plug it in, the same application will automatically run again once it receives power from the USB port.

Pressing CNTRL+C will cause gdb to interrupt execution on the target and you’ll see what line of code from the example application that it was about to execute next.

Helpful Related Resources

Rust Book - A great intro to Rust as a programming language

Embedded Rust Book - A great intro to embedded programming using Rust

Getting Starting with Raspberry Pi Pico - from the Raspberry Pi Foundation (C/C++ centric but related)

Embedded Software Engineering 101 - if you’re brand new to embedded concepts, this is an amazingly accessible introduction

Future posts in this series will dive more into working with Rust on the Pico, exploring the current capabilities of the Raspberry Pi Pico hardware abstraction layer using what we learned here.

I welcome all feedback about this guide and any future ones in the series. And if you’d like to see me cover a specific aspect of Rust and the Pico, let me know!

As always, thanks for reading and passing this along to anyone else that would find value from reading the Relational Technologist Substack.

Please enjoy!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK