The Relational Technologist

Share this post

Getting Started with Rust on a Raspberry Pi Pico (Part 2)

reltech.substack.com

Getting Started with Rust on a Raspberry Pi Pico (Part 2)

Iterating Rust development faster with cargo run

Jim Hodapp
Nov 1, 2021
1
2
Share this post

Getting Started with Rust on a Raspberry Pi Pico (Part 2)

reltech.substack.com

This is the second in a series of guides exploring the use of Rust on the Raspberry Pi Pico. In this guide, I’ll be showing you how to make your life easier through the use of the cargo run command which is an important part of developing all kinds of applications with Rust.

If you’re not already familiar with Cargo, then I encourage you to start by reading the official Cargo Book provided by the Rust community. At a minimum, read the first section on what Cargo is and why it exists. You’ll be much better equipped to fully appreciate this guide.

Part 1 of the series - blinking an LED from a Pico

Part 3 of the series - iterating even faster by flashing and debugging with Visual Studio Code


Review from Last Time

From the first guide in this series, I showed you how to blink an LED with Rust on a Pico device. We made use of OpenOCD, gdb and a little bit of Cargo to build the example application’s binary to run on the target Pico device.

The techniques from the guide certainly get the job done in that it gives you all the tooling and setup you need to start building applications for the Pico using Rust. But it’s not a very fun process nor is it one that allows you to iterate quickly when making rapid changes to your source code.

Each time you made a change to your code, you have to call cargo build, manually start up OpenOCD, manually run gdb, and then type a series of commands in gdb to flash your binary onto the target and begin debugging your code.

This is exactly where cargo run can speed up and greatly reduce the number of commands you need to type in and run each time you want to test out your changes.

Configuring Cargo

The first step to take to be able to use cargo run is to modify your project’s Cargo configuration file. We’re still going to be using the example application from the first guide in the series.

To begin, open up the following file in a text editor:

$ vim ~/Projects/rp2040-project-template/.cargo/config


The file should look like this by default:

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-run-rp --chip RP2040"

rustflags = [
  "-C", "linker=flip-link",
  "-C", "link-arg=--nmagic",
  "-C", "link-arg=-Tlink.x",
  "-C", "link-arg=-Tdefmt.x",

  # Code-size optimizations.
  #   trap unreachable can save a lot of space, but requires nightly compiler.
  #   uncomment the next line if you wish to enable it
  # "-Z", "trap-unreachable=no",
  "-C", "inline-threshold=5",
  "-C", "no-vectorize-loops",
]

[build]
target = "thumbv6m-none-eabi"

Notice the second line in the config file:

runner = "probe-run-rp --chip RP2040"

Comment this line out like so:

# runner = "probe-run-rp --chip RP2040"

Then add a new line just under the one you commented out:

If you’re using Ubuntu:

runner = "gdb-multiarch -q -x openocd.gdb"

or if you’re on a Mac:

runner = "arm-none-eabi-gdb -q -x openocd.gdb"

The resulting file should now look like:

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "arm-none-eabi-gdb -q -x openocd.gdb"
# runner = "probe-run-rp --chip RP2040"

rustflags = [
  "-C", "linker=flip-link",
  "-C", "link-arg=--nmagic",
  "-C", "link-arg=-Tlink.x",
  "-C", "link-arg=-Tdefmt.x",

  # Code-size optimizations.
  #   trap unreachable can save a lot of space, but requires nightly compiler.
  #   uncomment the next line if you wish to enable it
  # "-Z", "trap-unreachable=no",
  "-C", "inline-threshold=5",
  "-C", "no-vectorize-loops",
]

[build]
target = "thumbv6m-none-eabi"


When you execute cargo run from the root directory of the example application source tree, Cargo will know to execute gdb for you - you no longer have to execute gdb manually yourself. It can have different runners for different contexts, but since we didn’t specify a particular —target, it’ll make use of the one we specified because of the broadly specified platform target from the first line:

[target.'cfg(all(target_arch = "arm", target_os = "none"))']


For more information about platform targets, see the Cargo Book documentation.

Saner gdb use

We’re not quite ready to try this out just yet. You’ll notice that at the end of the line specifying the runner, I include a local gdb config file to use. As you’ll see shortly, this file contains the base set of gdb commands that we want to run every time we execute cargo run. This saves us a lot of repetitive typing allowing us to iterate much more quickly on our development cycle.

Using your text editor, create the following file:

$ vim ~/Projects/rp2040-project-template/openocd.gdb


and then add the following text to the file from your editor and save it:

# connect to OpenOCD on TCP port 3333
target extended-remote :3333

# print demangled function/variable symbols
set print asm-demangle on

# set backtrace limit to not have infinite backtrace loops
set backtrace limit 32

# detect unhandled exceptions, hard faults and panics
break DefaultHandler
break HardFault

# *try* stopping at the user entry point (it might be gone due to inlining)
break main

monitor arm semihosting enable

# load the application binary onto the Pico's flash
load

# start the process but immediately halt the processor
stepi

Based on openocd.gdb from the Embedded Rust Discovery Book

Running and Debugging with Cargo

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

$ cd ~/Projects/openocd

In a new terminal run and keep OpenOCD open:

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

At this point we’re ready to try cargo run. From the root directory of the example application source tree, run the following in a separate terminal from the one used to run OpenOCD:

$ cargo run


You should see output similar to the following:

    Finished dev [optimized + debuginfo] target(s) in 0.06s
     Running `arm-none-eabi-gdb -q -x openocd.gdb target/thumbv6m-none-eabi/debug/rp2040-project-template`
Reading symbols from target/thumbv6m-none-eabi/debug/rp2040-project-template...
warning: multi-threaded target stopped without sending a thread-id, using first non-exited thread
0x100001aa in Reset ()
Breakpoint 1 at 0x10001f24: file /Users/jhodapp/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.7.0/src/lib.rs, line 933.
Note: automatically using hardware breakpoints for read-only addresses.
Breakpoint 2 at 0x10003140: file /Users/jhodapp/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.7.0/src/lib.rs, line 923.
Breakpoint 3 at 0x100001f4: file src/main.rs, line 26.
semihosting is enabled

Loading section .boot2, size 0x100 lma 0x10000000
Loading section .vector_table, size 0xa8 lma 0x10000100
Loading section .text, size 0x2f9c lma 0x100001a8
Loading section .rodata, size 0x83c lma 0x10003150
Loading section .data, size 0x30 lma 0x1000398c
Start address 0x100001a8, load size 14768
Transfer rate: 9 KB/sec, 2953 bytes/write.
0x100001aa in Reset ()
(gdb)

If you type continue into gdb, then it should hit the main() function breakpoint. If you type continue again then the LED should start blinking exactly like it did from the first guide in the series. If you press CNTRL+C, you’ll notice that you’re inside a gdb command shell where you are now set up to debug this application.

Let’s list out the breakpoints that we set in the gdb config file:

(gdb) info breakpoints
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x10001f24 in cortex_m_rt::DefaultHandler_ at /Users/jhodapp/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.7.0/src/lib.rs:933
2       breakpoint     keep y   0x10003140 in cortex_m_rt::HardFault_ at /Users/jhodapp/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.7.0/src/lib.rs:923
3       breakpoint     keep y   0x100001f4 in rp2040_project_template::__cortex_m_rt_main_trampoline at src/main.rs:26

Success! We’ve got an easy-to-iterate embedded Rust setup now.

Note that I did try the instructions specified at here for a non-OpenOCD setup using a CMSIS-DAP debug probe with probe-run-rp, which also uses a second Pico to program a first. I did not have any luck with getting this to run and haven’t had the time to figure out what’s not working. If you have better luck with this than I, please let me know. I’ll add additional instructions to this guide covering the probe-run-rp method to use cargo run for fast iterative embedded Rust development.

Next

For the next guide in this series I’ll be exploring an even easier way to debug your Rust application on a Pico and well as starting to explore working with more of the Pico’s functionality with Rust.

Please enjoy!

Read Part 3 of this series, iterating even faster by flashing and debugging with Visual Studio Code


Enjoying this series? Make sure to subscribe so you don’t miss any of the future guides in this series of working with Rust on the Raspberry Pi Pico.

2
Share this post

Getting Started with Rust on a Raspberry Pi Pico (Part 2)

reltech.substack.com
2 Comments
Randall Young
Nov 21, 2021Liked by Jim Hodapp

Looking forward to your next article in this series. Would love to be able to debug rust code on Linux with Visual Code Studio and the 4$ PICO as a target!

Expand full comment
Reply
1 reply by Jim Hodapp
1 more comment…
TopNewCommunity

No posts

Ready for more?

© 2023 Jim Hodapp
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing