From 0bab1d52a58edafdc17224eb266f6415877d1aa7 Mon Sep 17 00:00:00 2001 From: Adam Gausmann Date: Wed, 29 Oct 2025 23:10:31 -0500 Subject: [PATCH] LED matrix demo --- Cargo.lock | 4 ++ firmware/Cargo.toml | 1 + firmware/src/main.rs | 136 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 127 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b793c9..6f6ca5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -766,6 +766,7 @@ dependencies = [ "heapless", "mipidsi", "panic-probe", + "rgb", ] [[package]] @@ -1083,6 +1084,9 @@ name = "rgb" version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +dependencies = [ + "bytemuck", +] [[package]] name = "rp-pac" diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index 997c8ff..118de60 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -47,3 +47,4 @@ mipidsi = "0.9.0" embedded-hal-bus = "0.3.0" embassy-futures = "0.1.2" heapless = "0.8.0" +rgb = "0.8.52" diff --git a/firmware/src/main.rs b/firmware/src/main.rs index 2000a1e..1383dbd 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -1,36 +1,61 @@ #![no_std] #![no_main] +use core::convert::Infallible; + use embassy_executor::Spawner; use embassy_futures::select::select_array; +use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::Pio; +use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; use embassy_rp::pwm::{self, Pwm}; use embassy_rp::rom_data::reset_to_usb_boot; use embassy_time::Delay; +use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; use embedded_hal_bus::spi::ExclusiveDevice; use heapless::String; use mipidsi::interface::SpiInterface; use mipidsi::models::ST7789; use mipidsi::options::Orientation; +use rgb::RGB8; + use {defmt_rtt as _, panic_probe as _}; -use embassy_rp::spi; -use embassy_rp::spi::{Blocking, Spi}; +use embassy_rp::spi::{self, Blocking, Spi}; use embedded_graphics::image::{Image, ImageRawLE}; use embedded_graphics::mono_font::MonoTextStyle; use embedded_graphics::mono_font::ascii::FONT_10X20; -use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::pixelcolor::{Rgb565, Rgb888}; use embedded_graphics::prelude::*; use embedded_graphics::text::Text; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => embassy_rp::pio::InterruptHandler; +}); + const DISPLAY_FREQ: u32 = 64_000_000; #[embassy_executor::main] async fn main(spawner: Spawner) -> ! { let p = embassy_rp::init(Default::default()); + // Setup buttons + let _btn_a = Input::new(p.PIN_12, Pull::Up); + let _btn_b = Input::new(p.PIN_13, Pull::Up); + // let _btn_x = Input::new(p.PIN_14, Pull::Up); + let btn_y = Input::new(p.PIN_15, Pull::Up); + + let mut btn_f1 = Input::new(p.PIN_11, Pull::Up); + let mut btn_f2 = Input::new(p.PIN_14, Pull::Up); + let mut btn_f3 = Input::new(p.PIN_9, Pull::Up); + let mut btn_f4 = Input::new(p.PIN_3, Pull::Up); + + spawner.spawn(dfu_button(btn_y)).ok(); + // display SPI let mut display_buffer = [0u8; 512]; @@ -87,16 +112,33 @@ async fn main(spawner: Spawner) -> ! { b_pwm.set_config(&b_config); }; - // Setup buttons - let _btn_a = Input::new(p.PIN_12, Pull::Up); - let _btn_b = Input::new(p.PIN_13, Pull::Up); - // let _btn_x = Input::new(p.PIN_14, Pull::Up); - let btn_y = Input::new(p.PIN_15, Pull::Up); + let mut led_pio = Pio::new(p.PIO0, Irqs); + let led_program = PioWs2812Program::new(&mut led_pio.common); - let mut btn_f1 = Input::new(p.PIN_11, Pull::Up); - let mut btn_f2 = Input::new(p.PIN_14, Pull::Up); - let mut btn_f3 = Input::new(p.PIN_9, Pull::Up); - let mut btn_f4 = Input::new(p.PIN_3, Pull::Up); + let mut led_surface = LedMatrix::new( + Serpentine { + size: Size::new(40, 8), + }, + PioWs2812::new( + &mut led_pio.common, + led_pio.sm0, + p.DMA_CH0, + p.PIN_10, + &led_program, + ), + ); + led_surface.clear(Rgb888::BLACK).ok(); + Rectangle::new(Point::zero(), led_surface.size()) + .into_styled( + PrimitiveStyleBuilder::new() + .stroke_color(Rgb888::RED) + .stroke_width(1) + .fill_color(Rgb888::CSS_DARK_BLUE) + .build(), + ) + .draw(&mut led_surface) + .ok(); + led_surface.sync().await; let mut render = |f_keys: [bool; 4]| { display.clear(Rgb565::BLACK).ok(); @@ -115,8 +157,6 @@ async fn main(spawner: Spawner) -> ! { let mut last_state = None; - spawner.spawn(dfu_button(btn_y)).ok(); - loop { let state = [ btn_f1.is_low(), @@ -144,3 +184,71 @@ async fn dfu_button(mut btn_y: Input<'static>) { btn_y.wait_for_falling_edge().await; reset_to_usb_boot(0, 0); } + +struct Serpentine { + size: Size, +} + +impl Serpentine { + fn map(&self, mut p: Point) -> Option { + if p.x < 0 || p.x as u32 >= self.size.width || p.y < 0 || p.y as u32 >= self.size.height { + return None; + } + + if p.y % 2 == 0 { + p.x = self.size.width as i32 - 1 - p.x; + } + + Some(p.y as usize * self.size.width as usize + p.x as usize) + } +} + +struct LedMatrix<'d> { + buffer: [RGB8; 320], + layout: Serpentine, + pio: PioWs2812<'d, PIO0, 0, 320>, +} + +impl<'d> LedMatrix<'d> { + fn new(layout: Serpentine, pio: PioWs2812<'d, PIO0, 0, 320>) -> Self { + Self { + buffer: [RGB8::default(); 320], + layout, + pio, + } + } + + async fn sync(&mut self) { + self.pio.write(&self.buffer).await; + } +} + +impl<'d> DrawTarget for LedMatrix<'d> { + type Color = Rgb888; + + type Error = Infallible; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for Pixel(point, color) in pixels { + if let Some(x) = self.layout.map(point).and_then(|i| self.buffer.get_mut(i)) { + // Map to 0-64 for brightness limiting + *x = RGB8::new( + color.r().div_ceil(4), + color.g().div_ceil(4), + color.b().div_ceil(4), + ); + } + } + + Ok(()) + } +} + +impl<'d> OriginDimensions for LedMatrix<'d> { + fn size(&self) -> Size { + self.layout.size + } +}