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
+96
@@ -0,0 +1,96 @@
|
||||
use defmt::trace;
|
||||
use embedded_hal_async::spi::{Operation, SpiDevice};
|
||||
|
||||
use crate::mod_params::RadioError::{self, SPI};
|
||||
use crate::mod_traits::InterfaceVariant;
|
||||
|
||||
pub(crate) struct SpiInterface<SPI, IV> {
|
||||
pub(crate) spi: SPI,
|
||||
pub(crate) iv: IV,
|
||||
}
|
||||
|
||||
impl<SPI, IV> SpiInterface<SPI, IV>
|
||||
where
|
||||
SPI: SpiDevice<u8>,
|
||||
IV: InterfaceVariant,
|
||||
{
|
||||
pub fn new(spi: SPI, iv: IV) -> Self {
|
||||
Self { spi, iv }
|
||||
}
|
||||
|
||||
// Write a buffer to the radio.
|
||||
pub async fn write(&mut self, write_buffer: &[u8], is_sleep_command: bool) -> Result<(), RadioError> {
|
||||
self.spi.write(write_buffer).await.map_err(|_| SPI)?;
|
||||
trace!("write: {=[u8]:02x}", write_buffer);
|
||||
|
||||
if !is_sleep_command {
|
||||
self.iv.wait_on_busy().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Write
|
||||
pub async fn write_with_payload(
|
||||
&mut self,
|
||||
write_buffer: &[u8],
|
||||
payload: &[u8],
|
||||
is_sleep_command: bool,
|
||||
) -> Result<(), RadioError> {
|
||||
let mut ops = [Operation::Write(write_buffer), Operation::Write(payload)];
|
||||
self.spi.transaction(&mut ops).await.map_err(|_| SPI)?;
|
||||
trace!("write_buf: {=[u8]:02x} -> {=[u8]:02x}", write_buffer, payload);
|
||||
|
||||
if !is_sleep_command {
|
||||
self.iv.wait_on_busy().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Request a read, filling the provided buffer.
|
||||
pub async fn read(&mut self, write_buffer: &[u8], read_buffer: &mut [u8]) -> Result<(), RadioError> {
|
||||
{
|
||||
let mut ops = [Operation::Write(write_buffer), Operation::Read(read_buffer)];
|
||||
|
||||
self.spi.transaction(&mut ops).await.map_err(|_| SPI)?;
|
||||
}
|
||||
|
||||
self.iv.wait_on_busy().await?;
|
||||
|
||||
trace!(
|
||||
"read: addr={=[u8]:02x}, len={}, data={=[u8]:02x}",
|
||||
write_buffer,
|
||||
read_buffer.len(),
|
||||
read_buffer
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Request a read with status, filling the provided buffer and returning the status.
|
||||
pub async fn read_with_status(&mut self, write_buffer: &[u8], read_buffer: &mut [u8]) -> Result<u8, RadioError> {
|
||||
let mut status = [0u8];
|
||||
{
|
||||
let mut ops = [
|
||||
Operation::Write(write_buffer),
|
||||
Operation::Read(&mut status),
|
||||
Operation::Read(read_buffer),
|
||||
];
|
||||
|
||||
self.spi.transaction(&mut ops).await.map_err(|_| SPI)?;
|
||||
}
|
||||
|
||||
self.iv.wait_on_busy().await?;
|
||||
|
||||
trace!(
|
||||
"read: addr={=[u8]:02x}, len={}, status={:02x}, buf={=[u8]:02x}",
|
||||
write_buffer,
|
||||
read_buffer.len(),
|
||||
status[0],
|
||||
read_buffer
|
||||
);
|
||||
|
||||
Ok(status[0])
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user