Compare commits
No commits in common. "3cc0d6aaada2e38066d743c7194e0f1c420129c4" and "347b0c6477633ccee2f498ca9badfbe01d988389" have entirely different histories.
3cc0d6aaad
...
347b0c6477
10 changed files with 87 additions and 619 deletions
13
Cargo.toml
13
Cargo.toml
|
|
@ -1,13 +0,0 @@
|
||||||
[workspace]
|
|
||||||
resolver = "2"
|
|
||||||
members = ["firmware"]
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
debug = 2
|
|
||||||
lto = true
|
|
||||||
opt-level = 'z'
|
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
debug = 2
|
|
||||||
lto = true
|
|
||||||
opt-level = "z"
|
|
||||||
0
.gitignore → firmware/.gitignore
vendored
0
.gitignore → firmware/.gitignore
vendored
27
Cargo.lock → firmware/Cargo.lock
generated
27
Cargo.lock → firmware/Cargo.lock
generated
|
|
@ -763,12 +763,9 @@ dependencies = [
|
||||||
"embedded-io",
|
"embedded-io",
|
||||||
"embedded-io-async",
|
"embedded-io-async",
|
||||||
"embedded-storage",
|
"embedded-storage",
|
||||||
"fixed",
|
|
||||||
"heapless",
|
"heapless",
|
||||||
"mipidsi",
|
"mipidsi",
|
||||||
"rgb",
|
"panic-probe",
|
||||||
"smart-leds",
|
|
||||||
"tinybmp",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -873,6 +870,16 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
|
|
@ -1076,9 +1083,6 @@ 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"
|
||||||
|
|
@ -1263,15 +1267,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tinybmp"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df43af2cb7b369009aa14144959bb4f2720ab62034c9073242f2d3a186c2edb6"
|
|
||||||
dependencies = [
|
|
||||||
"embedded-graphics",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.19.0"
|
version = "1.19.0"
|
||||||
|
|
@ -3,6 +3,7 @@ edition = "2024"
|
||||||
name = "led-mask"
|
name = "led-mask"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Adam Gausmann <adam@gaussian.dev>"]
|
authors = ["Adam Gausmann <adam@gaussian.dev>"]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "led-mask"
|
name = "led-mask"
|
||||||
|
|
@ -12,6 +13,7 @@ bench = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
defmt = "1.0"
|
defmt = "1.0"
|
||||||
defmt-rtt = "1.1"
|
defmt-rtt = "1.1"
|
||||||
|
panic-probe = { version = "1.0", features = ["print-defmt"] }
|
||||||
|
|
||||||
embedded-hal = "1.0.0"
|
embedded-hal = "1.0.0"
|
||||||
embedded-hal-async = "1.0.0"
|
embedded-hal-async = "1.0.0"
|
||||||
|
|
@ -46,7 +48,13 @@ 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"
|
|
||||||
smart-leds = "0.4.0"
|
[profile.release]
|
||||||
fixed = "1.29.0"
|
debug = 2
|
||||||
tinybmp = "0.6.0"
|
lto = true
|
||||||
|
opt-level = 'z'
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
debug = 2
|
||||||
|
lto = true
|
||||||
|
opt-level = "z"
|
||||||
|
|
|
||||||
|
|
@ -1,183 +0,0 @@
|
||||||
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.
|
Before Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
|
|
@ -1,101 +0,0 @@
|
||||||
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,61 +1,40 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
mod art;
|
|
||||||
mod led_matrix;
|
|
||||||
|
|
||||||
use core::panic::PanicInfo;
|
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_rp::bind_interrupts;
|
use embassy_futures::select::{select_array, select4};
|
||||||
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_time::{Delay, Timer};
|
||||||
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 embedded_hal_bus::spi::ExclusiveDevice;
|
||||||
|
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 {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
use crate::art::{ColorMapFilter, Hal, HappyEyes, Rainbow, RainbowFilter};
|
use embassy_rp::spi;
|
||||||
use crate::led_matrix::{LedMatrix, Serpentine};
|
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 defmt_rtt 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());
|
||||||
|
|
||||||
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
|
// display SPI
|
||||||
|
|
||||||
let mut display_buffer = [0u8; 512];
|
let mut display_buffer = [0u8; 512];
|
||||||
|
|
||||||
let mut _display = {
|
let mut display = {
|
||||||
let mut config = spi::Config::default();
|
let mut config = spi::Config::default();
|
||||||
config.frequency = DISPLAY_FREQ;
|
config.frequency = DISPLAY_FREQ;
|
||||||
config.phase = spi::Phase::CaptureOnSecondTransition;
|
config.phase = spi::Phase::CaptureOnSecondTransition;
|
||||||
|
|
@ -78,8 +57,17 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
.init(&mut Delay)
|
.init(&mut Delay)
|
||||||
.expect("display init")
|
.expect("display init")
|
||||||
};
|
};
|
||||||
// Blank display - unused at the moment
|
let _display_backlight = Output::new(p.PIN_20, Level::High);
|
||||||
let _display_backlight = Output::new(p.PIN_20, Level::Low);
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
// setup RGB LED
|
// setup RGB LED
|
||||||
let mut rg_config = pwm::Config::default();
|
let mut rg_config = pwm::Config::default();
|
||||||
|
|
@ -90,7 +78,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 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 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_a = 0x101 * (255 - r as u16);
|
||||||
rg_config.compare_b = 0x101 * (255 - g as u16);
|
rg_config.compare_b = 0x101 * (255 - g as u16);
|
||||||
b_config.compare_a = 0x101 * (255 - b as u16);
|
b_config.compare_a = 0x101 * (255 - b as u16);
|
||||||
|
|
@ -98,278 +86,52 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
b_pwm.set_config(&b_config);
|
b_pwm.set_config(&b_config);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut led_pio = Pio::new(p.PIO0, Irqs);
|
// Setup buttons
|
||||||
let led_program = PioWs2812Program::new(&mut led_pio.common);
|
let _btn_a = Input::new(p.PIN_12, Pull::Up);
|
||||||
let led_surface = LedMatrix::new(
|
let _btn_b = Input::new(p.PIN_13, Pull::Up);
|
||||||
Serpentine {
|
// let _btn_x = Input::new(p.PIN_14, Pull::Up);
|
||||||
size: Size::new(40, 8),
|
let _btn_y = Input::new(p.PIN_15, Pull::Up);
|
||||||
},
|
|
||||||
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
|
let mut btn_f1 = Input::new(p.PIN_11, Pull::Up);
|
||||||
loop {
|
let mut btn_f2 = Input::new(p.PIN_14, Pull::Up);
|
||||||
Timer::after_secs(1).await
|
let mut btn_f3 = Input::new(p.PIN_9, Pull::Up);
|
||||||
}
|
let mut btn_f4 = Input::new(p.PIN_3, Pull::Up);
|
||||||
}
|
|
||||||
|
|
||||||
struct Buttons {
|
let mut render = |f_keys: [bool; 4]| {
|
||||||
f1: Input<'static>,
|
display.clear(Rgb565::BLACK).ok();
|
||||||
f2: Input<'static>,
|
ferris.draw(&mut display).ok();
|
||||||
f3: Input<'static>,
|
hello_text.draw(&mut display).ok();
|
||||||
f4: Input<'static>,
|
|
||||||
a: Input<'static>,
|
|
||||||
b: Input<'static>,
|
|
||||||
// x = f2
|
|
||||||
y: Input<'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Buttons {
|
let mut f_key_string: String<16> = String::new();
|
||||||
fn read(&self) -> ButtonStates {
|
for (f, s) in f_keys.into_iter().zip(["1", "2", "3", "4"]) {
|
||||||
ButtonStates {
|
f_key_string.push_str(f.then_some(s).unwrap_or(" ")).ok();
|
||||||
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 {
|
Text::new(&f_key_string, Point::new(0, 108), style)
|
||||||
// Track the most recent chord key state.
|
.draw(&mut display)
|
||||||
pending_chord = new_chord;
|
.ok();
|
||||||
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 mut last_state = None;
|
||||||
let start = Instant::now();
|
|
||||||
|
|
||||||
let mut led_config = LedConfig::default();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let elapsed = start.elapsed();
|
let state = [
|
||||||
rainbow_slow.elapsed = elapsed;
|
btn_f1.is_low(),
|
||||||
rainbow_fast.elapsed = elapsed;
|
btn_f2.is_low(),
|
||||||
|
btn_f3.is_low(),
|
||||||
if let Some(new_config) = LED_CONFIG_SIGNAL.try_take() {
|
btn_f4.is_low(),
|
||||||
led_config = new_config;
|
];
|
||||||
|
if Some(state) != last_state {
|
||||||
|
render(state);
|
||||||
|
last_state = Some(state);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
select_array([
|
||||||
led_surface.clear(Rgb888::BLACK).ok();
|
btn_f1.wait_for_any_edge(),
|
||||||
match led_config.pattern {
|
btn_f2.wait_for_any_edge(),
|
||||||
LedPattern::Blank => {}
|
btn_f3.wait_for_any_edge(),
|
||||||
LedPattern::Hal => match led_config.hal {
|
btn_f4.wait_for_any_edge(),
|
||||||
HalConfig::Red => {
|
])
|
||||||
hal.draw(&mut led_surface).ok();
|
.await;
|
||||||
}
|
|
||||||
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