From c40ec7b2c6255c001e22891393da13ec1a9e338a Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 15 Sep 2025 18:37:48 -1000 Subject: [PATCH 1/5] Use the ili9341 crate --- ui-stm32/Cargo.toml | 4 ++ ui-stm32/src/board/ev13.rs | 135 +++++++++++++++++++++++++++++++++++-- 2 files changed, 135 insertions(+), 4 deletions(-) diff --git a/ui-stm32/Cargo.toml b/ui-stm32/Cargo.toml index 6336fe8..b15edfa 100644 --- a/ui-stm32/Cargo.toml +++ b/ui-stm32/Cargo.toml @@ -29,6 +29,10 @@ hex = { version = "0.4.3", default-features = false } heapless = { version = "0.9.1", features = ["defmt"] } embedded-io-async = "0.6.1" embedded-io = { version = "0.6.1", default-features = false } +ili9341 = "0.6.0" +display-interface = "0.5.0" +itertools = { version = "0.14.0", default-features = false } +embedded-graphics-core = { version = "0.4.0", default-features = false } [profile.release] debug = 2 diff --git a/ui-stm32/src/board/ev13.rs b/ui-stm32/src/board/ev13.rs index f7ee6af..88a1148 100644 --- a/ui-stm32/src/board/ev13.rs +++ b/ui-stm32/src/board/ev13.rs @@ -3,17 +3,102 @@ use embassy_stm32::{ bind_interrupts, exti::ExtiInput, gpio::{Input, Level, Output, Pull, Speed}, - mode::Async, + mode::{Async, Blocking}, peripherals, - spi::Spi, + spi::{Spi, Word}, usart::{self, UartRx, UartTx}, }; +use embassy_time::Delay; +use embedded_graphics_core::{pixelcolor::Rgb565, prelude::*, primitives::Rectangle}; +use ili9341::{Ili9341, Orientation}; +use itertools::Itertools; use ui_app::{Led, Outputs}; +struct NoopScreen; + +impl ui_app::Screen for NoopScreen { + fn width(&self) -> usize { + 320 + } + + fn height(&self) -> usize { + 240 + } + + fn fill(&mut self, color: u16) {} + + fn draw(&mut self, left: usize, right: usize, top: usize, bottom: usize, data: &[u16]) {} +} + +use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand}; + +struct DisplayData { + data_command: Output<'static>, + spi: Spi<'static, Blocking>, +} + +impl DisplayData { + fn write(&mut self, data: DataFormat<'_>) -> Result<(), DisplayError> { + use DataFormat::*; + match data { + U8(slice) => self.write_slice(slice), + U16(slice) => self.write_slice(slice), + U16BE(slice) => self.write_slice(slice), + U16LE(slice) => self.write_slice(slice), + U8Iter(iter) => self.write_iter(iter), + U16BEIter(iter) => self.write_iter(iter), + U16LEIter(iter) => self.write_iter(iter), + _ => unreachable!(), + } + } + + fn write_slice(&mut self, data: &[W]) -> Result<(), DisplayError> { + self.spi.blocking_write(data).unwrap(); + Ok(()) + } + + fn write_iter( + &mut self, + iter: &mut dyn Iterator, + ) -> Result<(), DisplayError> { + const CHUNK_SIZE: usize = 128; + + // XXX(RLB) Very C-style iteration, could probably write this in a way that would optimize + // better. + let mut data = [W::default(); CHUNK_SIZE]; + let mut n = 0; + for (i, x) in iter.enumerate() { + data[i % CHUNK_SIZE] = x; + n = i + 1; + + if n > 0 && n % CHUNK_SIZE == 0 { + self.spi.blocking_write(&data).unwrap(); + n = 0; + } + } + + self.spi.blocking_write(&data[..n]).unwrap(); + Ok(()) + } +} + +impl WriteOnlyDataCommand for DisplayData { + fn send_commands(&mut self, cmd: DataFormat<'_>) -> Result<(), DisplayError> { + self.data_command.set_low(); + self.write(cmd) + } + + fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError> { + self.data_command.set_high(); + self.write(buf) + } +} + pub struct Board { status_led: StatusLed, - screen: Screen, + screen: NoopScreen, net_tx: NetTx>, + display: Ili9341>, pub button_a: Option diff --git a/ui-tauri/src/main.js b/ui-tauri/src/main.js index 3b3ffa8..8f47950 100644 --- a/ui-tauri/src/main.js +++ b/ui-tauri/src/main.js @@ -67,26 +67,19 @@ async function handle_ptt(e) { } } -async function handle_screen(e) { - let { left, right, top, bottom, data } = e.payload; +async function handle_pixels(e) { + let { pixels } = e.payload; const screen = document.getElementById("screen"); const ctx = screen.getContext("2d"); - const width = right - left; - const height = bottom - top; - const imageData = ctx.createImageData(width, height); + const imageData = ctx.createImageData(1, 1); + for (let pixel of pixels) { + const { x, y, r, g, b } = pixel; - if (data.length != imageData.data.length) { - console.log(`malformed command ${data.length} != ${imageData.data.length}`); - return; + ctx.fillStyle = `rgb(${r}, ${g}, ${b}, 1)`; + ctx.fillRect( x, y, 1, 1 ); } - - for (let i = 0; i < imageData.data.length; i ++) { - imageData.data[i] = data[i]; - } - - ctx.putImageData(imageData, left, top); } async function handle_led(e) { @@ -100,9 +93,9 @@ async function handle_led(e) { async function handle_events() { const ptt_state = listen('PttState', handle_ptt); - const screen = listen('Screen', handle_screen); + const pixels = listen('Pixels', handle_pixels); const led = listen('LED', handle_led); - return Promise.all([ptt_state, screen, led]) + return Promise.all([ptt_state, pixels, led]) } window.addEventListener("DOMContentLoaded", async () => { diff --git a/ui-tauri/src/styles.css b/ui-tauri/src/styles.css index 2081606..8b13ddf 100644 --- a/ui-tauri/src/styles.css +++ b/ui-tauri/src/styles.css @@ -37,7 +37,7 @@ body { .container { margin: 0; - padding-top: 5vh; + padding-top: 2vh; display: flex; flex-direction: column; justify-content: center; @@ -46,8 +46,8 @@ body { #screen { background: #999; - width: 320px; - height: 240px; + width: 240px; + height: 320px; margin: 1em auto; } From 46ccb4fe98bdd3e7d46c1ecd123cd00ed98c7305 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Tue, 16 Sep 2025 08:49:52 -1000 Subject: [PATCH 4/5] Update ui-app tests --- ui-app/src/lib.rs | 1 - ui-app/tests/integration.rs | 31 ++++++++++++++++++------------- ui-tauri/src-tauri/src/lib.rs | 1 - 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/ui-app/src/lib.rs b/ui-app/src/lib.rs index 1d2dbc9..07a699e 100644 --- a/ui-app/src/lib.rs +++ b/ui-app/src/lib.rs @@ -172,7 +172,6 @@ where type Color = BinaryColor; type Error = D::Error; - // Required method fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> where I: IntoIterator>, diff --git a/ui-app/tests/integration.rs b/ui-app/tests/integration.rs index f627372..aa2b5d4 100644 --- a/ui-app/tests/integration.rs +++ b/ui-app/tests/integration.rs @@ -1,5 +1,9 @@ use ui_app::*; +use embedded_graphics::{ + draw_target::DrawTarget, pixelcolor::Rgb565, prelude::*, primitives::Rectangle, +}; + #[derive(Default)] struct MockLed { color: Option, @@ -15,21 +19,22 @@ impl Led for MockLed { #[derive(Default)] struct MockScreen; -impl Screen for MockScreen { - fn width(&self) -> usize { - 240 - } - - fn height(&self) -> usize { - 320 +impl Dimensions for MockScreen { + fn bounding_box(&self) -> Rectangle { + Rectangle::new(Point::new(0, 0), Size::new(240, 320)) } +} - fn fill(&mut self, _color: u16) { - // TODO: Store pixels - } +impl DrawTarget for MockScreen { + type Color = Rgb565; + type Error = String; - fn draw(&mut self, _left: usize, _right: usize, _top: usize, _bottom: usize, _data: &[u16]) { - // TODO: Store pixels + fn draw_iter(&mut self, _pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + // TODO(RLB) Store pixels + Ok(()) } } @@ -57,7 +62,7 @@ impl Outputs for MockOutputs { &mut self.status_led } - fn screen(&mut self) -> &mut impl Screen { + fn screen(&mut self) -> &mut impl DrawTarget { &mut self.screen } diff --git a/ui-tauri/src-tauri/src/lib.rs b/ui-tauri/src-tauri/src/lib.rs index a1e36b2..18fc8aa 100644 --- a/ui-tauri/src-tauri/src/lib.rs +++ b/ui-tauri/src-tauri/src/lib.rs @@ -78,7 +78,6 @@ impl DrawTarget for Board { type Color = Rgb565; type Error = String; - // Required method fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> where I: IntoIterator>, From 4ff537d4ca9ce3e48762f1dec7edb546c0e693ed Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Tue, 16 Sep 2025 08:54:38 -1000 Subject: [PATCH 5/5] Fix formatting --- ui-app/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-app/src/lib.rs b/ui-app/src/lib.rs index 07a699e..7711a94 100644 --- a/ui-app/src/lib.rs +++ b/ui-app/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -use bitmap_font::{tamzen::FONT_14x26, TextStyle}; +use bitmap_font::{TextStyle, tamzen::FONT_14x26}; use defmt::Format; use embedded_graphics::{ draw_target::DrawTarget,