LED matrix demo

This commit is contained in:
Adam Gausmann 2025-10-29 23:10:31 -05:00
parent 40ce8dba2c
commit 0bab1d52a5
3 changed files with 127 additions and 14 deletions

4
Cargo.lock generated
View file

@ -766,6 +766,7 @@ dependencies = [
"heapless", "heapless",
"mipidsi", "mipidsi",
"panic-probe", "panic-probe",
"rgb",
] ]
[[package]] [[package]]
@ -1083,6 +1084,9 @@ name = "rgb"
version = "0.8.52" version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "rp-pac" name = "rp-pac"

View file

@ -47,3 +47,4 @@ mipidsi = "0.9.0"
embedded-hal-bus = "0.3.0" embedded-hal-bus = "0.3.0"
embassy-futures = "0.1.2" embassy-futures = "0.1.2"
heapless = "0.8.0" heapless = "0.8.0"
rgb = "0.8.52"

View file

@ -1,36 +1,61 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use core::convert::Infallible;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_futures::select::select_array; use embassy_futures::select::select_array;
use embassy_rp::bind_interrupts;
use embassy_rp::gpio::{Input, Level, Output, Pull}; 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::pwm::{self, Pwm};
use embassy_rp::rom_data::reset_to_usb_boot; use embassy_rp::rom_data::reset_to_usb_boot;
use embassy_time::Delay; use embassy_time::Delay;
use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle};
use embedded_hal_bus::spi::ExclusiveDevice; use embedded_hal_bus::spi::ExclusiveDevice;
use heapless::String; use heapless::String;
use mipidsi::interface::SpiInterface; use mipidsi::interface::SpiInterface;
use mipidsi::models::ST7789; use mipidsi::models::ST7789;
use mipidsi::options::Orientation; use mipidsi::options::Orientation;
use rgb::RGB8;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
use embassy_rp::spi; use embassy_rp::spi::{self, Blocking, Spi};
use embassy_rp::spi::{Blocking, Spi};
use embedded_graphics::image::{Image, ImageRawLE}; use embedded_graphics::image::{Image, ImageRawLE};
use embedded_graphics::mono_font::MonoTextStyle; use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::mono_font::ascii::FONT_10X20; 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::prelude::*;
use embedded_graphics::text::Text; use embedded_graphics::text::Text;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => embassy_rp::pio::InterruptHandler<PIO0>;
});
const DISPLAY_FREQ: u32 = 64_000_000; const DISPLAY_FREQ: u32 = 64_000_000;
#[embassy_executor::main] #[embassy_executor::main]
async fn main(spawner: Spawner) -> ! { async fn main(spawner: Spawner) -> ! {
let p = embassy_rp::init(Default::default()); 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 // display SPI
let mut display_buffer = [0u8; 512]; let mut display_buffer = [0u8; 512];
@ -87,16 +112,33 @@ async fn main(spawner: Spawner) -> ! {
b_pwm.set_config(&b_config); b_pwm.set_config(&b_config);
}; };
// Setup buttons let mut led_pio = Pio::new(p.PIO0, Irqs);
let _btn_a = Input::new(p.PIN_12, Pull::Up); let led_program = PioWs2812Program::new(&mut led_pio.common);
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 led_surface = LedMatrix::new(
let mut btn_f2 = Input::new(p.PIN_14, Pull::Up); Serpentine {
let mut btn_f3 = Input::new(p.PIN_9, Pull::Up); size: Size::new(40, 8),
let mut btn_f4 = Input::new(p.PIN_3, Pull::Up); },
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]| { let mut render = |f_keys: [bool; 4]| {
display.clear(Rgb565::BLACK).ok(); display.clear(Rgb565::BLACK).ok();
@ -115,8 +157,6 @@ async fn main(spawner: Spawner) -> ! {
let mut last_state = None; let mut last_state = None;
spawner.spawn(dfu_button(btn_y)).ok();
loop { loop {
let state = [ let state = [
btn_f1.is_low(), btn_f1.is_low(),
@ -144,3 +184,71 @@ async fn dfu_button(mut btn_y: Input<'static>) {
btn_y.wait_for_falling_edge().await; btn_y.wait_for_falling_edge().await;
reset_to_usb_boot(0, 0); reset_to_usb_boot(0, 0);
} }
struct Serpentine {
size: Size,
}
impl Serpentine {
fn map(&self, mut p: Point) -> Option<usize> {
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<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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
}
}