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.
- rtt_
init_ defmt defmt
Initializes RTT with a single up channel and sets it as the defmt channel for the printing macros. - rtt_
init_ log log
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§
- init_
logger log
Init the logger with maximum level (Trace). - Init the logger with a specific level.
- set_
defmt_ channel defmt
Sets the channel to use fordefmt
macros. - Allows accessing the currently set print channel.