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:
17624 files changed
+4564928
-38
No files matched your search
@@ -0,0 +1,66 @@
|
||||
use core::cell::Cell;
|
||||
use core::task::Waker;
|
||||
|
||||
use crate::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex};
|
||||
use crate::blocking_mutex::Mutex;
|
||||
|
||||
/// Utility struct to register and wake a waker.
|
||||
/// If a waker is registered, registering another waker will replace the previous one without waking it.
|
||||
/// Intended to wake a task from an interrupt. Therefore, it is generally not expected,
|
||||
/// that multiple tasks register try to register a waker simultaneously.
|
||||
pub struct GenericAtomicWaker<M: RawMutex> {
|
||||
waker: Mutex<M, Cell<Option<Waker>>>,
|
||||
}
|
||||
|
||||
impl<M: RawMutex> GenericAtomicWaker<M> {
|
||||
/// Create a new `AtomicWaker`.
|
||||
pub const fn new(mutex: M) -> Self {
|
||||
Self {
|
||||
waker: Mutex::const_new(mutex, Cell::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a waker. Overwrites the previous waker, if any.
|
||||
pub fn register(&self, w: &Waker) {
|
||||
self.waker.lock(|cell| {
|
||||
cell.set(match cell.replace(None) {
|
||||
Some(w2) if (w2.will_wake(w)) => Some(w2),
|
||||
_ => Some(w.clone()),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Wake the registered waker, if any.
|
||||
pub fn wake(&self) {
|
||||
self.waker.lock(|cell| {
|
||||
if let Some(w) = cell.replace(None) {
|
||||
w.wake_by_ref();
|
||||
cell.set(Some(w));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility struct to register and wake a waker.
|
||||
pub struct AtomicWaker {
|
||||
waker: GenericAtomicWaker<CriticalSectionRawMutex>,
|
||||
}
|
||||
|
||||
impl AtomicWaker {
|
||||
/// Create a new `AtomicWaker`.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
waker: GenericAtomicWaker::new(CriticalSectionRawMutex::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a waker. Overwrites the previous waker, if any.
|
||||
pub fn register(&self, w: &Waker) {
|
||||
self.waker.register(w);
|
||||
}
|
||||
|
||||
/// Wake the registered waker, if any.
|
||||
pub fn wake(&self) {
|
||||
self.waker.wake();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use core::ptr;
|
||||
use core::ptr::NonNull;
|
||||
use core::sync::atomic::{AtomicPtr, Ordering};
|
||||
use core::task::Waker;
|
||||
|
||||
/// Utility struct to register and wake a waker.
|
||||
/// If a waker is registered, registering another waker will replace the previous one without waking it.
|
||||
/// The intended use case is to wake tasks from interrupts. Therefore, it is generally not expected,
|
||||
/// that multiple tasks register try to register a waker simultaneously.
|
||||
#[derive(Debug)]
|
||||
pub struct AtomicWaker {
|
||||
waker: AtomicPtr<()>,
|
||||
}
|
||||
|
||||
impl AtomicWaker {
|
||||
/// Create a new `AtomicWaker`.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
waker: AtomicPtr::new(ptr::null_mut()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a waker. Overwrites the previous waker, if any.
|
||||
pub fn register(&self, w: &Waker) {
|
||||
self.waker.store(w.as_turbo_ptr().as_ptr() as _, Ordering::Release);
|
||||
}
|
||||
|
||||
/// Wake the registered waker, if any.
|
||||
pub fn wake(&self) {
|
||||
if let Some(ptr) = NonNull::new(self.waker.load(Ordering::Acquire)) {
|
||||
unsafe { Waker::from_turbo_ptr(ptr) }.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
//! Async low-level wait queues
|
||||
|
||||
#[cfg_attr(feature = "turbowakers", path = "atomic_waker_turbo.rs")]
|
||||
mod atomic_waker;
|
||||
pub use atomic_waker::*;
|
||||
|
||||
mod waker_registration;
|
||||
pub use waker_registration::*;
|
||||
|
||||
mod multi_waker;
|
||||
pub use multi_waker::*;
|
||||
@@ -0,0 +1,63 @@
|
||||
use core::task::Waker;
|
||||
|
||||
use heapless::Vec;
|
||||
|
||||
/// Utility struct to register and wake multiple wakers.
|
||||
/// Queue of wakers with a maximum length of `N`.
|
||||
/// Intended for waking multiple tasks.
|
||||
#[derive(Debug)]
|
||||
pub struct MultiWakerRegistration<const N: usize> {
|
||||
wakers: Vec<Waker, N>,
|
||||
}
|
||||
|
||||
impl<const N: usize> MultiWakerRegistration<N> {
|
||||
/// Create a new empty instance
|
||||
pub const fn new() -> Self {
|
||||
Self { wakers: Vec::new() }
|
||||
}
|
||||
|
||||
/// Register a waker.
|
||||
///
|
||||
/// If the buffer is full, [wakes all the wakers](Self::wake), clears its buffer and registers the waker.
|
||||
pub fn register(&mut self, w: &Waker) {
|
||||
// If we already have some waker that wakes the same task as `w`, do nothing.
|
||||
// This avoids cloning wakers, and avoids unnecessary mass-wakes.
|
||||
for w2 in &self.wakers {
|
||||
if w.will_wake(w2) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if self.wakers.is_full() {
|
||||
// All waker slots were full. It's a bit inefficient, but we can wake everything.
|
||||
// Any future that is still active will simply reregister.
|
||||
// This won't happen a lot, so it's ok.
|
||||
self.wake();
|
||||
}
|
||||
|
||||
if self.wakers.push(w.clone()).is_err() {
|
||||
// This can't happen unless N=0
|
||||
// (Either `wakers` wasn't full, or it was in which case `wake()` empied it)
|
||||
panic!("tried to push a waker to a zero-length MultiWakerRegistration")
|
||||
}
|
||||
}
|
||||
|
||||
/// Wake all registered wakers. This clears the buffer
|
||||
pub fn wake(&mut self) {
|
||||
// heapless::Vec has no `drain()`, do it unsafely ourselves...
|
||||
|
||||
// First set length to 0, without dropping the contents.
|
||||
// This is necessary for soundness: if wake() panics and we're using panic=unwind.
|
||||
// Setting len=0 upfront ensures other code can't observe the vec in an inconsistent state.
|
||||
// (it'll leak wakers, but that's not UB)
|
||||
let len = self.wakers.len();
|
||||
unsafe { self.wakers.set_len(0) }
|
||||
|
||||
for i in 0..len {
|
||||
// Move a waker out of the vec.
|
||||
let waker = unsafe { self.wakers.as_mut_ptr().add(i).read() };
|
||||
// Wake it by value, which consumes (drops) it.
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use core::mem;
|
||||
use core::task::Waker;
|
||||
|
||||
/// Utility struct to register and wake a waker.
|
||||
/// If a waker is registered, registering another waker will replace the previous one.
|
||||
/// The previous waker will be woken in this case, giving it a chance to reregister itself.
|
||||
/// Although it is possible to wake multiple tasks this way,
|
||||
/// this will cause them to wake each other in a loop registering themselves.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WakerRegistration {
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
impl WakerRegistration {
|
||||
/// Create a new `WakerRegistration`.
|
||||
pub const fn new() -> Self {
|
||||
Self { waker: None }
|
||||
}
|
||||
|
||||
/// Register a waker. Overwrites the previous waker, if any.
|
||||
pub fn register(&mut self, w: &Waker) {
|
||||
match self.waker {
|
||||
// Optimization: If both the old and new Wakers wake the same task, we can simply
|
||||
// keep the old waker, skipping the clone. (In most executor implementations,
|
||||
// cloning a waker is somewhat expensive, comparable to cloning an Arc).
|
||||
Some(ref w2) if (w2.will_wake(w)) => {}
|
||||
_ => {
|
||||
// clone the new waker and store it
|
||||
if let Some(old_waker) = mem::replace(&mut self.waker, Some(w.clone())) {
|
||||
// We had a waker registered for another task. Wake it, so the other task can
|
||||
// reregister itself if it's still interested.
|
||||
//
|
||||
// If two tasks are waiting on the same thing concurrently, this will cause them
|
||||
// to wake each other in a loop fighting over this WakerRegistration. This wastes
|
||||
// CPU but things will still work.
|
||||
//
|
||||
// If the user wants to have two tasks waiting on the same thing they should use
|
||||
// a more appropriate primitive that can store multiple wakers.
|
||||
old_waker.wake()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wake the registered waker, if any.
|
||||
pub fn wake(&mut self) {
|
||||
if let Some(w) = self.waker.take() {
|
||||
w.wake()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if a waker is currently registered
|
||||
pub fn occupied(&self) -> bool {
|
||||
self.waker.is_some()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user