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
Vendored
+289
@@ -0,0 +1,289 @@
|
||||
#![no_std]
|
||||
|
||||
pub mod result;
|
||||
|
||||
use result::Result;
|
||||
|
||||
/// Trait for enums of target-specific exception numbers.
|
||||
///
|
||||
/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available
|
||||
/// exceptions for a specific device. Alternatively, the `riscv` crate provides a default
|
||||
/// implementation for the RISC-V ISA. Each variant must convert to a `usize` of its exception number.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// * This trait must only be implemented on the `riscv` crate or on a PAC of a RISC-V target.
|
||||
/// * This trait must only be implemented on enums of exceptions.
|
||||
/// * Each enum variant must represent a distinct value (no duplicates are permitted),
|
||||
/// * Each enum variant must always return the same value (do not change at runtime).
|
||||
/// * All the exception numbers must be less than or equal to `MAX_EXCEPTION_NUMBER`.
|
||||
/// * `MAX_EXCEPTION_NUMBER` must coincide with the highest allowed exception number.
|
||||
pub unsafe trait ExceptionNumber: Copy {
|
||||
/// Highest number assigned to an exception.
|
||||
const MAX_EXCEPTION_NUMBER: usize;
|
||||
|
||||
/// Converts an exception to its corresponding number.
|
||||
fn number(self) -> usize;
|
||||
|
||||
/// Tries to convert a number to a valid exception.
|
||||
fn from_number(value: usize) -> Result<Self>;
|
||||
}
|
||||
|
||||
/// Trait for enums of target-specific interrupt numbers.
|
||||
///
|
||||
/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available
|
||||
/// interrupts for a specific device. Alternatively, the `riscv` crate provides a default
|
||||
/// implementation for the RISC-V ISA. Each variant must convert to a `usize` of its interrupt number.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// * This trait must only be implemented on the `riscv` crate or on a PAC of a RISC-V target.
|
||||
/// * This trait must only be implemented on enums of interrupts.
|
||||
/// * Each enum variant must represent a distinct value (no duplicates are permitted),
|
||||
/// * Each enum variant must always return the same value (do not change at runtime).
|
||||
/// * All the interrupt numbers must be less than or equal to `MAX_INTERRUPT_NUMBER`.
|
||||
/// * `MAX_INTERRUPT_NUMBER` must coincide with the highest allowed interrupt number.
|
||||
pub unsafe trait InterruptNumber: Copy {
|
||||
/// Highest number assigned to an interrupt source.
|
||||
const MAX_INTERRUPT_NUMBER: usize;
|
||||
|
||||
/// Converts an interrupt source to its corresponding number.
|
||||
fn number(self) -> usize;
|
||||
|
||||
/// Tries to convert a number to a valid interrupt.
|
||||
fn from_number(value: usize) -> Result<Self>;
|
||||
}
|
||||
|
||||
/// Marker trait for enums of target-specific core interrupt numbers.
|
||||
///
|
||||
/// Core interrupts are interrupts are retrieved from the `mcause` CSR. Usually, vectored mode is
|
||||
/// only available for core interrupts. The `riscv` crate provides a default implementation for
|
||||
/// the RISC-V ISA. However, a PAC may override the default implementation if the target has a
|
||||
/// different interrupt numbering scheme (e.g., ESP32C3).
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Each enum variant must represent a valid core interrupt number read from the `mcause` CSR.
|
||||
pub unsafe trait CoreInterruptNumber: InterruptNumber {}
|
||||
|
||||
/// Marker trait for enums of target-specific external interrupt numbers.
|
||||
///
|
||||
/// External interrupts are interrupts caused by external sources (e.g., GPIO, UART, SPI).
|
||||
/// External interrupts are **not** retrieved from the `mcause` CSR.
|
||||
/// Instead, RISC-V processors have a single core interrupt for all external interrupts.
|
||||
/// An additional peripheral (e.g., PLIC) is used to multiplex the external interrupts.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Each enum variant must represent a valid external interrupt number.
|
||||
pub unsafe trait ExternalInterruptNumber: InterruptNumber {}
|
||||
|
||||
/// Trait for enums of priority levels.
|
||||
///
|
||||
/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available
|
||||
/// priority numbers for a specific device. Each variant must convert to a `usize` of its priority level.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// * This trait must only be implemented on a PAC of a RISC-V target.
|
||||
/// * This trait must only be implemented on enums of priority levels.
|
||||
/// * Each enum variant must represent a distinct value (no duplicates are permitted).
|
||||
/// * Each enum variant must always return the same value (do not change at runtime).
|
||||
/// * All the priority level numbers must be less than or equal to `MAX_PRIORITY_NUMBER`.
|
||||
/// * `MAX_PRIORITY_NUMBER` must coincide with the highest allowed priority number.
|
||||
pub unsafe trait PriorityNumber: Copy {
|
||||
/// Number assigned to the highest priority level.
|
||||
const MAX_PRIORITY_NUMBER: usize;
|
||||
|
||||
/// Converts a priority level to its corresponding number.
|
||||
fn number(self) -> usize;
|
||||
|
||||
/// Tries to convert a number to a valid priority level.
|
||||
fn from_number(value: usize) -> Result<Self>;
|
||||
}
|
||||
|
||||
/// Trait for enums of HART identifiers.
|
||||
///
|
||||
/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available
|
||||
/// HARTs for a specific device. Each variant must convert to a `usize` of its HART ID number.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// * This trait must only be implemented on a PAC of a RISC-V target.
|
||||
/// * This trait must only be implemented on enums of HART IDs.
|
||||
/// * Each enum variant must represent a distinct value (no duplicates are permitted),
|
||||
/// * Each anum variant must always return the same value (do not change at runtime).
|
||||
/// * All the HART ID numbers must be less than or equal to `MAX_HART_ID_NUMBER`.
|
||||
/// * `MAX_HART_ID_NUMBER` must coincide with the highest allowed HART ID number.
|
||||
pub unsafe trait HartIdNumber: Copy {
|
||||
/// Highest number assigned to a context.
|
||||
const MAX_HART_ID_NUMBER: usize;
|
||||
|
||||
/// Converts a HART ID to its corresponding number.
|
||||
fn number(self) -> usize;
|
||||
|
||||
/// Tries to convert a number to a valid HART ID.
|
||||
fn from_number(value: usize) -> Result<Self>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::result::Error;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum Exception {
|
||||
E1 = 1,
|
||||
E3 = 3,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum Interrupt {
|
||||
I1 = 1,
|
||||
I2 = 2,
|
||||
I4 = 4,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum Priority {
|
||||
P0 = 0,
|
||||
P1 = 1,
|
||||
P2 = 2,
|
||||
P3 = 3,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum HartId {
|
||||
H0 = 0,
|
||||
H1 = 1,
|
||||
H2 = 2,
|
||||
}
|
||||
|
||||
unsafe impl ExceptionNumber for Exception {
|
||||
const MAX_EXCEPTION_NUMBER: usize = Self::E3 as usize;
|
||||
|
||||
#[inline]
|
||||
fn number(self) -> usize {
|
||||
self as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_number(number: usize) -> Result<Self> {
|
||||
match number {
|
||||
1 => Ok(Exception::E1),
|
||||
3 => Ok(Exception::E3),
|
||||
_ => Err(Error::InvalidVariant(number)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl InterruptNumber for Interrupt {
|
||||
const MAX_INTERRUPT_NUMBER: usize = Self::I4 as usize;
|
||||
|
||||
#[inline]
|
||||
fn number(self) -> usize {
|
||||
self as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_number(number: usize) -> Result<Self> {
|
||||
match number {
|
||||
1 => Ok(Interrupt::I1),
|
||||
2 => Ok(Interrupt::I2),
|
||||
4 => Ok(Interrupt::I4),
|
||||
_ => Err(Error::InvalidVariant(number)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl PriorityNumber for Priority {
|
||||
const MAX_PRIORITY_NUMBER: usize = Self::P3 as usize;
|
||||
|
||||
#[inline]
|
||||
fn number(self) -> usize {
|
||||
self as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_number(number: usize) -> Result<Self> {
|
||||
match number {
|
||||
0 => Ok(Priority::P0),
|
||||
1 => Ok(Priority::P1),
|
||||
2 => Ok(Priority::P2),
|
||||
3 => Ok(Priority::P3),
|
||||
_ => Err(Error::InvalidVariant(number)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl HartIdNumber for HartId {
|
||||
const MAX_HART_ID_NUMBER: usize = Self::H2 as usize;
|
||||
|
||||
#[inline]
|
||||
fn number(self) -> usize {
|
||||
self as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_number(number: usize) -> Result<Self> {
|
||||
match number {
|
||||
0 => Ok(HartId::H0),
|
||||
1 => Ok(HartId::H1),
|
||||
2 => Ok(HartId::H2),
|
||||
_ => Err(Error::InvalidVariant(number)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_exception_enum() {
|
||||
assert_eq!(Exception::E1.number(), 1);
|
||||
assert_eq!(Exception::E3.number(), 3);
|
||||
|
||||
assert_eq!(Exception::from_number(0), Err(Error::InvalidVariant(0)));
|
||||
assert_eq!(Exception::from_number(1), Ok(Exception::E1));
|
||||
assert_eq!(Exception::from_number(2), Err(Error::InvalidVariant(2)));
|
||||
assert_eq!(Exception::from_number(3), Ok(Exception::E3));
|
||||
assert_eq!(Exception::from_number(4), Err(Error::InvalidVariant(4)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_interrupt_enum() {
|
||||
assert_eq!(Interrupt::I1.number(), 1);
|
||||
assert_eq!(Interrupt::I2.number(), 2);
|
||||
assert_eq!(Interrupt::I4.number(), 4);
|
||||
|
||||
assert_eq!(Interrupt::from_number(0), Err(Error::InvalidVariant(0)));
|
||||
assert_eq!(Interrupt::from_number(1), Ok(Interrupt::I1));
|
||||
assert_eq!(Interrupt::from_number(2), Ok(Interrupt::I2));
|
||||
assert_eq!(Interrupt::from_number(3), Err(Error::InvalidVariant(3)));
|
||||
assert_eq!(Interrupt::from_number(4), Ok(Interrupt::I4));
|
||||
assert_eq!(Interrupt::from_number(5), Err(Error::InvalidVariant(5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_priority_enum() {
|
||||
assert_eq!(Priority::P0.number(), 0);
|
||||
assert_eq!(Priority::P1.number(), 1);
|
||||
assert_eq!(Priority::P2.number(), 2);
|
||||
assert_eq!(Priority::P3.number(), 3);
|
||||
|
||||
assert_eq!(Priority::from_number(0), Ok(Priority::P0));
|
||||
assert_eq!(Priority::from_number(1), Ok(Priority::P1));
|
||||
assert_eq!(Priority::from_number(2), Ok(Priority::P2));
|
||||
assert_eq!(Priority::from_number(3), Ok(Priority::P3));
|
||||
assert_eq!(Priority::from_number(4), Err(Error::InvalidVariant(4)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_hart_id_enum() {
|
||||
assert_eq!(HartId::H0.number(), 0);
|
||||
assert_eq!(HartId::H1.number(), 1);
|
||||
assert_eq!(HartId::H2.number(), 2);
|
||||
|
||||
assert_eq!(HartId::from_number(0), Ok(HartId::H0));
|
||||
assert_eq!(HartId::from_number(1), Ok(HartId::H1));
|
||||
assert_eq!(HartId::from_number(2), Ok(HartId::H2));
|
||||
assert_eq!(HartId::from_number(3), Err(Error::InvalidVariant(3)));
|
||||
}
|
||||
}
|
||||
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
use core::fmt;
|
||||
|
||||
/// Convenience alias for the [Result](core::result::Result) type for the library.
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
/// Represents error variants for the library.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Attempted out-of-bounds access.
|
||||
IndexOutOfBounds {
|
||||
index: usize,
|
||||
min: usize,
|
||||
max: usize,
|
||||
},
|
||||
/// Invalid field value.
|
||||
InvalidFieldValue {
|
||||
field: &'static str,
|
||||
value: usize,
|
||||
bitmask: usize,
|
||||
},
|
||||
/// Invalid value of a register field that does not match any known variants.
|
||||
InvalidFieldVariant { field: &'static str, value: usize },
|
||||
/// Invalid value.
|
||||
InvalidValue { value: usize, bitmask: usize },
|
||||
/// Invalid value that does not match any known variants.
|
||||
InvalidVariant(usize),
|
||||
/// Unimplemented function or type.
|
||||
Unimplemented,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::IndexOutOfBounds { index, min, max } => write!(
|
||||
f,
|
||||
"out-of-bounds access, index: {index}, min: {min}, max: {max}"
|
||||
),
|
||||
Self::InvalidFieldValue {
|
||||
field,
|
||||
value,
|
||||
bitmask,
|
||||
} => write!(
|
||||
f,
|
||||
"invalid {field} field value: {value:#x}, valid bitmask: {bitmask:#x}",
|
||||
),
|
||||
Self::InvalidFieldVariant { field, value } => {
|
||||
write!(f, "invalid {field} field variant: {value:#x}")
|
||||
}
|
||||
Self::InvalidValue { value, bitmask } => {
|
||||
write!(f, "invalid value: {value:#x}, valid bitmask: {bitmask:#x}",)
|
||||
}
|
||||
Self::InvalidVariant(value) => {
|
||||
write!(f, "invalid variant: {value:#x}")
|
||||
}
|
||||
Self::Unimplemented => write!(f, "unimplemented"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user