Compare commits
10 commits
347b0c6477
...
3cc0d6aaad
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cc0d6aaad | |||
| 3e1245260d | |||
| 4d4d39c7a1 | |||
| 8e96e70cfd | |||
| b9363f1c7d | |||
| c90c10cf7a | |||
| 0bab1d52a5 | |||
| 40ce8dba2c | |||
| 4099896e36 | |||
| 5f36125e92 |
10 changed files with 627 additions and 95 deletions
0
firmware/.gitignore → .gitignore
vendored
0
firmware/.gitignore → .gitignore
vendored
27
firmware/Cargo.lock → Cargo.lock
generated
27
firmware/Cargo.lock → Cargo.lock
generated
|
|
@ -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
13
Cargo.toml
Normal 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"
|
||||
|
|
@ -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
183
firmware/src/art.rs
Normal 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()),
|
||||
)
|
||||
}
|
||||
BIN
firmware/src/assets/eye1.bmp
Normal file
BIN
firmware/src/assets/eye1.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
firmware/src/assets/hal.bmp
Normal file
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
101
firmware/src/led_matrix.rs
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue