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

+33
View File
@@ -0,0 +1,33 @@
//! This is not an example; this is a link-pass test
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt as rt;
extern crate panic_halt;
use core::ptr;
use rt::entry;
static mut BSS1: u16 = 0;
static mut BSS2: u8 = 0;
static mut DATA1: u8 = 1;
static mut DATA2: u16 = 1;
static RODATA1: &[u8; 3] = b"012";
static RODATA2: &[u8; 2] = b"34";
#[entry]
fn main() -> ! {
unsafe {
let _bss1 = ptr::read_volatile(ptr::addr_of!(BSS1));
let _bss2 = ptr::read_volatile(ptr::addr_of!(BSS2));
let _data1 = ptr::read_volatile(ptr::addr_of!(DATA1));
let _data2 = ptr::read_volatile(ptr::addr_of!(DATA2));
let _rodata1 = ptr::read_volatile(ptr::addr_of!(RODATA1));
let _rodata2 = ptr::read_volatile(ptr::addr_of!(RODATA2));
}
loop {}
}
+27
View File
@@ -0,0 +1,27 @@
//! using `#[cfg]` on `static` shouldn't cause compile errors
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
// This example uses an undefined cfg, `cfg(never)`
#![allow(unexpected_cfgs)]
extern crate cortex_m_rt as rt;
extern crate panic_halt;
use rt::{entry, exception};
#[entry]
fn main() -> ! {
#[cfg(never)]
static mut COUNT: u32 = 0;
loop {}
}
#[exception]
fn SysTick() {
#[cfg(never)]
static mut FOO: u32 = 0;
}
+31
View File
@@ -0,0 +1,31 @@
//! This is not an example; this is a linker overflow detection test
//! which should fail to link due to .data overflowing FLASH.
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt as rt;
extern crate panic_halt;
use core::ptr;
use rt::entry;
// This large static array uses most of .rodata
const RODATA_SIZE: usize = 250 * 1024;
static RODATA: [u8; RODATA_SIZE] = [1u8; RODATA_SIZE];
// This large mutable array causes .data to use the rest of FLASH
// without also overflowing RAM.
const DATA_SIZE: usize = 8 * 1024;
static mut DATA: [u8; DATA_SIZE] = [1u8; DATA_SIZE];
#[entry]
fn main() -> ! {
unsafe {
let _bigdata: u8 = ptr::read_volatile(ptr::addr_of!(RODATA) as *const u8);
let _bigdata: u8 = ptr::read_volatile(ptr::addr_of!(DATA) as *const u8);
}
loop {}
}
+37
View File
@@ -0,0 +1,37 @@
//! Manually create the interrupts portion of the vector table
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt as rt;
extern crate panic_halt;
use rt::entry;
#[entry]
fn main() -> ! {
loop {}
}
// interrupts portion of the vector table
#[repr(C)]
pub union Vector {
handler: unsafe extern "C" fn(),
reserved: usize,
}
extern "C" {
fn WWDG();
fn PVD();
}
#[allow(unsafe_code)]
#[link_section = ".vector_table.interrupts"]
#[no_mangle]
pub static __INTERRUPTS: [Vector; 3] = [
Vector { handler: WWDG },
Vector { reserved: 0 },
Vector { handler: PVD },
];
@@ -0,0 +1,18 @@
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt;
extern crate panic_halt;
use cortex_m_rt::{entry, exception};
#[entry]
fn foo() -> ! {
loop {}
}
#[exception]
unsafe fn DefaultHandler(_irqn: i16) -> ! {
loop {}
}
+18
View File
@@ -0,0 +1,18 @@
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt;
extern crate panic_halt;
use cortex_m_rt::{entry, exception};
#[entry]
fn foo() -> ! {
loop {}
}
#[exception]
fn SysTick() -> ! {
loop {}
}
+20
View File
@@ -0,0 +1,20 @@
//! `static mut` variables local to the entry point are safe to use
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt as rt;
extern crate panic_halt;
use rt::entry;
#[entry]
fn main() -> ! {
static mut COUNT: u32 = 0;
loop {
*COUNT += 1;
}
}
+31
View File
@@ -0,0 +1,31 @@
//! This is not an example; this is a linker test that ensures
//! that the jump from HardFault to _HardFault doesn't exceed
//! the 2kB limit of the branch instruction.
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt;
extern crate panic_halt;
use core::arch::asm;
use cortex_m_rt::{entry, exception, ExceptionFrame};
// This defines both `HardFault` and `_HardFault`. Both should have
// link_section attributes placing them at the end of the .text section,
// close to each other. If one of them is missing that attribute, they
// will end up separated by `foo`, which will make the linker fail.
#[exception(trampoline = true)]
unsafe fn HardFault(_ef: &ExceptionFrame) -> ! {
loop {}
}
#[entry]
fn foo() -> ! {
unsafe {
// 2kB of NOP instructions to make the function artificially larger
asm!(".fill 1024,2,0xbf00",);
}
loop {}
}
+13
View File
@@ -0,0 +1,13 @@
//! Directly plug a `main` symbol instead of using `#[entry]`
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt as rt;
extern crate panic_halt;
#[no_mangle]
pub unsafe extern "C" fn main() -> ! {
loop {}
}
+17
View File
@@ -0,0 +1,17 @@
//! Minimal `cortex-m-rt` based program
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt as rt;
extern crate panic_halt;
use rt::entry;
// the program entry point
#[entry]
fn main() -> ! {
loop {}
}
+29
View File
@@ -0,0 +1,29 @@
//! How to override the hard fault exception handler and the default exception handler
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m;
extern crate cortex_m_rt as rt;
extern crate panic_halt;
use cortex_m::asm;
use rt::{entry, exception, ExceptionFrame};
#[entry]
fn main() -> ! {
loop {}
}
#[exception]
unsafe fn DefaultHandler(_irqn: i16) {
asm::bkpt();
}
#[exception]
unsafe fn HardFault(_ef: &ExceptionFrame) -> ! {
asm::bkpt();
loop {}
}
+20
View File
@@ -0,0 +1,20 @@
//! `cortex-m-rt` based program with a function run before RAM is initialized.
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt as rt;
extern crate panic_halt;
use rt::{entry, pre_init};
#[pre_init]
unsafe fn disable_watchdog() {
// Do what you need to disable the watchdog.
}
#[entry]
fn main() -> ! {
loop {}
}
+34
View File
@@ -0,0 +1,34 @@
#![no_main]
#![no_std]
use core::{
fmt::Write,
sync::atomic::{AtomicU32, Ordering},
};
static DATA_VAL: AtomicU32 = AtomicU32::new(1234);
static BSS_VAL: AtomicU32 = AtomicU32::new(0);
#[cortex_m_rt::entry]
fn main() -> ! {
let x = 42;
loop {
let mut hstdout = cortex_m_semihosting::hio::hstdout().unwrap();
// check that .data and .bss were initialised OK
if DATA_VAL.load(Ordering::Relaxed) == 1234 && BSS_VAL.load(Ordering::Relaxed) == 0 {
_ = writeln!(hstdout, "x = {}", x);
}
cortex_m_semihosting::debug::exit(cortex_m_semihosting::debug::EXIT_SUCCESS);
}
}
// Define a panic handler that uses semihosting to exit immediately,
// so that any panics cause qemu to quit instead of hang.
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
loop {
cortex_m_semihosting::debug::exit(cortex_m_semihosting::debug::EXIT_FAILURE);
}
}
+24
View File
@@ -0,0 +1,24 @@
//! Preserving state across executions of an exception handler
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt as rt;
extern crate panic_halt;
use rt::{entry, exception};
#[entry]
fn main() -> ! {
loop {}
}
// exception handler with state
#[exception]
fn SysTick() {
static mut STATE: u32 = 0;
*STATE += 1;
}
+16
View File
@@ -0,0 +1,16 @@
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt;
extern crate panic_halt;
use cortex_m_rt::{entry, exception};
#[entry]
fn foo() -> ! {
loop {}
}
#[exception]
unsafe fn DefaultHandler(_irqn: i16) {}
+13
View File
@@ -0,0 +1,13 @@
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt;
extern crate panic_halt;
use cortex_m_rt::entry;
#[entry]
unsafe fn foo() -> ! {
loop {}
}
+16
View File
@@ -0,0 +1,16 @@
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt;
extern crate panic_halt;
use cortex_m_rt::{entry, exception};
#[entry]
fn foo() -> ! {
loop {}
}
#[exception]
unsafe fn SysTick() {}
+18
View File
@@ -0,0 +1,18 @@
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt;
extern crate panic_halt;
use cortex_m_rt::{entry, exception, ExceptionFrame};
#[entry]
fn foo() -> ! {
loop {}
}
#[exception]
unsafe fn HardFault(_ef: &ExceptionFrame) -> ! {
loop {}
}
+36
View File
@@ -0,0 +1,36 @@
//! Checks that the declared unsafety is respected by the attributes
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate cortex_m_rt;
extern crate panic_halt;
use cortex_m_rt::{entry, exception, ExceptionFrame};
#[entry]
unsafe fn main() -> ! {
foo();
loop {}
}
#[exception]
unsafe fn DefaultHandler(_irqn: i16) {
foo();
}
#[exception]
unsafe fn HardFault(_ef: &ExceptionFrame) -> ! {
foo();
loop {}
}
#[exception]
unsafe fn SysTick() {
foo();
}
unsafe fn foo() {}
+51
View File
@@ -0,0 +1,51 @@
//! Tests that a crate can still build with all warnings enabled.
//!
//! The code generated by the `cortex-m-rt` macros might need to manually
//! `#[allow]` some of them (even though Rust does that by default for a few
//! warnings too).
#![no_std]
#![no_main]
#![deny(warnings, missing_docs, rust_2018_idioms)]
extern crate cortex_m_rt;
extern crate panic_halt;
use cortex_m_rt::{entry, exception, interrupt, pre_init, ExceptionFrame};
#[allow(non_camel_case_types)]
enum interrupt {
INT,
}
extern "C" {
fn INT();
}
#[repr(C)]
union Vector {
#[allow(dead_code)]
handler: unsafe extern "C" fn(),
}
#[link_section = ".vector_table.interrupts"]
#[no_mangle]
#[used]
static __INTERRUPTS: [Vector; 1] = [Vector { handler: INT }];
/// Dummy interrupt.
#[interrupt]
fn INT() {}
#[exception]
fn HardFault(_eh: &ExceptionFrame) -> ! {
loop {}
}
#[entry]
fn main() -> ! {
loop {}
}
#[pre_init]
unsafe fn pre_init() {}