cargo-embed

cargo-embed is the big brother of cargo-flash.

It can also flash a target just like cargo-flash, but it can also open an RTT terminal as well as a GDB server.

And there is much more to come in the future!

Installation

cargo-embed can be found on crates.io. We recommend installing it via

cargo install cargo-embed

Configuration

cargo-embed can be configured via a Embed.toml (or .embed.toml) in the project root.

Here's a simple example:

[default.general]
chip = "STM32F401CCUx"

[default.rtt]
enabled = true

All available options can be found in the default.toml. This example uses toml syntax to set each option under the default top-level profile key.

The Embed.toml should be part of the project, for local-only configuration overrides, you can create an Embed.local.toml (or .embed.local.toml) file and add that to your .gitignore. The local files take precedence.

Profiles

The data in the Embed.toml is structured in two levels: The outer layer is a configuration profile name, inside each profile there are then a series of sections with different options. The default profile is called "default" ;) When invoking cargo-embed, a different profile name can be passed as an argument with --config <profile>, which will load the settings under that profile instead of default.

For example, in your Embed.toml:

[default.general]
chip = "STM32F401CCUx"

# No need to set this again, other profiles inherit from the "default" profile
#[with_rtt.general]
#chip = "STM32F401CCUx"

[with_rtt.rtt]
enabled = true

Now you can run cargo embed --config with_rtt to have RTT enabled, while cargo embed will use the default config "default" without RTT.

RTT

RTT stands for real time transfers and is a mechanism to transfer data between the debug host and the debuggee.

In its essence it provides a configurable amount of ringbuffers, which are read/written by the target and the debug host. The protocol initially was published by Segger but there is really no magic to it other than ringbuffers being used. This mechanism allows us to transfer data from the target to the host and vice versa really fast.

RTT features:

  • Fast duplex data transfers
  • A configurable amount of channels(buffers)
  • Channels can be blocking and non blocking - your choice

This guide should get you going in no time to speed up your development with probe-rs.

Target

For the target side, we offer rtt-target, a small lib to set up the RTT structures in the target memory and read/write data from/to them.

A minimal example of a host firmware would be

#![no_std]
#![no_main]

use microbit as _;
use panic_halt as _;
use rtt_target::{rprintln, rtt_init_print};

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

Host

On the host, just run

cargo embed

with RTT enabled in the Embed.toml file.

Now you should be able to see all the 'Hello World!'s in a default channel called "Terminal"!

Hello World output via RTT

Don't panic!

Of course it is easy to log all panics via RTT! Here is a most simple example of a panic handler:

#![no_std]
#![no_main]

use core::panic::PanicInfo;
use microbit as _;
use rtt_target::{rprintln, rtt_init_print};

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

        for _ in 0..1_000_000 {
            cortex_m::asm::nop();
        }

        panic!("This is an intentional panic.");
    }
}

#[inline(never)]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    rprintln!("{}", info);
    loop {} // You might need a compiler fence in here.
}

And in the open rttui view you should see the panic.

Example panic logged to your channel

We intentionally ship no default panic handler, so that you may choose the channel of your choice to log the panic to.

So how do I get more channels, you might ask. Read on!

All the channels!

You can define multiple channels via a provided macro as seen in this snippet

#![no_main]
#![no_std]

use microbit as _;
use panic_halt as _;

use core::fmt::Write;
use cortex_m_rt::entry;

use rtt_target::rtt_init;

#[entry]
fn main() -> ! {
    let channels = rtt_init! {
        up: {
            0: {
                size: 512
                mode: BlockIfFull
                name: "Up zero"
            }
            1: {
                size: 128
                name: "Up one"
            }
            2: {
                size: 128
                name: "Up two"
            }
        }
        down: {
            0: {
                size: 512
                mode: BlockIfFull
                name: "Down zero"
            }
        }
    };

    let mut output2 = channels.up.1;
    writeln!(
        output2,
        "Hi! I will turn anything you type on channel 0 into upper case."
    )
    .ok();

    let mut output = channels.up.0;
    let mut log = channels.up.2;
    let mut input = channels.down.0;
    let mut buf = [0u8; 512];
    let mut count: u8 = 0;

    loop {
        let bytes = input.read(&mut buf[..]);
        if bytes > 0 {
            for c in buf.iter_mut() {
                c.make_ascii_uppercase();
            }

            let mut p = 0;
            while p < bytes {
                p += output.write(&buf[p..bytes]);
            }
        }

        writeln!(log, "Messsge no. {}/{}", count, bytes).ok();

        count += 1;

        for _ in 0..1_000_000 {
            cortex_m::asm::nop();
        }
    }
}

In this example we define three up channels and one down channel. The third up channel continuously logs, the second prints just a single info message and what the first one does, you'll figure it when you examine channel two ;)

On the host side it looks like

Output of chree channels

As you can observe, we see all three up channels. You can switch to and from them with the F-keys. The down channel will automatically be associated with the corresponding up channel and an input field will automatically be displayed for channels with a corresponding down channel. This is done via the channel number, which must be the same for the up and down channels. This is the default rttui behavior and can be configured. RTT itself can handle any up/down numbers combination.

Now you should be able to debug conveniently. Happy coding!