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>
135 lines
3.5 KiB
Rust
135 lines
3.5 KiB
Rust
// Copyright © 2018–2025 Trevor Spiteri
|
||
|
||
// This library is free software: you can redistribute it and/or
|
||
// modify it under the terms of either
|
||
//
|
||
// * the Apache License, Version 2.0 or
|
||
// * the MIT License
|
||
//
|
||
// at your option.
|
||
//
|
||
// You should have recieved copies of the Apache License and the MIT
|
||
// License along with the library. If not, see
|
||
// <https://www.apache.org/licenses/LICENSE-2.0> and
|
||
// <https://opensource.org/licenses/MIT>.
|
||
|
||
use core::cell::Cell;
|
||
use core::fmt::{Debug, Formatter, Result as FmtResult, Write};
|
||
#[cfg(target_has_atomic = "32")]
|
||
use core::sync::atomic::{AtomicU32, Ordering};
|
||
|
||
// This is an ugly hack to check whether a `Formatter` has `debug_lower_hex` or
|
||
// `debug_upper_hex`.
|
||
//
|
||
// We do a dummy write with format string "{:x?}" to get `debug_lower_hex`, and
|
||
// a dummy write with format string "{:X?}" to get `debug_upper_hex`. Each time,
|
||
// we get the flags using the deprecated `Formatter::flags`.
|
||
//
|
||
// If AtomicU32 is supported, we cache the flags.
|
||
|
||
#[cfg(target_has_atomic = "32")]
|
||
static LOWER_FLAGS: AtomicU32 = AtomicU32::new(0);
|
||
#[cfg(target_has_atomic = "32")]
|
||
static UPPER_FLAGS: AtomicU32 = AtomicU32::new(0);
|
||
|
||
fn get_flags(f: &Formatter) -> u32 {
|
||
#[allow(deprecated)]
|
||
f.flags()
|
||
}
|
||
|
||
struct StoreFlags(Cell<u32>);
|
||
|
||
impl Debug for StoreFlags {
|
||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||
self.0.set(get_flags(f));
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
struct Discard;
|
||
|
||
impl Write for Discard {
|
||
fn write_str(&mut self, _s: &str) -> FmtResult {
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
pub enum IsDebugHex {
|
||
No,
|
||
Lower,
|
||
Upper,
|
||
}
|
||
|
||
pub fn is_debug_hex(f: &Formatter) -> IsDebugHex {
|
||
let flags = get_flags(f);
|
||
// avoid doing unnecessary work if flags is zero
|
||
if flags == 0 {
|
||
return IsDebugHex::No;
|
||
}
|
||
|
||
let (lower_mask, upper_mask) = get_flag_masks();
|
||
|
||
if flags & lower_mask != 0 {
|
||
IsDebugHex::Lower
|
||
} else if flags & upper_mask != 0 {
|
||
IsDebugHex::Upper
|
||
} else {
|
||
IsDebugHex::No
|
||
}
|
||
}
|
||
|
||
#[cfg(target_has_atomic = "32")]
|
||
fn load_cache() -> Option<(u32, u32)> {
|
||
let cached_lower = LOWER_FLAGS.load(Ordering::Relaxed);
|
||
let cached_upper = UPPER_FLAGS.load(Ordering::Relaxed);
|
||
|
||
if cached_lower == u32::MAX || cached_upper == u32::MAX {
|
||
// error was detected, so no need to repeat the error generation
|
||
return Some((0, 0));
|
||
}
|
||
if cached_lower == 0 || cached_upper == 0 {
|
||
return None;
|
||
}
|
||
Some((cached_lower, cached_upper))
|
||
}
|
||
|
||
#[cfg(target_has_atomic = "32")]
|
||
fn store_cache(mut lower: u32, mut upper: u32) {
|
||
if lower == 0 || upper == 0 {
|
||
lower = u32::MAX;
|
||
upper = u32::MAX;
|
||
}
|
||
LOWER_FLAGS.store(lower, Ordering::Relaxed);
|
||
UPPER_FLAGS.store(upper, Ordering::Relaxed);
|
||
}
|
||
|
||
#[cfg(not(target_has_atomic = "32"))]
|
||
fn load_cache() -> Option<(u32, u32)> {
|
||
None
|
||
}
|
||
|
||
#[cfg(not(target_has_atomic = "32"))]
|
||
fn store_cache(_lower: u32, _upper: u32) {}
|
||
|
||
fn get_flag_masks() -> (u32, u32) {
|
||
if let Some(cached) = load_cache() {
|
||
return cached;
|
||
}
|
||
|
||
let store_flags = StoreFlags(Cell::new(0));
|
||
if write!(Discard, "{store_flags:x?}").is_err() {
|
||
store_cache(u32::MAX, u32::MAX);
|
||
return (0, 0);
|
||
}
|
||
let lower_flags = store_flags.0.get();
|
||
if write!(Discard, "{store_flags:X?}").is_err() {
|
||
store_cache(u32::MAX, u32::MAX);
|
||
return (0, 0);
|
||
}
|
||
let upper_flags = store_flags.0.get();
|
||
let lower_mask = lower_flags & !upper_flags;
|
||
let upper_mask = upper_flags & !lower_flags;
|
||
store_cache(lower_mask, upper_mask);
|
||
(lower_mask, upper_mask)
|
||
}
|