A persistent defmt logger that survives resets.
This crate provides a defmt::global_logger that stores logged messages in a ring buffer,
similar to defmt-brtt. Unlike defmt-brtt, the buffer state persists across resets, so panic
messages and logs leading up to them can be transmitted after the device restarts. Basically
the combination of panic-persist (see Panic Handler section) and defmt-brtt.
Reserve a memory region in your linker script for the persist buffer. This region must be outside any section to prevent program initialization from zeroing or modifying it on boot.
Define __defmt_persist_start and __defmt_persist_end symbols pointing to this region.
Example memory.x before modification:
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
RAM : ORIGIN = 0x20000000, LENGTH = 64K
}
After modification to reserve a 1K region:
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
RAM : ORIGIN = 0x20000000, LENGTH = 63K
DEFMT_PERSIST : ORIGIN = 0x2000FC00, LENGTH = 1K
}
__defmt_persist_start = ORIGIN(DEFMT_PERSIST);
__defmt_persist_end = ORIGIN(DEFMT_PERSIST) + LENGTH(DEFMT_PERSIST);
Then call init early in your program:
let Ok(mut consumer) = defmt_persist::init() else {
panic!("init failed");
};Use the returned Consumer to read and transmit buffered logs:
# fn transmit(_: (&[u8], &[u8])) -> usize { 0 }
# fn example(mut consumer: defmt_persist::Consumer<'_>) {
// Drain any persisted logs from before the reset
while !consumer.is_empty() {
let grant = consumer.read();
let len = transmit(grant.bufs()); // It's OK to not empty the entire grant, data is not lost
grant.release(len);
}
# }To capture panic messages that survive resets, define a panic handler that logs via defmt before resetting:
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
defmt::error!("{}", defmt::Display2Format(info));
cortex_m::peripheral::SCB::sys_reset(); // Or hardfault if it should go via fault handlers.
}After reset, the panic message will be available in the persist buffer and can be read using
the Consumer from init(). See panic_test.rs for a
complete example.
Alternatively, panic-probe can be used for
hardfault-on-panic behavior.
If your system uses a bootloader, the bootloader's linker script must also reserve/don't touch
the same memory region. Otherwise, the bootloader may use this memory for its stack or heap,
corrupting the persisted logs before the application starts. The bootloader can also call
init to read and transmit persisted logs from the application before launching it.
rtt: Also output logs via RTT (default: enabled)async-await: Enable async API for waiting on new data (default: enabled)ecc: Add ECC cache flush for MCUs with ECC-protected RAM (32-bit or 64-bit), e.g. STM32H7/H5 (default: enabled)
Run the library unit tests:
cargo test --all-featuresRun the full QEMU-based integration testsuite (requires qemu-system-arm):
cargo xtask test [example_name]This runs tests for persistence across resets, buffer corruption recovery, async API, and ring buffer wraparound.
To run a single example in QEMU during development:
cargo xtask qemu <example_name>Licensed under either of Apache License, Version 2.0 or MIT license at your option.