From 8e96e70cfddc9f50f5b4c31940ca6eb086d80937 Mon Sep 17 00:00:00 2001 From: Adam Gausmann Date: Fri, 31 Oct 2025 08:37:53 -0500 Subject: [PATCH] animation --- Cargo.lock | 2 + firmware/Cargo.toml | 2 + firmware/src/art.rs | 90 ++++++++++++++++++++++++++++++++++++++++++-- firmware/src/main.rs | 55 ++++++++++++++++----------- 4 files changed, 125 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18df47e..2fbb89e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -763,9 +763,11 @@ dependencies = [ "embedded-io", "embedded-io-async", "embedded-storage", + "fixed", "heapless", "mipidsi", "rgb", + "smart-leds", "tinytga", ] diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index 0a1bdb6..b857b6b 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -48,3 +48,5 @@ embassy-futures = "0.1.2" heapless = "0.8.0" rgb = "0.8.52" tinytga = "0.5.0" +smart-leds = "0.4.0" +fixed = "1.29.0" diff --git a/firmware/src/art.rs b/firmware/src/art.rs index 1e26154..7b5238c 100644 --- a/firmware/src/art.rs +++ b/firmware/src/art.rs @@ -1,4 +1,7 @@ -use embedded_graphics::{image::Image, pixelcolor::Rgb888, prelude::*}; +use embassy_time::Duration; +use embedded_graphics::{image::Image, pixelcolor::Rgb888, prelude::*, primitives::Rectangle}; +use fixed::types::U0F8; +use smart_leds::hsv::{Hsv, hsv2rgb}; use tinytga::Tga; pub struct Hal { @@ -15,7 +18,6 @@ impl Hal { impl Drawable for Hal { type Color = Rgb888; - type Output = (); fn draw(&self, target: &mut D) -> Result @@ -40,7 +42,6 @@ impl HappyEyes { impl Drawable for HappyEyes { type Color = Rgb888; - type Output = (); fn draw(&self, target: &mut D) -> Result @@ -50,3 +51,86 @@ impl Drawable for HappyEyes { self.image_data.draw(target) } } + +pub struct Rainbow { + pub rate: Duration, + pub width: u32, + + pub elapsed: Duration, +} + +impl Rainbow { + fn map(&self, x: i32) -> Rgb888 { + let hue = ((x as f32 / self.width as f32 + + self.elapsed.as_micros() as f32 / self.rate.as_micros() as f32) + * 256.0) as u32 as u8; + let color = hsv2rgb(Hsv { + hue, + sat: 255, + val: 255, + }); + Rgb888::new(color.r, color.g, color.b) + } +} + +impl Drawable for Rainbow { + type Color = Rgb888; + type Output = (); + + fn draw(&self, target: &mut D) -> Result + where + D: DrawTarget, + { + let bb = target.bounding_box(); + for dx in 0..bb.size.width { + let color = self.map(dx as i32); + target.fill_solid( + &Rectangle::new( + bb.top_left + Point::new(dx as i32, 0), + Size::new(1, bb.size.height), + ), + color, + )?; + } + Ok(()) + } +} + +pub struct RainbowFilter<'a, D> { + pub inner_target: &'a mut D, + pub rainbow: &'a Rainbow, +} + +impl<'a, D: DrawTarget> DrawTarget for RainbowFilter<'a, D> { + type Color = D::Color; + type Error = D::Error; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + self.inner_target.draw_iter( + pixels + .into_iter() + .map(|Pixel(point, color)| Pixel(point, mix(color, self.rainbow.map(point.x)))), + ) + } +} + +impl<'a, D: Dimensions> Dimensions for RainbowFilter<'a, D> { + fn bounding_box(&self) -> Rectangle { + self.inner_target.bounding_box() + } +} + +fn mix_u8(a: u8, b: u8) -> u8 { + (a as u16 * b as u16 / 255) as u8 +} + +fn mix(a: Rgb888, b: Rgb888) -> Rgb888 { + Rgb888::new( + mix_u8(a.r(), b.r()), + mix_u8(a.g(), b.g()), + mix_u8(a.b(), b.b()), + ) +} diff --git a/firmware/src/main.rs b/firmware/src/main.rs index fe13805..cea649e 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -16,7 +16,7 @@ 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_rp::spi::{self, Blocking, Spi}; -use embassy_time::Delay; +use embassy_time::{Delay, Duration, Instant, Ticker}; use embedded_graphics::image::{Image, ImageRawLE}; use embedded_graphics::mono_font::MonoTextStyle; use embedded_graphics::mono_font::ascii::FONT_10X20; @@ -30,7 +30,7 @@ use mipidsi::interface::SpiInterface; use mipidsi::models::ST7789; use mipidsi::options::Orientation; -use crate::art::{Hal, HappyEyes}; +use crate::art::{Hal, HappyEyes, Rainbow, RainbowFilter}; use crate::led_matrix::{LedMatrix, Serpentine}; use defmt_rtt as _; @@ -116,8 +116,7 @@ async fn main(spawner: Spawner) -> ! { let mut led_pio = Pio::new(p.PIO0, Irqs); let led_program = PioWs2812Program::new(&mut led_pio.common); - - let mut led_surface = LedMatrix::new( + let led_surface = LedMatrix::new( Serpentine { size: Size::new(40, 8), }, @@ -129,23 +128,7 @@ async fn main(spawner: Spawner) -> ! { &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(); - */ - // let hal = Hal::new(); - let eyes = HappyEyes::new(); - eyes.draw(&mut led_surface).ok(); - led_surface.sync().await; + spawner.spawn(led_task(led_surface)).ok(); let mut render = |f_keys: [bool; 4]| { display.clear(Rgb565::BLACK).ok(); @@ -186,6 +169,36 @@ async fn main(spawner: Spawner) -> ! { } } +#[embassy_executor::task] +async fn led_task(mut led_surface: LedMatrix<'static>) { + let hal = Hal::new(); + let eyes = HappyEyes::new(); + let mut rainbow = Rainbow { + // rate: Duration::from_secs(10), + // width: 300, + rate: Duration::from_secs(1), + width: 80, + elapsed: Duration::from_secs(0), + }; + + let mut ticker = Ticker::every(Duration::from_hz(30)); + let start = Instant::now(); + + loop { + let elapsed = start.elapsed(); + rainbow.elapsed = elapsed; + + led_surface.clear(Rgb888::BLACK).ok(); + eyes.draw(&mut RainbowFilter { + inner_target: &mut led_surface, + rainbow: &rainbow, + }) + .ok(); + led_surface.sync().await; + ticker.next().await; + } +} + #[embassy_executor::task] async fn dfu_button(mut btn_y: Input<'static>) { btn_y.wait_for_falling_edge().await;