Article

probe-run, run embedded Rust apps like native apps

Published on 4 min read
Knurling icon
Knurling
A tool set to develop embedded applications faster.
❤️ Sponsor

    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!