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

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+16
View File
@@ -0,0 +1,16 @@
#[doc = r"Register block"]
#[repr(C)]
pub struct SUBSCRIBE_CHG {
#[doc = "0x00 - Description cluster: Subscribe configuration for task CHG\\[n\\].EN"]
pub en: EN,
#[doc = "0x04 - Description cluster: Subscribe configuration for task CHG\\[n\\].DIS"]
pub dis: DIS,
}
#[doc = "EN (rw) register accessor: an alias for `Reg<EN_SPEC>`"]
pub type EN = crate::Reg<en::EN_SPEC>;
#[doc = "Description cluster: Subscribe configuration for task CHG\\[n\\].EN"]
pub mod en;
#[doc = "DIS (rw) register accessor: an alias for `Reg<DIS_SPEC>`"]
pub type DIS = crate::Reg<dis::DIS_SPEC>;
#[doc = "Description cluster: Subscribe configuration for task CHG\\[n\\].DIS"]
pub mod dis;
+140
View File
@@ -0,0 +1,140 @@
#[doc = "Register `DIS` reader"]
pub struct R(crate::R<DIS_SPEC>);
impl core::ops::Deref for R {
type Target = crate::R<DIS_SPEC>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<crate::R<DIS_SPEC>> for R {
#[inline(always)]
fn from(reader: crate::R<DIS_SPEC>) -> Self {
R(reader)
}
}
#[doc = "Register `DIS` writer"]
pub struct W(crate::W<DIS_SPEC>);
impl core::ops::Deref for W {
type Target = crate::W<DIS_SPEC>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl core::ops::DerefMut for W {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<crate::W<DIS_SPEC>> for W {
#[inline(always)]
fn from(writer: crate::W<DIS_SPEC>) -> Self {
W(writer)
}
}
#[doc = "Field `CHIDX` reader - Channel that task CHG\\[n\\].DIS will subscribe to"]
pub type CHIDX_R = crate::FieldReader<u8, u8>;
#[doc = "Field `CHIDX` writer - Channel that task CHG\\[n\\].DIS will subscribe to"]
pub type CHIDX_W<'a, const O: u8> = crate::FieldWriter<'a, u32, DIS_SPEC, u8, u8, 4, O>;
#[doc = "Field `EN` reader - "]
pub type EN_R = crate::BitReader<EN_A>;
#[doc = "\n\nValue on reset: 0"]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum EN_A {
#[doc = "0: Disable subscription"]
DISABLED = 0,
#[doc = "1: Enable subscription"]
ENABLED = 1,
}
impl From<EN_A> for bool {
#[inline(always)]
fn from(variant: EN_A) -> Self {
variant as u8 != 0
}
}
impl EN_R {
#[doc = "Get enumerated values variant"]
#[inline(always)]
pub fn variant(&self) -> EN_A {
match self.bits {
false => EN_A::DISABLED,
true => EN_A::ENABLED,
}
}
#[doc = "Checks if the value of the field is `DISABLED`"]
#[inline(always)]
pub fn is_disabled(&self) -> bool {
*self == EN_A::DISABLED
}
#[doc = "Checks if the value of the field is `ENABLED`"]
#[inline(always)]
pub fn is_enabled(&self) -> bool {
*self == EN_A::ENABLED
}
}
#[doc = "Field `EN` writer - "]
pub type EN_W<'a, const O: u8> = crate::BitWriter<'a, u32, DIS_SPEC, EN_A, O>;
impl<'a, const O: u8> EN_W<'a, O> {
#[doc = "Disable subscription"]
#[inline(always)]
pub fn disabled(self) -> &'a mut W {
self.variant(EN_A::DISABLED)
}
#[doc = "Enable subscription"]
#[inline(always)]
pub fn enabled(self) -> &'a mut W {
self.variant(EN_A::ENABLED)
}
}
impl R {
#[doc = "Bits 0:3 - Channel that task CHG\\[n\\].DIS will subscribe to"]
#[inline(always)]
pub fn chidx(&self) -> CHIDX_R {
CHIDX_R::new((self.bits & 0x0f) as u8)
}
#[doc = "Bit 31"]
#[inline(always)]
pub fn en(&self) -> EN_R {
EN_R::new(((self.bits >> 31) & 1) != 0)
}
}
impl W {
#[doc = "Bits 0:3 - Channel that task CHG\\[n\\].DIS will subscribe to"]
#[inline(always)]
pub fn chidx(&mut self) -> CHIDX_W<0> {
CHIDX_W::new(self)
}
#[doc = "Bit 31"]
#[inline(always)]
pub fn en(&mut self) -> EN_W<31> {
EN_W::new(self)
}
#[doc = "Writes raw bits to the register."]
#[inline(always)]
pub unsafe fn bits(&mut self, bits: u32) -> &mut Self {
self.0.bits(bits);
self
}
}
#[doc = "Description cluster: Subscribe configuration for task CHG\\[n\\].DIS\n\nThis register you can [`read`](crate::generic::Reg::read), [`write_with_zero`](crate::generic::Reg::write_with_zero), [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`modify`](crate::generic::Reg::modify). See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [dis](index.html) module"]
pub struct DIS_SPEC;
impl crate::RegisterSpec for DIS_SPEC {
type Ux = u32;
}
#[doc = "`read()` method returns [dis::R](R) reader structure"]
impl crate::Readable for DIS_SPEC {
type Reader = R;
}
#[doc = "`write(|w| ..)` method takes [dis::W](W) writer structure"]
impl crate::Writable for DIS_SPEC {
type Writer = W;
}
#[doc = "`reset()` method sets DIS to value 0"]
impl crate::Resettable for DIS_SPEC {
#[inline(always)]
fn reset_value() -> Self::Ux {
0
}
}
+140
View File
@@ -0,0 +1,140 @@
#[doc = "Register `EN` reader"]
pub struct R(crate::R<EN_SPEC>);
impl core::ops::Deref for R {
type Target = crate::R<EN_SPEC>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<crate::R<EN_SPEC>> for R {
#[inline(always)]
fn from(reader: crate::R<EN_SPEC>) -> Self {
R(reader)
}
}
#[doc = "Register `EN` writer"]
pub struct W(crate::W<EN_SPEC>);
impl core::ops::Deref for W {
type Target = crate::W<EN_SPEC>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl core::ops::DerefMut for W {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<crate::W<EN_SPEC>> for W {
#[inline(always)]
fn from(writer: crate::W<EN_SPEC>) -> Self {
W(writer)
}
}
#[doc = "Field `CHIDX` reader - Channel that task CHG\\[n\\].EN will subscribe to"]
pub type CHIDX_R = crate::FieldReader<u8, u8>;
#[doc = "Field `CHIDX` writer - Channel that task CHG\\[n\\].EN will subscribe to"]
pub type CHIDX_W<'a, const O: u8> = crate::FieldWriter<'a, u32, EN_SPEC, u8, u8, 4, O>;
#[doc = "Field `EN` reader - "]
pub type EN_R = crate::BitReader<EN_A>;
#[doc = "\n\nValue on reset: 0"]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum EN_A {
#[doc = "0: Disable subscription"]
DISABLED = 0,
#[doc = "1: Enable subscription"]
ENABLED = 1,
}
impl From<EN_A> for bool {
#[inline(always)]
fn from(variant: EN_A) -> Self {
variant as u8 != 0
}
}
impl EN_R {
#[doc = "Get enumerated values variant"]
#[inline(always)]
pub fn variant(&self) -> EN_A {
match self.bits {
false => EN_A::DISABLED,
true => EN_A::ENABLED,
}
}
#[doc = "Checks if the value of the field is `DISABLED`"]
#[inline(always)]
pub fn is_disabled(&self) -> bool {
*self == EN_A::DISABLED
}
#[doc = "Checks if the value of the field is `ENABLED`"]
#[inline(always)]
pub fn is_enabled(&self) -> bool {
*self == EN_A::ENABLED
}
}
#[doc = "Field `EN` writer - "]
pub type EN_W<'a, const O: u8> = crate::BitWriter<'a, u32, EN_SPEC, EN_A, O>;
impl<'a, const O: u8> EN_W<'a, O> {
#[doc = "Disable subscription"]
#[inline(always)]
pub fn disabled(self) -> &'a mut W {
self.variant(EN_A::DISABLED)
}
#[doc = "Enable subscription"]
#[inline(always)]
pub fn enabled(self) -> &'a mut W {
self.variant(EN_A::ENABLED)
}
}
impl R {
#[doc = "Bits 0:3 - Channel that task CHG\\[n\\].EN will subscribe to"]
#[inline(always)]
pub fn chidx(&self) -> CHIDX_R {
CHIDX_R::new((self.bits & 0x0f) as u8)
}
#[doc = "Bit 31"]
#[inline(always)]
pub fn en(&self) -> EN_R {
EN_R::new(((self.bits >> 31) & 1) != 0)
}
}
impl W {
#[doc = "Bits 0:3 - Channel that task CHG\\[n\\].EN will subscribe to"]
#[inline(always)]
pub fn chidx(&mut self) -> CHIDX_W<0> {
CHIDX_W::new(self)
}
#[doc = "Bit 31"]
#[inline(always)]
pub fn en(&mut self) -> EN_W<31> {
EN_W::new(self)
}
#[doc = "Writes raw bits to the register."]
#[inline(always)]
pub unsafe fn bits(&mut self, bits: u32) -> &mut Self {
self.0.bits(bits);
self
}
}
#[doc = "Description cluster: Subscribe configuration for task CHG\\[n\\].EN\n\nThis register you can [`read`](crate::generic::Reg::read), [`write_with_zero`](crate::generic::Reg::write_with_zero), [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`modify`](crate::generic::Reg::modify). See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [en](index.html) module"]
pub struct EN_SPEC;
impl crate::RegisterSpec for EN_SPEC {
type Ux = u32;
}
#[doc = "`read()` method returns [en::R](R) reader structure"]
impl crate::Readable for EN_SPEC {
type Reader = R;
}
#[doc = "`write(|w| ..)` method takes [en::W](W) writer structure"]
impl crate::Writable for EN_SPEC {
type Writer = W;
}
#[doc = "`reset()` method sets EN to value 0"]
impl crate::Resettable for EN_SPEC {
#[inline(always)]
fn reset_value() -> Self::Ux {
0
}
}
+16
View File
@@ -0,0 +1,16 @@
#[doc = r"Register block"]
#[repr(C)]
pub struct TASKS_CHG {
#[doc = "0x00 - Description cluster: Enable channel group n"]
pub en: EN,
#[doc = "0x04 - Description cluster: Disable channel group n"]
pub dis: DIS,
}
#[doc = "EN (w) register accessor: an alias for `Reg<EN_SPEC>`"]
pub type EN = crate::Reg<en::EN_SPEC>;
#[doc = "Description cluster: Enable channel group n"]
pub mod en;
#[doc = "DIS (w) register accessor: an alias for `Reg<DIS_SPEC>`"]
pub type DIS = crate::Reg<dis::DIS_SPEC>;
#[doc = "Description cluster: Disable channel group n"]
pub mod dis;
+71
View File
@@ -0,0 +1,71 @@
#[doc = "Register `DIS` writer"]
pub struct W(crate::W<DIS_SPEC>);
impl core::ops::Deref for W {
type Target = crate::W<DIS_SPEC>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl core::ops::DerefMut for W {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<crate::W<DIS_SPEC>> for W {
#[inline(always)]
fn from(writer: crate::W<DIS_SPEC>) -> Self {
W(writer)
}
}
#[doc = "Disable channel group n\n\nValue on reset: 0"]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DIS_AW {
#[doc = "1: Trigger task"]
TRIGGER = 1,
}
impl From<DIS_AW> for bool {
#[inline(always)]
fn from(variant: DIS_AW) -> Self {
variant as u8 != 0
}
}
#[doc = "Field `DIS` writer - Disable channel group n"]
pub type DIS_W<'a, const O: u8> = crate::BitWriter<'a, u32, DIS_SPEC, DIS_AW, O>;
impl<'a, const O: u8> DIS_W<'a, O> {
#[doc = "Trigger task"]
#[inline(always)]
pub fn trigger(self) -> &'a mut W {
self.variant(DIS_AW::TRIGGER)
}
}
impl W {
#[doc = "Bit 0 - Disable channel group n"]
#[inline(always)]
pub fn dis(&mut self) -> DIS_W<0> {
DIS_W::new(self)
}
#[doc = "Writes raw bits to the register."]
#[inline(always)]
pub unsafe fn bits(&mut self, bits: u32) -> &mut Self {
self.0.bits(bits);
self
}
}
#[doc = "Description cluster: Disable channel group n\n\nThis register you can [`write_with_zero`](crate::generic::Reg::write_with_zero), [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write). See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [dis](index.html) module"]
pub struct DIS_SPEC;
impl crate::RegisterSpec for DIS_SPEC {
type Ux = u32;
}
#[doc = "`write(|w| ..)` method takes [dis::W](W) writer structure"]
impl crate::Writable for DIS_SPEC {
type Writer = W;
}
#[doc = "`reset()` method sets DIS to value 0"]
impl crate::Resettable for DIS_SPEC {
#[inline(always)]
fn reset_value() -> Self::Ux {
0
}
}
+71
View File
@@ -0,0 +1,71 @@
#[doc = "Register `EN` writer"]
pub struct W(crate::W<EN_SPEC>);
impl core::ops::Deref for W {
type Target = crate::W<EN_SPEC>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl core::ops::DerefMut for W {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<crate::W<EN_SPEC>> for W {
#[inline(always)]
fn from(writer: crate::W<EN_SPEC>) -> Self {
W(writer)
}
}
#[doc = "Enable channel group n\n\nValue on reset: 0"]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum EN_AW {
#[doc = "1: Trigger task"]
TRIGGER = 1,
}
impl From<EN_AW> for bool {
#[inline(always)]
fn from(variant: EN_AW) -> Self {
variant as u8 != 0
}
}
#[doc = "Field `EN` writer - Enable channel group n"]
pub type EN_W<'a, const O: u8> = crate::BitWriter<'a, u32, EN_SPEC, EN_AW, O>;
impl<'a, const O: u8> EN_W<'a, O> {
#[doc = "Trigger task"]
#[inline(always)]
pub fn trigger(self) -> &'a mut W {
self.variant(EN_AW::TRIGGER)
}
}
impl W {
#[doc = "Bit 0 - Enable channel group n"]
#[inline(always)]
pub fn en(&mut self) -> EN_W<0> {
EN_W::new(self)
}
#[doc = "Writes raw bits to the register."]
#[inline(always)]
pub unsafe fn bits(&mut self, bits: u32) -> &mut Self {
self.0.bits(bits);
self
}
}
#[doc = "Description cluster: Enable channel group n\n\nThis register you can [`write_with_zero`](crate::generic::Reg::write_with_zero), [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write). See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [en](index.html) module"]
pub struct EN_SPEC;
impl crate::RegisterSpec for EN_SPEC {
type Ux = u32;
}
#[doc = "`write(|w| ..)` method takes [en::W](W) writer structure"]
impl crate::Writable for EN_SPEC {
type Writer = W;
}
#[doc = "`reset()` method sets EN to value 0"]
impl crate::Resettable for EN_SPEC {
#[inline(always)]
fn reset_value() -> Self::Ux {
0
}
}