Crate rtt_target

Source
Expand description

Target side implementation of the RTT (Real-Time Transfer) I/O protocol

RTT implements input and output to/from a debug probe using in-memory ring buffers and memory polling. This enables debug logging from the microcontroller with minimal delays and no blocking, making it usable even in real-time applications where e.g. semihosting delays cannot be tolerated.

§Hardware support

This crate is platform agnostic and can be used on any chip that supports background memory access via its debug interface. The printing macros require a critical section which is platform-dependent.

To interface with RTT from the host computer, a debug probe such as an ST-Link or J-Link is required. The normal debug protocol (e.g. SWD) is used to access RTT, so no extra connections such as SWO pins are needed.

§Initialization

RTT must be initialized at the start of your program using one of the init macros. See the macros for more details.

The initialization macros return channel objects that can be used for writing and reading. Different channel objects can safely be used concurrently in different contexts without locking. In an interrupt-based application with realtime constraints you could use a separate channel for every interrupt context to allow for lock-free logging.

§Channels and virtual terminals

RTT supports multiple channels in both directions. Up channels go from target to host, and down channels go from host to target. Each channel is identified by its direction and number.

By convention channel 0 is reserved for terminal use. In the up direction there is a set of escape sequences that further enable the single channel to be treated as up to 16 virtual terminals. This can be used to separate different types of messages (for example, log levels) from each other without having to allocate memory for multiple buffers. As a downside, multiple threads cannot write to the same channel at once, even if using different virtual terminal numbers, so access has to be synchronized. Down channel 0 is conventionally used for keyboard input.

Note: Some host side programs only display channel 0 by default, so to see the other channels you might need to configure them appropriately.

The other channels can be used to either enable concurrent use from multiple sources without locking, or to send e.g. binary data in either direction.

Channel 0 can also be used for arbitrary data, but most tools expect it to be plain text.

§Channel modes

By default, channels start in NoBlockSkip mode, which discards data if the buffer is full. This enables RTT to not crash the application if there is no debug probe attached or if the host is not reading the buffers. However if the application outputs faster than the host can read (which is easy to do, because writing is very fast), messages will be lost. Channels can be set to blocking mode if this is desirable, however in that case the application will likely freeze when the buffer fills up if a debugger is not attached.

The channel mode can also be changed on the fly by the debug probe. Therefore it may be advantageous to use a non-blocking mode in your microcontroller code, and set a blocking mode as needed when debugging. That way you will never end up with an application that freezes without a debugger connected.

§Defmt integration

The defmt crate can be used to format messages in a way that is more efficient and more informative than the standard format! macros. To use defmt with RTT, the defmt feature must be enabled, and the channel must be set up with set_defmt_channel or you can use the rtt_init_defmt macro to initialize rtt and defmt at once.

[dependencies]
defmt = { version = "0.3" }
rtt-target = { version = "0.6", features = ["defmt"] }

§Log integration

Rtt-target also supports integration with the log crate. The log feature must be enabled to configure a logger that forwards log messages to RTT. The logger can be initialized with rtt_init_log!.

use rtt_target::rtt_init_log;

fn main() -> ! {
   rtt_init_log!(); // Pass a log::LevelFilter as an argument to set the min log level different from Trace
   loop {
       log::info!("Hello, world!");
   }
}

§Plain Printing

For no-hassle output the rprint and rprintln macros are provided. They use a single down channel defined at initialization time, and a critical section for synchronization, and they therefore work exactly like the standard println style macros. They can be used from any context. The rtt_init_print convenience macro initializes printing on channel 0.

use rtt_target::{rtt_init_print, rprintln};

fn main() -> ! {
    rtt_init_print!();
    loop {
        rprintln!("Hello, world!");
    }
}

To use rtt functionality only in debug builds use macros prefixed with debug_*. They have exactly the same functionality as without debug - the only difference is that they are removed when built with --release. It’s safe to use debug_rprintln and debug_rprint even if rtt was initialized with rtt_init instead of debug_rtt_init.

Under the hood this uses the debug-assertions flag. Set this flag to true to include all debug macros also in release mode.

use rtt_target::{debug_rtt_init_print, debug_rprintln};

fn main() -> ! {
    debug_rtt_init_print!(); // nop in --release
    loop {
        debug_rprintln!("Hello, world!"); // not present in --release
    }
}

The macros also support an extended syntax to print to different RTT virtual terminals.

Please note that because a critical section is used, printing into a blocking channel will cause the application to block and freeze when the buffer is full.

§Reading

The following example shows how to set up the RTT to read simple input sent from the host to the target.

use rtt_target::{rtt_init_default, rprintln};

fn main() -> ! {
    let mode = loop {
        read = channels.down.0.read(&mut read_buf);
        for i in 0..read {
            match read_buf[i] as char {
                '0' => break 0,
                '1' => break 1,
                _ => {}
            }
        }
    };
}

Macros§

  • The same as rprint macro but works only in debug
  • The same as rprintln macro but works only in debug
  • The same as rtt_init macro but works only in debug
  • The same as rtt_init_default macro but works only in debug
  • The same as rtt_init_print macro but works only in debug
  • Print to RTT and return the value of a given expression for quick debugging. This is equivalent to Rust’s std::dbg!() macro.
  • Prints to the print RTT channel. Works just like the standard print.
  • Prints to the print RTT channel, with a newline. Works just like the standard println.
  • Initializes RTT with the specified channels. Channel numbers, buffer sizes and names can be defined.
  • Initializes RTT with default up/down channels.
  • Initializes RTT with a single up channel and sets it as the defmt channel for the printing macros.
  • Initializes RTT with a single up channel, sets it as the print channel for the printing macros and sets up a log backend with the given log level.
  • Initializes RTT with a single up channel and sets it as the print channel for the printing macros.

Structs§

  • RTT down (host to target) channel
  • An up channel that supports writing into multiple virtual terminals within the same buffer.
  • Formatted writing operation. Don’t store an instance of this, but rather create a new one for every write.
  • RTT up (target to host) channel
  • Writer for ufmt. Don’t store an instance of this, but rather create a new one for every write.

Enums§

  • Specifies what to do when a channel doesn’t have enough buffer space for a complete write.

Functions§