This repository has been archived on 2026-08-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
donglora/vendor/ssd1306/src/command.rs
T
swaitsandClaude Opus 4.6 3102fc747a 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>
2026-03-29 20:42:54 -06:00

328 lines
10 KiB
Rust

//! Display commands.
// Shamefully taken from https://github.com/EdgewaterDevelopment/rust-ssd1306
#[cfg(feature = "async")]
use display_interface::AsyncWriteOnlyDataCommand;
use display_interface::{DataFormat::U8, DisplayError, WriteOnlyDataCommand};
/// SSD1306 Commands
#[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async"))]
#[derive(Debug, Copy, Clone)]
pub enum Command {
/// Set contrast. Higher number is higher contrast. Default = 0x7F
Contrast(u8),
/// Turn entire display on. If set, all pixels will
/// be set to on, if not, the value in memory will be used.
AllOn(bool),
/// Invert display.
Invert(bool),
/// Turn display on or off.
DisplayOn(bool),
/// Set up horizontal scrolling.
/// Values are scroll direction, start page, end page,
/// and number of frames per step.
HScrollSetup(HScrollDir, Page, Page, NFrames),
/// Set up horizontal + vertical scrolling.
/// Values are scroll direction, start page, end page,
/// number of frames per step, and vertical scrolling offset.
/// Scrolling offset may be from 0-63
VHScrollSetup(VHScrollDir, Page, Page, NFrames, u8),
/// Enable scrolling
EnableScroll(bool),
/// Setup vertical scroll area.
/// Values are number of rows above scroll area (0-63)
/// and number of rows of scrolling. (0-64)
VScrollArea(u8, u8),
/// Set the lower nibble of the column start address
/// register for Page addressing mode, using the lower
/// 4 bits given.
/// This is only for page addressing mode
LowerColStart(u8),
/// Set the upper nibble of the column start address
/// register for Page addressing mode, using the lower
/// 4 bits given.
/// This is only for page addressing mode
UpperColStart(u8),
/// Set the column start address register for Page addressing mode.
/// Combines LowerColStart and UpperColStart
/// This is only for page addressing mode
ColStart(u8),
/// Set addressing mode
AddressMode(AddrMode),
/// Setup column start and end address
/// values range from 0-127
/// This is only for horizontal or vertical addressing mode
ColumnAddress(u8, u8),
/// Setup page start and end address
/// This is only for horizontal or vertical addressing mode
PageAddress(Page, Page),
/// Set GDDRAM page start address for Page addressing mode
PageStart(Page),
/// Set display start line from 0-63
StartLine(u8),
/// Reverse columns from 127-0
SegmentRemap(bool),
/// Set multiplex ratio from 15-63 (MUX-1)
Multiplex(u8),
/// Scan from COM[n-1] to COM0 (where N is mux ratio)
ReverseComDir(bool),
/// Set vertical shift
DisplayOffset(u8),
/// Setup com hardware configuration
/// First value indicates sequential (false) or alternative (true)
/// pin configuration. Second value disables (false) or enables (true)
/// left/right remap.
ComPinConfig(bool, bool),
/// Set up display clock.
/// First value is oscillator frequency, increasing with higher value
/// Second value is divide ratio - 1
DisplayClockDiv(u8, u8),
/// Set up phase 1 and 2 of precharge period. Each value must be in the range 1 - 15.
PreChargePeriod(u8, u8),
/// Set Vcomh Deselect level
VcomhDeselect(VcomhLevel),
/// NOOP
Noop,
/// Enable charge pump
ChargePump(bool),
/// Select external or internal I REF. Only for 72 x 40 display with SSD1306B driver
InternalIref(bool, bool),
}
#[maybe_async_cfg::maybe(
sync(keep_self),
async(
feature = "async",
idents(WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"))
)
)]
impl Command {
/// Send command to SSD1306
pub async fn send<DI>(self, iface: &mut DI) -> Result<(), DisplayError>
where
DI: WriteOnlyDataCommand,
{
match self {
Command::Contrast(val) => Self::send_commands(iface, &[0x81, val]).await,
Command::AllOn(on) => Self::send_commands(iface, &[0xA4 | (on as u8)]).await,
Command::Invert(inv) => Self::send_commands(iface, &[0xA6 | (inv as u8)]).await,
Command::DisplayOn(on) => Self::send_commands(iface, &[0xAE | (on as u8)]).await,
Command::HScrollSetup(dir, start, end, rate) => {
Self::send_commands(
iface,
&[
0x26 | (dir as u8),
0,
start as u8,
rate as u8,
end as u8,
0,
0xFF,
],
)
.await
}
Command::VHScrollSetup(dir, start, end, rate, offset) => {
Self::send_commands(
iface,
&[
0x28 | (dir as u8),
0,
start as u8,
rate as u8,
end as u8,
offset,
],
)
.await
}
Command::EnableScroll(en) => Self::send_commands(iface, &[0x2E | (en as u8)]).await,
Command::VScrollArea(above, lines) => {
Self::send_commands(iface, &[0xA3, above, lines]).await
}
Command::LowerColStart(addr) => Self::send_commands(iface, &[0xF & addr]).await,
Command::UpperColStart(addr) => {
Self::send_commands(iface, &[0x10 | (0xF & addr)]).await
}
Command::ColStart(addr) => {
Self::send_commands(iface, &[0xF & addr, 0x10 | (0xF & (addr >> 4))]).await
}
Command::AddressMode(mode) => Self::send_commands(iface, &[0x20, mode as u8]).await,
Command::ColumnAddress(start, end) => {
Self::send_commands(iface, &[0x21, start, end]).await
}
Command::PageAddress(start, end) => {
Self::send_commands(iface, &[0x22, start as u8, end as u8]).await
}
Command::PageStart(page) => Self::send_commands(iface, &[0xB0 | (page as u8)]).await,
Command::StartLine(line) => Self::send_commands(iface, &[0x40 | (0x3F & line)]).await,
Command::SegmentRemap(remap) => {
Self::send_commands(iface, &[0xA0 | (remap as u8)]).await
}
Command::Multiplex(ratio) => Self::send_commands(iface, &[0xA8, ratio]).await,
Command::ReverseComDir(rev) => {
Self::send_commands(iface, &[0xC0 | ((rev as u8) << 3)]).await
}
Command::DisplayOffset(offset) => Self::send_commands(iface, &[0xD3, offset]).await,
Command::ComPinConfig(alt, lr) => {
Self::send_commands(iface, &[0xDA, 0x2 | ((alt as u8) << 4) | ((lr as u8) << 5)])
.await
}
Command::DisplayClockDiv(fosc, div) => {
Self::send_commands(iface, &[0xD5, ((0xF & fosc) << 4) | (0xF & div)]).await
}
Command::PreChargePeriod(phase1, phase2) => {
Self::send_commands(iface, &[0xD9, ((0xF & phase2) << 4) | (0xF & phase1)]).await
}
Command::VcomhDeselect(level) => {
Self::send_commands(iface, &[0xDB, (level as u8) << 4]).await
}
Command::Noop => Self::send_commands(iface, &[0xE3]).await,
Command::ChargePump(en) => {
Self::send_commands(iface, &[0x8D, 0x10 | ((en as u8) << 2)]).await
}
Command::InternalIref(en, current) => {
Self::send_commands(iface, &[0xAD, ((current as u8) << 5) | ((en as u8) << 4)])
.await
}
}
}
async fn send_commands<DI>(iface: &mut DI, data: &[u8]) -> Result<(), DisplayError>
where
DI: WriteOnlyDataCommand,
{
iface.send_commands(U8(data)).await
}
}
/// Horizontal Scroll Direction
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub enum HScrollDir {
/// Left to right
LeftToRight = 0,
/// Right to left
RightToLeft = 1,
}
/// Vertical and horizontal scroll dir
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub enum VHScrollDir {
/// Vertical and right horizontal
VerticalRight = 0b01,
/// Vertical and left horizontal
VerticalLeft = 0b10,
}
/// Display page
#[derive(Debug, Clone, Copy)]
pub enum Page {
/// Page 0
Page0 = 0b0000,
/// Page 1
Page1 = 0b0001,
/// Page 2
Page2 = 0b0010,
/// Page 3
Page3 = 0b0011,
/// Page 4
Page4 = 0b0100,
/// Page 5
Page5 = 0b0101,
/// Page 6
Page6 = 0b0110,
/// Page 7
Page7 = 0b0111,
/// Page 8
Page8 = 0b1000,
/// Page 9
Page9 = 0b1001,
/// Page 10
Page10 = 0b1010,
/// Page 11
Page11 = 0b1011,
/// Page 12
Page12 = 0b1100,
/// Page 13
Page13 = 0b1101,
/// Page 14
Page14 = 0b1110,
/// Page 15
Page15 = 0b1111,
}
impl From<u8> for Page {
fn from(val: u8) -> Page {
match val / 8 {
0 => Page::Page0,
1 => Page::Page1,
2 => Page::Page2,
3 => Page::Page3,
4 => Page::Page4,
5 => Page::Page5,
6 => Page::Page6,
7 => Page::Page7,
8 => Page::Page8,
9 => Page::Page9,
10 => Page::Page10,
11 => Page::Page11,
12 => Page::Page12,
13 => Page::Page13,
14 => Page::Page14,
15 => Page::Page15,
_ => panic!("Page too high"),
}
}
}
/// Frame interval
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub enum NFrames {
/// 2 Frames
F2 = 0b111,
/// 3 Frames
F3 = 0b100,
/// 4 Frames
F4 = 0b101,
/// 5 Frames
F5 = 0b000,
/// 25 Frames
F25 = 0b110,
/// 64 Frames
F64 = 0b001,
/// 128 Frames
F128 = 0b010,
/// 256 Frames
F256 = 0b011,
}
/// Address mode
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)]
pub enum AddrMode {
/// Horizontal mode
Horizontal = 0b00,
/// Vertical mode
Vertical = 0b01,
/// Page mode (default)
Page = 0b10,
}
/// Vcomh Deselect level
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub enum VcomhLevel {
/// 0.65 * Vcc
V065 = 0b001,
/// 0.77 * Vcc
V077 = 0b010,
/// 0.83 * Vcc
V083 = 0b011,
/// Auto
Auto = 0b100,
}