feat: show MAC address on splash screen

Splash/waiting screen now shows:
  Row 1: "DongLoRa" (bold)
  Row 2: "Board Name vX.Y.Z"
  Row 3: MAC address (XX:XX:XX:XX:XX:XX)
  Row 4: "Waiting for host..."

Each board reads its MAC at init:
- ESP32-S3: efuse MAC via esp_hal::efuse::Efuse::mac_address()
- nRF52840: FICR device address registers

BoardInfo struct bundles name/version/mac to keep dashboard() under
clippy's argument limit.
This commit is contained in:
swaits committed 2026-03-29 21:33:07 -06:00
1 parent 7dc79c1a24
commit d22d2b92ac
5 files changed
+67 -19

No files matched your search

+3 -1
View File
@@ -41,6 +41,7 @@ pub struct UsbParts {
pub struct DisplayParts {
pub i2c: DisplayI2c,
pub mac: [u8; 6],
}
// ── Board init ───────────────────────────────────────────────────────
@@ -130,7 +131,8 @@ impl Board {
.with_sda(p.GPIO17)
.with_scl(p.GPIO18)
.into_async();
let display = Some(DisplayParts { i2c });
let mac = esp_hal::efuse::Efuse::mac_address();
let display = Some(DisplayParts { i2c, mac });
(radio, usb, display)
}
+3 -1
View File
@@ -41,6 +41,7 @@ pub struct UsbParts {
pub struct DisplayParts {
pub i2c: DisplayI2c,
pub mac: [u8; 6],
}
// ── Board init ───────────────────────────────────────────────────────
@@ -141,7 +142,8 @@ impl Board {
.with_sda(p.GPIO17)
.with_scl(p.GPIO18)
.into_async();
let display = Some(DisplayParts { i2c });
let mac = esp_hal::efuse::Efuse::mac_address();
let display = Some(DisplayParts { i2c, mac });
(radio, usb, display)
}
+18 -1
View File
@@ -49,6 +49,7 @@ pub struct UsbParts {
pub struct DisplayParts {
pub i2c: DisplayI2c,
pub mac: [u8; 6],
}
// ── Board init ───────────────────────────────────────────────────────
@@ -110,7 +111,23 @@ impl Board {
// via SSD1306 init (fails gracefully if no display attached).
let i2c_cfg = twim::Config::default();
let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_13, p.P0_14, i2c_cfg);
let display = Some(DisplayParts { i2c });
// Read device address from FICR (factory info registers).
// FICR is at fixed address 0x10000000 on nRF52840.
let mac = unsafe {
let ficr = 0x10000000 as *const u32;
// DEVICEADDR[0] at offset 0x0A4, DEVICEADDR[1] at offset 0x0A8
let addr0 = core::ptr::read_volatile(ficr.byte_add(0x0A4));
let addr1 = core::ptr::read_volatile(ficr.byte_add(0x0A8));
[
addr0 as u8,
(addr0 >> 8) as u8,
(addr0 >> 16) as u8,
(addr0 >> 24) as u8,
addr1 as u8,
(addr1 >> 8) as u8,
]
};
let display = Some(DisplayParts { i2c, mac });
(radio, usb, display)
}
+19 -8
View File
@@ -26,6 +26,13 @@ const SPARK_H: i32 = 19; // y=45..63
const RSSI_MIN: i16 = -120;
const RSSI_MAX: i16 = 0;
/// Static board identity info for display rendering.
pub struct BoardInfo<'a> {
pub name: &'a str,
pub version: &'a str,
pub mac: &'a str,
}
/// Render the main status dashboard.
pub fn dashboard(
target: &mut impl DrawTarget<Color = BinaryColor>,
@@ -33,16 +40,14 @@ pub fn dashboard(
rssi_history: &[i16; RSSI_HISTORY_LEN],
tx_history: &[bool; RSSI_HISTORY_LEN],
rssi_count: usize,
board_name: &str,
version: &str,
board: &BoardInfo,
) {
let _ = target.clear(BinaryColor::Off);
let style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
let mut buf: String<32> = String::new();
match status.config {
Some(cfg) => {
// Row 0: state + frequency, TX power right-justified
let mut buf: String<32> = String::new();
let freq_mhz = cfg.freq_hz / 1_000_000;
let freq_khz = (cfg.freq_hz % 1_000_000) / 1_000;
let _ = write!(buf, " {}.{:03}MHz", freq_mhz, freq_khz);
@@ -145,24 +150,30 @@ pub fn dashboard(
rssi_sparkline(target, rssi_history, tx_history, rssi_count);
}
None => {
// Row 1: "DongLoRa" in bold
let title_style = MonoTextStyle::new(&FONT_9X15_BOLD, BinaryColor::On);
Text::with_alignment(
"DongLoRa",
Point::new(W / 2, 24),
Point::new(W / 2, 15),
title_style,
Alignment::Center,
)
.draw(target)
.ok();
Text::with_alignment(version, Point::new(W / 2, 38), style, Alignment::Center)
// Row 2: "Board Name v0.1.0"
buf.clear();
let _ = write!(buf, "{} v{}", board.name, board.version);
Text::with_alignment(&buf, Point::new(W / 2, 28), style, Alignment::Center)
.draw(target)
.ok();
Text::with_alignment(board_name, Point::new(W / 2, 50), style, Alignment::Center)
// Row 3: MAC address
Text::with_alignment(board.mac, Point::new(W / 2, 41), style, Alignment::Center)
.draw(target)
.ok();
// Row 4: status
Text::with_alignment(
"Waiting for host...",
Point::new(W / 2, 63),
Point::new(W / 2, 54),
style,
Alignment::Center,
)
+24 -8
View File
@@ -81,6 +81,23 @@ pub async fn display_task(
status: &'static StatusWatch,
display_commands: &'static DisplayCommandChannel,
) {
// Format MAC address as "XX:XX:XX:XX:XX:XX"
let mut mac_str: heapless::String<18> = heapless::String::new();
let m = parts.mac;
let _ = core::fmt::Write::write_fmt(
&mut mac_str,
format_args!(
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
m[0], m[1], m[2], m[3], m[4], m[5]
),
);
let board_info = render::BoardInfo {
name: BOARD_NAME,
version: env!("CARGO_PKG_VERSION"),
mac: &mac_str,
};
let interface = I2CDisplayInterface::new(parts.i2c);
let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
.into_buffered_graphics_mode();
@@ -104,7 +121,7 @@ pub async fn display_task(
};
// Show splash/waiting screen (dashboard with no config renders it)
render_and_flush(&mut display, &state).await;
render_and_flush(&mut display, &state, &board_info).await;
loop {
match select3(
@@ -129,7 +146,7 @@ pub async fn display_task(
state.last_status = radio_status;
if state.display_on {
render_and_flush(&mut display, &state).await;
render_and_flush(&mut display, &state, &board_info).await;
}
}
Either3::Second(cmd) => match cmd {
@@ -145,18 +162,18 @@ pub async fn display_task(
if let Some(s) = receiver.try_get() {
state.last_status = s;
}
render_and_flush(&mut display, &state).await;
render_and_flush(&mut display, &state, &board_info).await;
}
DisplayCommand::Reset => {
state = DisplayState::new();
render_and_flush(&mut display, &state).await;
render_and_flush(&mut display, &state, &board_info).await;
}
},
Either3::Third(()) => {
// Timer tick: advance sparkline slot
state.advance_slot();
if state.display_on {
render_and_flush(&mut display, &state).await;
render_and_flush(&mut display, &state, &board_info).await;
}
}
}
@@ -171,7 +188,7 @@ type Display<I> = ssd1306::Ssd1306Async<
ssd1306::mode::BufferedGraphicsModeAsync<DisplaySize128x64>,
>;
async fn render_and_flush<I>(display: &mut Display<I>, state: &DisplayState)
async fn render_and_flush<I>(display: &mut Display<I>, state: &DisplayState, board: &render::BoardInfo<'_>)
where
I: display_interface::AsyncWriteOnlyDataCommand,
{
@@ -181,8 +198,7 @@ where
&state.rssi_history,
&state.tx_history,
state.rssi_count,
BOARD_NAME,
env!("CARGO_PKG_VERSION"),
board,
);
let _ = display.flush().await;
}