#![allow(missing_docs)]
#![no_std]
#![no_main]
#![allow(clippy::future_not_send, reason = "single-threaded")]
use core::convert::Infallible;
use defmt::info;
use defmt_rtt as _;
use device_envoy_rp::button::Button as _;
use device_envoy_rp::button::{ButtonRp, PressedTo};
use device_envoy_rp::led_strip::Current;
use device_envoy_rp::led_strip::Gamma;
use device_envoy_rp::led2d;
use device_envoy_rp::led2d::Frame2d;
use device_envoy_rp::led2d::Led2dFont;
use device_envoy_rp::led2d::layout::LedLayout;
use device_envoy_rp::{Error, Result, led2d::Led2d as _};
use embassy_executor::Spawner;
use embassy_futures::select::{Either, select};
use embassy_rp::init;
use embassy_time::{Duration, Timer};
use heapless::Vec;
use panic_probe as _;
use smart_leds::{RGB8, colors};
const LED_LAYOUT_4X12: LedLayout<48, 12, 4> = LedLayout::serpentine_column_major();
led2d! {
Led4x12 {
pin: PIN_3,
led_layout: LED_LAYOUT_4X12,
font: Led2dFont::Font3x4Trim,
pio: PIO1,
dma: DMA_CH0,
max_current: Current::Milliamps(500),
gamma: Gamma::Linear,
max_frames: 32,
}
}
#[embassy_executor::main]
pub async fn main(spawner: Spawner) -> ! {
let err = inner_main(spawner).await.unwrap_err();
core::panic!("{err}");
}
async fn inner_main(spawner: Spawner) -> Result<Infallible> {
info!("LED 2D API Exploration (12x4 display)");
let p = init(Default::default());
let led4x12 = Led4x12::new(p.PIN_3, p.PIO1, p.DMA_CH0, spawner)?;
let mut button = ButtonRp::new(p.PIN_13, PressedTo::Ground);
loop {
info!("Demo 1: 3x4 font (\"RUST\" in four colors)");
demo_rust_text(&led4x12).await?;
button.wait_for_press().await;
info!("Demo 2: Blink text (\"RUST\")");
demo_blink_text(&led4x12).await?;
button.wait_for_press().await;
info!("Demo 3: Colored corners");
demo_colored_corners(&led4x12).await?;
button.wait_for_press().await;
info!("Demo 4: Blink pattern");
demo_blink_pattern(&led4x12).await?;
button.wait_for_press().await;
info!("Demo 5: Rectangle with diagonals (embedded-graphics)");
demo_rectangle_diagonals_embedded_graphics(&led4x12).await?;
button.wait_for_press().await;
info!("Demo 6: Bouncing dot (manual frames)");
demo_bouncing_dot_manual(&led4x12, &mut button).await?;
info!("Demo 7: Bouncing dot (animation)");
demo_bouncing_dot_animation(&led4x12).await?;
button.wait_for_press().await;
}
}
async fn demo_rust_text(led4x12: &Led4x12) -> Result<()> {
let colors = [colors::RED, colors::GREEN, colors::BLUE, colors::YELLOW];
led4x12.write_text("RUST\ntwo", &colors);
Ok(())
}
async fn demo_blink_text(led4x12: &Led4x12) -> Result<()> {
let mut on_frame = Frame2d::new();
led4x12.write_text_to_frame(
"rust",
&[colors::RED, colors::GREEN, colors::BLUE, colors::YELLOW],
&mut on_frame,
);
led4x12.animate([
(on_frame, Duration::from_millis(500)),
(Frame2d::new(), Duration::from_millis(500)),
]);
Ok(())
}
async fn demo_colored_corners(led4x12: &Led4x12) -> Result<()> {
let mut frame = Frame2d::new();
frame[(0, 0)] = colors::RED; frame[(Led4x12::WIDTH - 1, 0)] = colors::GREEN; frame[(0, Led4x12::HEIGHT - 1)] = colors::BLUE; frame[(Led4x12::WIDTH - 1, Led4x12::HEIGHT - 1)] = colors::YELLOW;
led4x12.write_frame(frame);
Ok(())
}
async fn demo_blink_pattern(led4x12: &Led4x12) -> Result<()> {
let mut on_frame = Frame2d::new();
for y_index in 0..Led4x12::HEIGHT {
for x_index in 0..Led4x12::WIDTH {
if (y_index + x_index) % 2 == 0 {
on_frame[(x_index, y_index)] = colors::CYAN;
}
}
}
led4x12.animate([
(on_frame, Duration::from_millis(500)),
(Frame2d::new(), Duration::from_millis(500)),
]);
Ok(())
}
async fn demo_rectangle_diagonals_embedded_graphics(led4x12: &Led4x12) -> Result<()> {
use device_envoy_rp::led_strip::ToRgb888;
use embedded_graphics::{
Drawable,
prelude::*,
primitives::{Line, PrimitiveStyle, Rectangle},
};
let mut frame = Frame2d::new();
let frame_top_left = Led4x12::TOP_LEFT;
let frame_size = Led4x12::SIZE;
let frame_bottom_right = Led4x12::BOTTOM_RIGHT;
let frame_bottom_left = Led4x12::BOTTOM_LEFT;
let frame_top_right = Led4x12::TOP_RIGHT;
Rectangle::new(frame_top_left, frame_size)
.into_styled(PrimitiveStyle::with_stroke(colors::RED.to_rgb888(), 1))
.draw(&mut frame)?;
Line::new(frame_top_left, frame_bottom_right)
.into_styled(PrimitiveStyle::with_stroke(colors::BLUE.to_rgb888(), 1))
.draw(&mut frame)?;
Line::new(frame_bottom_left, frame_top_right)
.into_styled(PrimitiveStyle::with_stroke(colors::BLUE.to_rgb888(), 1))
.draw(&mut frame)?;
led4x12.write_frame(frame);
Ok(())
}
struct BouncingDot {
x_position: isize,
y_position: isize,
x_velocity: isize,
y_velocity: isize,
x_limit: isize,
y_limit: isize,
color_index: usize,
colors: [RGB8; 3],
frame_delay: Duration,
}
impl BouncingDot {
fn new() -> Self {
const FRAME_DELAY: Duration = Duration::from_millis(50);
Self {
x_position: 0,
y_position: 0,
x_velocity: 1,
y_velocity: 1,
x_limit: Led4x12::WIDTH as isize,
y_limit: Led4x12::HEIGHT as isize,
color_index: 0,
colors: [colors::RED, colors::GREEN, colors::BLUE],
frame_delay: FRAME_DELAY,
}
}
fn step_axis(position: &mut isize, velocity: &mut isize, limit: isize) -> bool {
*position += *velocity;
if (0..limit).contains(position) {
return false;
}
*velocity = -*velocity;
*position += *velocity; true
}
fn advance(&mut self) -> bool {
let hit_x = Self::step_axis(&mut self.x_position, &mut self.x_velocity, self.x_limit);
let hit_y = Self::step_axis(&mut self.y_position, &mut self.y_velocity, self.y_limit);
hit_x | hit_y
}
fn current_color(&self) -> RGB8 {
self.colors[self.color_index]
}
fn advance_color(&mut self) {
self.color_index = (self.color_index + 1) % self.colors.len();
}
async fn run(&mut self, led4x12: &Led4x12) -> Result<Infallible> {
loop {
let mut frame = Frame2d::new();
let x_position =
usize::try_from(self.x_position).expect("x position must be nonnegative");
let y_position =
usize::try_from(self.y_position).expect("y position must be nonnegative");
assert!(x_position < Led4x12::WIDTH);
assert!(y_position < Led4x12::HEIGHT);
frame[(x_position, y_position)] = self.current_color();
led4x12.write_frame(frame);
if self.advance() {
self.advance_color();
}
Timer::after(self.frame_delay).await;
}
}
}
async fn demo_bouncing_dot_manual(led4x12: &Led4x12, button: &mut ButtonRp<'_>) -> Result<()> {
let mut bouncing_dot = BouncingDot::new();
match select(bouncing_dot.run(led4x12), button.wait_for_press()).await {
Either::First(result) => result.map(|_| ()),
Either::Second(_) => Ok(()),
}
}
async fn demo_bouncing_dot_animation(led4x12: &Led4x12) -> Result<()> {
let mut color_cycle = [colors::CYAN, colors::YELLOW, colors::LIME].iter().cycle();
fn step_and_hit(position: &mut isize, velocity: &mut isize, limit: isize) -> bool {
*position += *velocity;
if (0..limit).contains(position) {
return false;
}
*velocity = -*velocity;
*position += *velocity; true
}
let mut frames = Vec::<_, { Led4x12::MAX_FRAMES }>::new();
let (mut x, mut y) = (0isize, 0isize);
let (mut vx, mut vy) = (1isize, 1isize);
let (x_limit, y_limit) = (Led4x12::WIDTH as isize, Led4x12::HEIGHT as isize);
let mut color = *color_cycle.next().unwrap();
for _ in 0..Led4x12::MAX_FRAMES {
let mut frame = Frame2d::new();
frame[(x as usize, y as usize)] = color;
frames
.push((frame, Duration::from_millis(50)))
.map_err(|_| Error::FormatError)?;
if step_and_hit(&mut x, &mut vx, x_limit) | step_and_hit(&mut y, &mut vy, y_limit) {
color = *color_cycle.next().unwrap();
}
}
led4x12.animate(frames);
Ok(())
}