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
+1
@@ -0,0 +1 @@
|
||||
{"files":{".cargo_vcs_info.json":"96605b7306f87ca94b37b6e04eb23bf98393390b85c4d8fc210901beef13f265","CHANGELOG.md":"079617c5c2d2ff2aaf597f5b8b36b96ebe4375a6cec253b4f7368a9021b5dbfe","Cargo.toml":"3070964015d592fcbcab9a9c9e62e29d7b1c8fb90986bab0cababdf48e15d2c3","Cargo.toml.orig":"33a3670161a8c426406b06e4f87b9afefc9f3a603ddb12ab3b478a7db4a7a8ac","README.md":"81be4ce9892ea6bec014ebe1e5608c583c06db7dd081eddeb40e79cf8a2262ce","src/lib.rs":"27a6803089997d08c438b568921a5cbfa0a3f6ddeb0c123ac168692fcec60ba7","src/result.rs":"4da48ac78ef99cc4bdf861c1b5f0a764b95bd134db538e38d1ce442cfb3aab07"},"package":"8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "f73533a7d5663fc7ea85d715d828ca9983683761"
|
||||
},
|
||||
"path_in_vcs": "riscv-pac"
|
||||
}
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.2.0] - 2024-10-19
|
||||
|
||||
### Added
|
||||
|
||||
- Add `result` module for `Error` and `Result` types
|
||||
- Add `ExceptionNumber` trait.
|
||||
- Classify interrupt numbers in `CoreInterruptNumber` and `ExternalInterruptNumber`.
|
||||
- Added simple tests to illustrate how to implement all the provided traits.
|
||||
|
||||
### Changed
|
||||
|
||||
- All traits now work with `usize` data type.
|
||||
|
||||
## [v0.1.1] - 2024-02-15
|
||||
|
||||
- Fix crates.io badge links
|
||||
|
||||
## [v0.1.0] - 2024-01-14
|
||||
|
||||
### Added
|
||||
|
||||
- Add `InterruptNumber`, `PriorityNumber`, and `HartIdNumber` traits.
|
||||
|
||||
### Changed
|
||||
|
||||
- Update `README.md`
|
||||
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
rust-version = "1.60"
|
||||
name = "riscv-pac"
|
||||
version = "0.2.0"
|
||||
authors = ["The RISC-V Team <risc-v@teams.rust-embedded.org>"]
|
||||
build = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Low level access to RISC-V processors"
|
||||
documentation = "https://docs.rs/riscv-pac"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"riscv",
|
||||
"register",
|
||||
"peripheral",
|
||||
]
|
||||
categories = [
|
||||
"embedded",
|
||||
"hardware-support",
|
||||
"no-std",
|
||||
]
|
||||
license = "ISC"
|
||||
repository = "https://github.com/rust-embedded/riscv"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
default-target = "riscv64imac-unknown-none-elf"
|
||||
targets = [
|
||||
"riscv32i-unknown-none-elf",
|
||||
"riscv32imc-unknown-none-elf",
|
||||
"riscv32imac-unknown-none-elf",
|
||||
"riscv64imac-unknown-none-elf",
|
||||
"riscv64gc-unknown-none-elf",
|
||||
]
|
||||
|
||||
[lib]
|
||||
name = "riscv_pac"
|
||||
path = "src/lib.rs"
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
[](https://crates.io/crates/riscv-pac)
|
||||
[](https://crates.io/crates/riscv-pac)
|
||||
|
||||
# `riscv-pac`
|
||||
|
||||
> Target-specific traits to be implemented by PACs
|
||||
|
||||
This project is developed and maintained by the [RISC-V team][team].
|
||||
|
||||
## [Documentation](https://docs.rs/crate/riscv-pac)
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
This crate is guaranteed to compile on stable Rust 1.60 and up. It *might*
|
||||
compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2023-2024 [RISC-V team][team]
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Contribution to this crate is organized under the terms of the [Rust Code of
|
||||
Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises
|
||||
to intervene to uphold that code of conduct.
|
||||
|
||||
[CoC]: CODE_OF_CONDUCT.md
|
||||
[team]: https://github.com/rust-embedded/wg#the-risc-v-team
|
||||
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