probe-run, run embedded Rust apps like native apps

Today we are pleased to announce the public release of probe-run, a custom Cargo runner for embedded development. This host application integrates into your Cargo workflow and lets you cargo run embedded applications. Let's see how to use it for ARM Cortex-M application development.

Setup

First, start by installing the Cargo runner.

$ cargo install probe-run

Now set probe-run as the Cargo runner for your embedded application. You do this in .cargo/config; if you are using the cortex-m-quickstart template then extend it like this:

# file: .cargo/config
# under this section
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# (we removed the commented out runners that were here)
runner = "probe-run --chip nRF52840_xxAA" # <- add this

Instead of nRF52840_xxAA use the name of your microcontroller; you should find it in the output of the probe-run --list-chips command.

Now you are all set to cargo run embedded applications!

Run it

Let's say you have this program.

#![no_std]

use cortex_m::asm;
use cortex_m_rt::entry;
use rtt_target::{rprintln, rtt_init_print};

#[entry]
fn main() -> ! {
    rtt_init_print!();
    rprintln!("Hello, world!");

    loop {
        asm::bkpt()
    }
}

cargo run-ing this program produces the following output:

$ cargo run --bin hello
  Running `probe-run --chip nrf52 target/thumbv7em-none-eabi/debug/hello`
flashing program ..
DONE
resetting device
Hello, world!
stack backtrace:
   0: 0x0000167c - __bkpt
   1: 0x0000055e - hello::__cortex_m_rt_main
   2: 0x00000432 - main
   3: 0x00001e28 - Reset

As you can see, text printed by the device over RTT (rprintln!) is shown in the output of probe-run.

Stack backtraces

When the firmware reaches a BKPT (BreaKPoinT) instruction the device halts. The probe-run tool treats this halted state as the "end" of the application and exits. Before exiting probe-run prints the stack backtrace of the halted program. This backtrace follows the format of the std backtraces you get from std::panic! but includes <exception entry> lines to indicate where an exception/interrupt occurred.

We find it quite useful to "exit" embedded applications with a backtrace on panics. See the #[panic_handler] function in the example below:

#![no_std]

use cortex_m::{asm, peripheral::SCB};
use cortex_m_rt::{entry, exception};
use rtt_target::{rprintln, rtt_init_print};

#[entry]
fn main() -> ! {
    rtt_init_print!();
    rprintln!("main");
    SCB::set_pendsv(); // trigger PendSV exception
    rprintln!("after PendSV");

    exit()
}

#[exception]
fn PendSV() {
    rprintln!("PendSV");
    panic!()
}

#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
    rprintln!("{}", info);
    exit()
}

fn exit() -> ! {
    loop {
        asm::bkpt() // halt = exit probe-run
    }
}

Now we have the same panicking behavior as std programs!

$ cargo run --bin panic
flashing program ..
DONE
resetting device
main
PendSV
panicked at 'explicit panic', src/main.rs:21:5
stack backtrace:
   0: 0x00001f58 - __bkpt
   1: 0x00000490 - nrf52::exit
   2: 0x00000484 - rust_begin_unwind
   3: 0x00002134 - core::panicking::panic_fmt
   4: 0x000020cc - core::panicking::panic
   5: 0x00000616 - nrf52::__cortex_m_rt_PendSV
   6: 0x000005e6 - PendSV
      <exception entry>
   7: 0x00001e00 - core::ptr::write_volatile
   8: 0x000006d2 - cortex_m::peripheral::scb::SCB::set_pendsv
   9: 0x000005c4 - nrf52::__cortex_m_rt_main
  10: 0x0000049a - main
  11: 0x00002de6 - Reset

Device support

probe-run is built on top of the probe-rs library so it inherits its device and probe support.

Currently, probe-run does not support the (hard-float) thumbv7em-none-eabihf target; you'll get a CLI error if you try to cargo run a program compiled for that target. You can work around this limitation by compiling your application to the (soft-float) thumbv7em-none-eabi target (note: no hf). Hard float support is on the TODO list.

What's next?

In follow-up blog posts we'll cover:

  • how to use custom panic handler and probe-run to run unit tests on the embedded device
  • how the defmt ("deferred formatting") logging framework – introduced in a previous post as binfmt – plugs into probe-run

Sponsor this work

probe-run is a Knurling project and can be funded through GitHub sponsors. Sponsors get early access to defmt and other tools we are building. Thank you to all of the people already sponsoring our work through the Knurling project!