Compare commits

...

10 commits

Author SHA1 Message Date
3cc0d6aaad Fill in remaining color settings 2025-11-01 20:28:10 -05:00
3e1245260d Replace tga with bmp for code size reduction
Saved almost 100K just because
the TGA parsing code was getting
duplicated for each draw target.

Compression may still be helpful
later but it needs to reuse/share the decompression code and not duplicate it extra times.
2025-10-31 12:33:45 -05:00
4d4d39c7a1 chord controller 2025-10-31 09:14:40 -05:00
8e96e70cfd animation 2025-10-31 08:37:58 -05:00
b9363f1c7d HappyEyes 2025-10-30 20:49:39 -05:00
c90c10cf7a HAL 2025-10-30 20:06:14 -05:00
0bab1d52a5 LED matrix demo 2025-10-29 23:17:24 -05:00
40ce8dba2c fixup workspace 2025-10-29 20:55:44 -05:00
4099896e36 DFU button for development mode 2025-10-29 20:48:05 -05:00
5f36125e92 Create workspace for repo 2025-10-29 20:47:52 -05:00
10 changed files with 627 additions and 95 deletions

View file

View file

@ -763,9 +763,12 @@ dependencies = [
"embedded-io",
"embedded-io-async",
"embedded-storage",
"fixed",
"heapless",
"mipidsi",
"panic-probe",
"rgb",
"smart-leds",
"tinybmp",
]
[[package]]
@ -870,16 +873,6 @@ dependencies = [
"syn",
]
[[package]]
name = "panic-probe"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a"
dependencies = [
"cortex-m",
"defmt",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
@ -1083,6 +1076,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"
@ -1267,6 +1263,15 @@ dependencies = [
"syn",
]
[[package]]
name = "tinybmp"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df43af2cb7b369009aa14144959bb4f2720ab62034c9073242f2d3a186c2edb6"
dependencies = [
"embedded-graphics",
]
[[package]]
name = "typenum"
version = "1.19.0"

13
Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[workspace]
resolver = "2"
members = ["firmware"]
[profile.release]
debug = 2
lto = true
opt-level = 'z'
[profile.dev]
debug = 2
lto = true
opt-level = "z"

View file

@ -3,7 +3,6 @@ edition = "2024"
name = "led-mask"
version = "0.1.0"
authors = ["Adam Gausmann <adam@gaussian.dev>"]
resolver = "2"
[[bin]]
name = "led-mask"
@ -13,7 +12,6 @@ bench = false
[dependencies]
defmt = "1.0"
defmt-rtt = "1.1"
panic-probe = { version = "1.0", features = ["print-defmt"] }
embedded-hal = "1.0.0"
embedded-hal-async = "1.0.0"
@ -48,13 +46,7 @@ mipidsi = "0.9.0"
embedded-hal-bus = "0.3.0"
embassy-futures = "0.1.2"
heapless = "0.8.0"
[profile.release]
debug = 2
lto = true
opt-level = 'z'
[profile.dev]
debug = 2
lto = true
opt-level = "z"
rgb = "0.8.52"
smart-leds = "0.4.0"
fixed = "1.29.0"
tinybmp = "0.6.0"

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

@ -0,0 +1,183 @@
use embassy_time::Duration;
use embedded_graphics::{pixelcolor::Rgb888, prelude::*, primitives::Rectangle};
use smart_leds::hsv::{Hsv, hsv2rgb};
use tinybmp::Bmp;
pub struct Hal {
image_data: Bmp<'static, Rgb888>,
}
impl Hal {
pub fn new() -> Self {
Self {
image_data: Bmp::from_slice(include_bytes!("assets/hal.bmp")).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>,
{
self.image_data.draw(target)
}
}
pub struct HappyEyes {
image_data: Bmp<'static, Rgb888>,
}
impl HappyEyes {
pub fn new() -> Self {
Self {
image_data: Bmp::from_slice(include_bytes!("assets/eye1.bmp")).unwrap(),
}
}
pub fn draw_colored<D: DrawTarget<Color = Rgb888>>(
&self,
target: &mut D,
color: Rgb888,
) -> Result<(), D::Error> {
self.draw(&mut ColorMapFilter {
inner_target: target,
filter: |c| mix(c, color),
})
}
}
impl Drawable for HappyEyes {
type Color = Rgb888;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
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<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
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<Color = Rgb888>> DrawTarget for RainbowFilter<'a, D> {
type Color = D::Color;
type Error = D::Error;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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()
}
}
pub struct ColorMapFilter<'a, D, F>
where
F: FnMut(Rgb888) -> Rgb888,
{
pub inner_target: &'a mut D,
pub filter: F,
}
impl<'a, D, F> DrawTarget for ColorMapFilter<'a, D, F>
where
D: DrawTarget<Color = Rgb888>,
F: FnMut(Rgb888) -> Rgb888,
{
type Color = D::Color;
type Error = D::Error;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
self.inner_target.draw_iter(
pixels
.into_iter()
.map(|Pixel(point, color)| Pixel(point, (self.filter)(color))),
)
}
}
impl<'a, D: Dimensions, F> Dimensions for ColorMapFilter<'a, D, F>
where
F: FnMut(Rgb888) -> Rgb888,
{
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()),
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

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,40 +1,61 @@
#![no_std]
#![no_main]
mod art;
mod led_matrix;
use core::panic::PanicInfo;
use embassy_executor::Spawner;
use embassy_futures::select::{select_array, select4};
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_time::{Delay, Timer};
use embassy_rp::rom_data::reset_to_usb_boot;
use embassy_rp::spi::{self, Blocking, Spi};
use embassy_rp::spinlock_mutex::SpinlockRawMutex;
use embassy_sync::signal::Signal;
use embassy_time::{Delay, Duration, Instant, Ticker, Timer};
use embedded_graphics::pixelcolor::Rgb888;
use embedded_graphics::prelude::{DrawTarget, Drawable, RgbColor, Size, WebColors};
use embedded_hal_bus::spi::ExclusiveDevice;
use heapless::String;
use mipidsi::interface::SpiInterface;
use mipidsi::models::ST7789;
use mipidsi::options::Orientation;
use {defmt_rtt as _, panic_probe as _};
use embassy_rp::spi;
use embassy_rp::spi::{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::prelude::*;
use embedded_graphics::text::Text;
use crate::art::{ColorMapFilter, Hal, HappyEyes, Rainbow, RainbowFilter};
use crate::led_matrix::{LedMatrix, Serpentine};
use {defmt_rtt as _, panic_probe as _};
use defmt_rtt as _;
bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => embassy_rp::pio::InterruptHandler<PIO0>;
});
const DISPLAY_FREQ: u32 = 64_000_000;
#[embassy_executor::main]
async fn main(_spawner: Spawner) -> ! {
async fn main(spawner: Spawner) -> ! {
let p = embassy_rp::init(Default::default());
let buttons = Buttons {
f1: Input::new(p.PIN_11, Pull::Up),
f2: Input::new(p.PIN_14, Pull::Up),
f3: Input::new(p.PIN_9, Pull::Up),
f4: Input::new(p.PIN_3, Pull::Up),
a: Input::new(p.PIN_12, Pull::Up),
b: Input::new(p.PIN_13, Pull::Up),
// x = f2
y: Input::new(p.PIN_15, Pull::Up),
};
// display SPI
let mut display_buffer = [0u8; 512];
let mut display = {
let mut _display = {
let mut config = spi::Config::default();
config.frequency = DISPLAY_FREQ;
config.phase = spi::Phase::CaptureOnSecondTransition;
@ -57,17 +78,8 @@ async fn main(_spawner: Spawner) -> ! {
.init(&mut Delay)
.expect("display init")
};
let _display_backlight = Output::new(p.PIN_20, Level::High);
let raw_image_data = ImageRawLE::new(include_bytes!("assets/ferris.raw"), 86);
let ferris = Image::new(&raw_image_data, Point::new(0, 0));
let style = MonoTextStyle::new(&FONT_10X20, Rgb565::GREEN);
let hello_text = Text::new(
"Hello embedded_graphics \n + embassy + RP2040!",
Point::new(0, 68),
style,
);
// Blank display - unused at the moment
let _display_backlight = Output::new(p.PIN_20, Level::Low);
// setup RGB LED
let mut rg_config = pwm::Config::default();
@ -78,7 +90,7 @@ async fn main(_spawner: Spawner) -> ! {
let mut rg_pwm = Pwm::new_output_ab(p.PWM_SLICE3, p.PIN_6, p.PIN_7, rg_config.clone());
let mut b_pwm = Pwm::new_output_a(p.PWM_SLICE4, p.PIN_8, b_config.clone());
let mut set_led = |r: u8, g: u8, b: u8| {
let mut _set_led = |r: u8, g: u8, b: u8| {
rg_config.compare_a = 0x101 * (255 - r as u16);
rg_config.compare_b = 0x101 * (255 - g as u16);
b_config.compare_a = 0x101 * (255 - b as u16);
@ -86,52 +98,278 @@ 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 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 render = |f_keys: [bool; 4]| {
display.clear(Rgb565::BLACK).ok();
ferris.draw(&mut display).ok();
hello_text.draw(&mut display).ok();
let mut f_key_string: String<16> = String::new();
for (f, s) in f_keys.into_iter().zip(["1", "2", "3", "4"]) {
f_key_string.push_str(f.then_some(s).unwrap_or(" ")).ok();
}
Text::new(&f_key_string, Point::new(0, 108), style)
.draw(&mut display)
.ok();
};
let mut last_state = None;
let mut led_pio = Pio::new(p.PIO0, Irqs);
let led_program = PioWs2812Program::new(&mut led_pio.common);
let 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,
),
);
spawner.spawn(led_task(led_surface)).ok();
spawner.spawn(controller_task(buttons)).ok();
// go to sleep
loop {
let state = [
btn_f1.is_low(),
btn_f2.is_low(),
btn_f3.is_low(),
btn_f4.is_low(),
];
if Some(state) != last_state {
render(state);
last_state = Some(state);
continue;
}
select_array([
btn_f1.wait_for_any_edge(),
btn_f2.wait_for_any_edge(),
btn_f3.wait_for_any_edge(),
btn_f4.wait_for_any_edge(),
])
.await;
Timer::after_secs(1).await
}
}
struct Buttons {
f1: Input<'static>,
f2: Input<'static>,
f3: Input<'static>,
f4: Input<'static>,
a: Input<'static>,
b: Input<'static>,
// x = f2
y: Input<'static>,
}
impl Buttons {
fn read(&self) -> ButtonStates {
ButtonStates {
f1: self.f1.is_low(),
f2: self.f2.is_low(),
f3: self.f3.is_low(),
f4: self.f4.is_low(),
a: self.a.is_low(),
b: self.b.is_low(),
y: self.y.is_low(),
}
}
}
#[derive(Clone, Copy, PartialEq)]
struct ButtonStates {
f1: bool,
f2: bool,
f3: bool,
f4: bool,
a: bool,
b: bool,
// x = f2
y: bool,
}
impl ButtonStates {
fn chord(&self) -> u8 {
(self.f1 as u8) + ((self.f2 as u8) << 1) + ((self.f3 as u8) << 2) + ((self.f4 as u8) << 3)
}
}
#[derive(Clone, Copy, Default)]
struct LedConfig {
pattern: LedPattern,
hal: HalConfig,
happy: HappyConfig,
}
#[derive(Clone, Copy, PartialEq, Default)]
enum LedPattern {
#[default]
Blank,
Hal,
Happy,
}
#[derive(Clone, Copy, PartialEq, Default)]
enum HalConfig {
#[default]
Red,
Blue,
}
impl HalConfig {
fn next(self) -> Self {
match self {
HalConfig::Red => HalConfig::Blue,
HalConfig::Blue => HalConfig::Red,
}
}
}
#[derive(Clone, Copy, PartialEq, Default)]
enum HappyConfig {
Green,
Blue,
#[default]
Purple,
Pink,
RainbowSlow,
RainbowFast,
}
impl HappyConfig {
fn next(self) -> Self {
match self {
HappyConfig::Green => HappyConfig::Blue,
HappyConfig::Blue => HappyConfig::Purple,
HappyConfig::Purple => HappyConfig::Pink,
HappyConfig::Pink => HappyConfig::RainbowSlow,
HappyConfig::RainbowSlow => HappyConfig::RainbowFast,
HappyConfig::RainbowFast => HappyConfig::Green,
}
}
}
static LED_CONFIG_SIGNAL: Signal<SpinlockRawMutex<0>, LedConfig> = Signal::new();
#[embassy_executor::task]
async fn controller_task(buttons: Buttons) {
let mut scan_ticker = Ticker::every(Duration::from_millis(1));
let mut chord_debounce = 0;
let mut stable_chord = 0;
let mut pending_chord = 0;
let mut led_config = LedConfig::default();
loop {
let state = buttons.read();
let new_chord = state.chord();
if state.y {
reset_to_usb_boot(0, 0);
}
if new_chord != pending_chord {
// Track the most recent chord key state.
pending_chord = new_chord;
chord_debounce = 10;
} else if chord_debounce > 0 {
// Wait several scans to make sure it's a stable reading.
chord_debounce -= 1;
} else if (!stable_chord & pending_chord) != 0 {
// Once it's stable, if new keys were pressed compared to
// stable_chord, replace stable_chord.
stable_chord = pending_chord;
} else if pending_chord == 0 && stable_chord != 0 {
// If a stable 0 (all keys released) is read, then process
// stable_chord as an input.
match stable_chord {
0x1 => {
// blank display
led_config.pattern = LedPattern::Blank;
LED_CONFIG_SIGNAL.signal(led_config);
}
0x2 => {
// happy
if led_config.pattern == LedPattern::Happy {
led_config.happy = led_config.happy.next();
} else {
led_config.pattern = LedPattern::Happy;
}
LED_CONFIG_SIGNAL.signal(led_config);
}
0x4 => {
// hal
if led_config.pattern == LedPattern::Hal {
led_config.hal = led_config.hal.next();
} else {
led_config.pattern = LedPattern::Hal;
}
LED_CONFIG_SIGNAL.signal(led_config);
}
_ => {
// unknown chord
}
}
stable_chord = 0;
}
scan_ticker.next().await;
}
}
#[embassy_executor::task]
async fn led_task(mut led_surface: LedMatrix<'static>) {
let hal = Hal::new();
let eyes = HappyEyes::new();
let mut rainbow_slow = Rainbow {
rate: Duration::from_secs(10),
width: 300,
elapsed: Duration::from_secs(0),
};
let mut rainbow_fast = Rainbow {
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();
let mut led_config = LedConfig::default();
loop {
let elapsed = start.elapsed();
rainbow_slow.elapsed = elapsed;
rainbow_fast.elapsed = elapsed;
if let Some(new_config) = LED_CONFIG_SIGNAL.try_take() {
led_config = new_config;
}
led_surface.clear(Rgb888::BLACK).ok();
match led_config.pattern {
LedPattern::Blank => {}
LedPattern::Hal => match led_config.hal {
HalConfig::Red => {
hal.draw(&mut led_surface).ok();
}
HalConfig::Blue => {
hal.draw(&mut ColorMapFilter {
inner_target: &mut led_surface,
filter: |color| Rgb888::new(color.b(), color.g(), color.r() / 2),
})
.ok();
}
},
LedPattern::Happy => match led_config.happy {
HappyConfig::Green => {
eyes.draw_colored(&mut led_surface, Rgb888::GREEN).ok();
}
HappyConfig::Blue => {
eyes.draw_colored(&mut led_surface, Rgb888::BLUE).ok();
}
HappyConfig::Purple => {
eyes.draw_colored(&mut led_surface, Rgb888::new(172, 20, 172))
.ok();
}
HappyConfig::Pink => {
eyes.draw_colored(&mut led_surface, Rgb888::CSS_HOT_PINK)
.ok();
}
HappyConfig::RainbowSlow => {
eyes.draw(&mut RainbowFilter {
inner_target: &mut led_surface,
rainbow: &rainbow_slow,
})
.ok();
}
HappyConfig::RainbowFast => {
eyes.draw(&mut RainbowFilter {
inner_target: &mut led_surface,
rainbow: &rainbow_fast,
})
.ok();
}
},
}
led_surface.sync().await;
ticker.next().await;
}
}
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
reset_to_usb_boot(0, 0);
loop {}
}