feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+216
View File
@@ -0,0 +1,216 @@
//! # RISC-­V Trace Encoder (TRACE)
//!
//! ## Overview
//! The high-performance CPU supports instruction trace interface through the
//! trace encoder. The trace encoder connects to HP CPU’s instruction trace
//! interface, compresses the information into smaller packets, and then stores
//! the packets in internal SRAM.
//!
//! In complex systems, understanding program execution flow is not
//! straightforward. This may be due to a number of factors, for example,
//! interactions with other cores, peripherals, real-time events, poor
//! implementations, or some combination of all of the above.
//!
//! It is hard to use a debugger to monitor the program execution flow of a
//! running system in real time, as this is intrusive and might affect the
//! running state. But providing visibility of program execution is important.
//!
//! That is where instruction trace comes in, which provides trace of the
//! program execution.
//!
//! ## Examples
//! ```rust, no_run
#![doc = crate::before_snippet!()]
//! # use esp_hal::trace::Trace;
//! let mut trace = Trace::new(peripherals.TRACE0);
//! let mut buffer = [0_u8; 1024];
//! trace.start_trace(&mut buffer);
//! // traced code
//!
//! // end traced code
//! let res = trace.stop_trace().unwrap();
//! // transfer the trace result to the host and decode it there
//! # }
//! ```
use crate::{
peripheral::{Peripheral, PeripheralRef},
peripherals::trace::RegisterBlock,
system::PeripheralClockControl,
};
/// Errors returned from [Trace::stop_trace]
#[derive(Debug, Clone, Copy)]
pub enum Error {
/// Attempted to stop a trace which had not been started yet
NotStarted,
}
/// Returned by [Trace::stop_trace]
#[derive(Debug, Clone, Copy)]
pub struct TraceResult {
/// Start index of the valid data
pub valid_start_index: usize,
/// Length of the valid data
pub valid_length: usize,
}
/// TRACE Encoder Instance
pub struct Trace<'d, T> {
peripheral: PeripheralRef<'d, T>,
buffer: Option<&'d mut [u8]>,
}
impl<'d, T> Trace<'d, T>
where
T: Instance,
{
/// Construct a new instance
pub fn new(peripheral: impl Peripheral<P = T> + 'd) -> Self {
crate::into_ref!(peripheral);
PeripheralClockControl::reset(crate::system::Peripheral::Trace0);
PeripheralClockControl::enable(crate::system::Peripheral::Trace0);
Self {
peripheral,
buffer: None,
}
}
/// Start tracing, writing data into the `buffer`
pub fn start_trace(&mut self, buffer: &'d mut [u8]) {
let reg_block = self.peripheral.register_block();
reg_block
.mem_start_addr()
.modify(|_, w| unsafe { w.mem_start_addr().bits(buffer.as_ptr() as *const _ as u32) });
reg_block.mem_end_addr().modify(|_, w| unsafe {
w.mem_end_addr()
.bits((buffer.as_ptr() as *const _ as u32) + (buffer.len() as u32))
});
reg_block
.mem_addr_update()
.write(|w| w.mem_current_addr_update().set_bit());
// won't set bit in int-raw without enabling
reg_block
.intr_ena()
.modify(|_, w| w.mem_full_intr_ena().set_bit());
// for now always use looping mode
reg_block
.trigger()
.write(|w| w.mem_loop().set_bit().restart_ena().set_bit());
reg_block.intr_clr().write(|w| {
w.fifo_overflow_intr_clr()
.set_bit()
.mem_full_intr_clr()
.set_bit()
});
self.buffer.replace(buffer);
reg_block.trigger().write(|w| w.on().set_bit());
}
/// Stop tracing
///
/// Be aware that valid data might not start at index 0 and you need to
/// account for wrapping when reading the data.
pub fn stop_trace(&mut self) -> Result<TraceResult, Error> {
let reg_block = self.peripheral.register_block();
reg_block
.trigger()
.write(|w| w.off().set_bit().restart_ena().clear_bit());
if self.buffer.is_none() {
return Err(Error::NotStarted);
}
let buffer = self.buffer.take().unwrap();
while !reg_block.fifo_status().read().fifo_empty().bit_is_set() {}
let overflow = reg_block.intr_raw().read().mem_full_intr_raw().bit();
let idx = if overflow {
reg_block
.mem_current_addr()
.read()
.mem_current_addr()
.bits()
- &buffer as *const _ as u32
} else {
0
};
let len = if overflow {
buffer.len()
} else {
reg_block
.mem_current_addr()
.read()
.mem_current_addr()
.bits() as usize
- buffer.as_ptr() as *const _ as usize
- 14 // there will be 14 zero bytes at the start
};
let mut valid = false;
let mut fourteen_zeroes = false;
let mut zeroes = 0;
let start_index = if !valid {
let mut i = 0;
loop {
let b = unsafe {
buffer
.as_ptr()
.add((i + idx as usize) % buffer.len())
.read_volatile()
};
if !valid {
if b == 0 {
zeroes += 1;
} else {
zeroes = 0;
}
if zeroes >= 14 {
fourteen_zeroes = true;
}
if fourteen_zeroes && b != 0 {
valid = true;
}
}
if valid {
break i;
}
i += 1;
}
} else {
0
};
Ok(TraceResult {
valid_start_index: start_index,
valid_length: len,
})
}
}
/// Trace peripheral instance
pub trait Instance: crate::private::Sealed {
/// Get a reference to the peripheral's underlying register block
fn register_block(&self) -> &RegisterBlock;
}
impl Instance for crate::peripherals::TRACE0 {
fn register_block(&self) -> &RegisterBlock {
self
}
}