This commit is contained in:
Adam Gausmann 2025-10-30 20:06:14 -05:00
parent 0bab1d52a5
commit c90c10cf7a
6 changed files with 165 additions and 70 deletions

27
Cargo.lock generated
View file

@ -767,6 +767,7 @@ dependencies = [
"mipidsi",
"panic-probe",
"rgb",
"tinytga",
]
[[package]]
@ -808,6 +809,12 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "mipidsi"
version = "0.9.0"
@ -841,6 +848,16 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@ -1271,6 +1288,16 @@ dependencies = [
"syn",
]
[[package]]
name = "tinytga"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "477839bd612acb4d0551915eaf6eef8cc1b3a9dd58e18e9c2b746c78614a25d5"
dependencies = [
"embedded-graphics",
"nom",
]
[[package]]
name = "typenum"
version = "1.19.0"

View file

@ -48,3 +48,4 @@ embedded-hal-bus = "0.3.0"
embassy-futures = "0.1.2"
heapless = "0.8.0"
rgb = "0.8.52"
tinytga = "0.5.0"

27
firmware/src/art.rs Normal file
View file

@ -0,0 +1,27 @@
use embedded_graphics::{image::Image, pixelcolor::Rgb888, prelude::*};
use tinytga::Tga;
pub struct Hal {
image_data: Tga<'static, Rgb888>,
}
impl Hal {
pub fn new() -> Self {
Self {
image_data: Tga::from_slice(include_bytes!("assets/hal.tga")).unwrap(),
}
}
}
impl Drawable for Hal {
type Color = Rgb888;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
Image::new(&self.image_data, Point::zero()).draw(target)
}
}

BIN
firmware/src/assets/hal.tga Normal file

Binary file not shown.

101
firmware/src/led_matrix.rs Normal file
View file

@ -0,0 +1,101 @@
use core::convert::Infallible;
use embassy_rp::{peripherals::PIO0, pio_programs::ws2812::PioWs2812};
use embedded_graphics::{
Pixel,
pixelcolor::Rgb888,
prelude::{DrawTarget, OriginDimensions, Point, RgbColor, Size},
};
use rgb::RGB8;
// Sourced from the `smart-leds` crate
const GAMMA8: [u8; 256] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14,
14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27,
27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46,
47, 48, 49, 50, 50, 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72,
73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104,
105, 107, 109, 110, 112, 114, 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137,
138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175,
177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 220,
223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255,
];
pub fn gamma(color: Rgb888) -> Rgb888 {
Rgb888::new(
GAMMA8[color.r() as usize],
GAMMA8[color.g() as usize],
GAMMA8[color.b() as usize],
)
}
pub struct Serpentine {
pub 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)
}
}
pub struct LedMatrix<'d> {
buffer: [RGB8; 320],
layout: Serpentine,
pio: PioWs2812<'d, PIO0, 0, 320>,
}
impl<'d> LedMatrix<'d> {
pub fn new(layout: Serpentine, pio: PioWs2812<'d, PIO0, 0, 320>) -> Self {
Self {
buffer: [RGB8::default(); 320],
layout,
pio,
}
}
pub 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)) {
let color = gamma(color);
// Map to 0-64 for brightness limiting
*x = RGB8::new(
color.r().div_ceil(2),
color.g().div_ceil(2),
color.b().div_ceil(2),
);
}
}
Ok(())
}
}
impl<'d> OriginDimensions for LedMatrix<'d> {
fn size(&self) -> Size {
self.layout.size
}
}

View file

@ -1,7 +1,8 @@
#![no_std]
#![no_main]
use core::convert::Infallible;
mod art;
mod led_matrix;
use embassy_executor::Spawner;
use embassy_futures::select::select_array;
@ -19,7 +20,9 @@ use heapless::String;
use mipidsi::interface::SpiInterface;
use mipidsi::models::ST7789;
use mipidsi::options::Orientation;
use rgb::RGB8;
use crate::art::Hal;
use crate::led_matrix::{LedMatrix, Serpentine};
use {defmt_rtt as _, panic_probe as _};
@ -128,6 +131,7 @@ async fn main(spawner: Spawner) -> ! {
),
);
led_surface.clear(Rgb888::BLACK).ok();
/*
Rectangle::new(Point::zero(), led_surface.size())
.into_styled(
PrimitiveStyleBuilder::new()
@ -138,6 +142,9 @@ async fn main(spawner: Spawner) -> ! {
)
.draw(&mut led_surface)
.ok();
*/
let hal = Hal::new();
hal.draw(&mut led_surface).ok();
led_surface.sync().await;
let mut render = |f_keys: [bool; 4]| {
@ -184,71 +191,3 @@ 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<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
}
}