From ba09e87c1e763e78ba27d655cd6a8298b766123f Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Wed, 25 Dec 2024 11:23:38 +0100 Subject: [PATCH] stars, moon, trees, houses and snow * Version of Family Christmas 2024 --- Cargo.toml | 1 + build.rs | 2 +- examples/format.rs | 2 +- examples/hello.rs | 2 +- src/bit_string/thumbv6m.rs | 1 + src/led_display/draw.rs | 21 +++ src/led_display/mod.rs | 23 +-- src/lib.rs | 372 ++++++++++++++++++++++++++++++++----- src/main.rs | 129 +++++-------- src/neo_pixel/mod.rs | 342 ++-------------------------------- src/neo_pixel/screen.rs | 293 +++++++++++++++++++++++++++++ 11 files changed, 707 insertions(+), 481 deletions(-) create mode 100644 src/led_display/draw.rs create mode 100644 src/neo_pixel/screen.rs diff --git a/Cargo.toml b/Cargo.toml index d2079c6..1b0cfef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ panic-probe = { version = "0.3", features = ["print-defmt"] } embedded-hal = "1.0.0" embedded-alloc = "0.6.0" +rand = { version = "0.9.0-beta.1", features = ["small_rng"], default-features = false } [dev-dependencies] defmt-test = "0.3" diff --git a/build.rs b/build.rs index 4f6cfd9..2a51f4e 100644 --- a/build.rs +++ b/build.rs @@ -40,4 +40,4 @@ fn main() { // Set the linker script to the one provided by cortex-m-rt. println!("cargo:rustc-link-arg=-Tlink.x"); -} \ No newline at end of file +} diff --git a/examples/format.rs b/examples/format.rs index 31a357e..1f2d829 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,8 +1,8 @@ #![no_main] #![no_std] -use template_microbit as _; // global logger + panicking-behavior + memory layout use defmt::Format; // <- derive attribute +use template_microbit as _; // global logger + panicking-behavior + memory layout #[derive(Format)] struct S1 { diff --git a/examples/hello.rs b/examples/hello.rs index 36d8bc1..00d034e 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -12,7 +12,7 @@ fn main() -> ! { board.display_pins.col1.into_pulldown_input(); board.display_pins.row1.into_push_pull_output(High); - defmt::println!("Hello, world! freq ", ); + defmt::println!("Hello, world! freq ",); template_microbit::exit() } diff --git a/src/bit_string/thumbv6m.rs b/src/bit_string/thumbv6m.rs index 83e24bf..aecdbdb 100644 --- a/src/bit_string/thumbv6m.rs +++ b/src/bit_string/thumbv6m.rs @@ -1,6 +1,7 @@ use super::Sender; use core::arch::asm; +#[derive(Default)] pub struct Msb2LsbSender< const BITS: u32, const SHIFT: u32, diff --git a/src/led_display/draw.rs b/src/led_display/draw.rs new file mode 100644 index 0000000..2c92245 --- /dev/null +++ b/src/led_display/draw.rs @@ -0,0 +1,21 @@ +pub trait DisplayRectangles { + fn fill_rectangle(&mut self, a: Position, b: Position, color: Color); + + fn rectangle(&mut self, bottom_left: Position, upper_right: Position, color: Color); +} + +pub trait DisplayCircles { + fn fill_circle(&mut self, origin: Position, radius: usize, color: Color); + + fn circle(&mut self, origin: Position, radius: usize, color: Color); +} + +pub type Sprite = [[Color; X]; Y]; + +pub trait DisplaySprites { + fn copy( + &mut self, + origin: Position, + data: &Sprite, + ); +} diff --git a/src/led_display/mod.rs b/src/led_display/mod.rs index 098caeb..dc9aa0d 100644 --- a/src/led_display/mod.rs +++ b/src/led_display/mod.rs @@ -1,22 +1,13 @@ -pub trait PixelDisplay { +mod draw; + +pub use draw::*; + +pub trait Screen { fn show(&self); fn clear(&mut self); - fn fill(&mut self, value: u32); - - fn point(&mut self, position: Position, value: u32); - - fn fill_rectangle(&mut self, a: Position, b: Position, value: u32); - - fn fill_circle(&mut self, origin: Position, radius: usize, value: u32); - - fn rectangle(&mut self, bottom_left: Position, upper_right: Position, value: u32); - - fn circle(&mut self, origin: Position, radius: usize, value: u32); - - // fn segment(&mut self, origin: Position, end: Position, value: u32); - // - // fn copy(&mut self, origin: Position, data: &[[u32]]); + fn fill(&mut self, color: Color); + fn point(&mut self, position: Position, color: Color); } diff --git a/src/lib.rs b/src/lib.rs index b1f1eab..dc314fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,52 +1,322 @@ -// #![no_main] +//#![no_main] #![no_std] -// -// use cortex_m_semihosting::debug; -// -// use defmt_rtt as _; // global logger -// -// // use some_hal as _; // memory layout -// use microbit as _; -// -// use panic_probe as _; -// -// // same panicking *behavior* as `panic-probe` but doesn't print a panic message -// // this prevents the panic message being printed *twice* when `defmt::panic` is invoked -// #[defmt::panic_handler] -// fn panic() -> ! { -// cortex_m::asm::udf() -// } -// -// /// Terminates the application and makes a semihosting-capable debug tool exit -// /// with status code 0. -// pub fn exit() -> ! { -// loop { -// debug::exit(debug::EXIT_SUCCESS); -// } -// } -// -// /// Hardfault handler. -// /// -// /// Terminates the application and makes a semihosting-capable debug tool exit -// /// with an error. This seems better than the default, which is to spin in a -// /// loop. -// #[cortex_m_rt::exception] -// unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! { -// loop { -// debug::exit(debug::EXIT_FAILURE); -// } -// } -// -// // defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used -// // once within a crate. the module can be in any file but there can only be at most -// // one `#[tests]` module in this library crate -// #[cfg(test)] -// #[defmt_test::tests] -// mod unit_tests { -// use defmt::assert; -// -// #[test] -// fn it_works() { -// assert!(true) -// } -// } + +pub mod bit_string; +pub mod led_display; +pub mod neo_pixel; + +use crate::led_display::{DisplaySprites, Screen, Sprite}; +use crate::neo_pixel::{Color, CompositeNeoPixelDisplay, DisplayCircles, DisplayRectangles}; +use cortex_m::asm::delay; +use rand::rngs::SmallRng; +use rand::{RngCore, SeedableRng}; + +mod hardware { + use super::neo_pixel::{CompositeNeoPixelDisplay, NeoPixelSender}; + use cortex_m::asm::delay; + use microbit::hal::gpio::Level::Low; + use microbit::Board; + + // Bit delay, starting always from set HIGH, wait, go to LOW, wait, repeat + const T0H_NS: u32 = 250; // 350+/-150 [ns] + const T1H_NS: u32 = 900; // 700+/-150 [ns] + + // Latch reset delay + const TLL_NS: u32 = 250_000; // 6_000 [ns] min, newer pixels require ~250_000 [ns] + + const T0L_NS: u32 = 900; // 800+/-150 [ns] + const T1L_NS: u32 = 600; // 600+/-150 [ns] + + // Rounded down, from 20.8333 + pub const NS_PER_CYCLE: u32 = 114; // empirically measured to be about 114ns + + const T0H: u32 = T0H_NS / NS_PER_CYCLE; + const T0L: u32 = T0L_NS / NS_PER_CYCLE; + const T1H: u32 = T1H_NS / NS_PER_CYCLE; + const T1L: u32 = T1L_NS / NS_PER_CYCLE; + const TLL: u32 = TLL_NS / NS_PER_CYCLE; + + // TODO RETRIEVE THIS FROM PAC + const EDGE_PAD0_MASK: u32 = 1 << 3; + const EDGE_PAD0_SET_ADDRESS: u32 = 0x5000_0508; // Address of the SET register + const EDGE_PAD0_CLEAR_ADDRESS: u32 = 0x5000_050c; // Address of the toggle register + + pub fn setup() -> CompositeNeoPixelDisplay< + NeoPixelSender< + EDGE_PAD0_MASK, + EDGE_PAD0_SET_ADDRESS, + EDGE_PAD0_CLEAR_ADDRESS, + T0H, + T0L, + T1H, + T1L, + >, + TLL, + 8, + 8, + 4, + 1, + > { + let board = Board::take().unwrap(); + let _pin = board.edge.e00.into_push_pull_output(Low); + delay(TLL * 2); + + let sender = NeoPixelSender::< + EDGE_PAD0_MASK, + EDGE_PAD0_SET_ADDRESS, + EDGE_PAD0_CLEAR_ADDRESS, + T0H, + T0L, + T1H, + T1L, + >::new(); + + CompositeNeoPixelDisplay::<_, TLL, 8, 8, 4 /*NX*/, 1 /*NY*/>::new(sender) + } +} + +// const BLANK: Option = None; +impl< + Sender: neo_pixel::Sender, + const DELAY_RESET: u32, + const X: usize, + const Y: usize, + const NX: usize, + const NY: usize, + > CompositeNeoPixelDisplay +{ + fn dx(frame: i32, fps: i32, speed: i32) -> i32 { + ((speed * frame / fps) / 512) % 32 + } + + fn dy(frame: i32, fps: i32, speed: i32) -> i32 { + ((speed * frame / fps) / 512) % 8 + } + + fn paint_with_color( + &mut self, + x0: usize, + y0: usize, + sprite: &[[bool; SPRITE_Y]; SPRITE_X], + color: u32, + ) { + for (x, line) in sprite.iter().enumerate() { + for (y, paint) in line.iter().enumerate() { + if *paint { + self.point(self.position((x0 + x) % 32, (y0 + y) % 8), color); + } + } + } + } + + fn paint_list( + &mut self, + list: &[Option<(u8, u8, Color)>; S], + dx: u8, + dy: u8, + tree: &[[bool; ELEM_X]; ELEM_Y], + ) { + for (x0, y0, color) in list.iter().flatten() { + self.paint_with_color((*x0 + dx) as usize, (*y0 + dy) as usize, tree, *color); + } + } + + fn stars_far(&mut self, frame: i32, fps: i32, rng: &mut SmallRng) { + // We repeat the same 2-screen pseudo-random pattern. We have an area of (3 * 8 * 2 =) 48 + // pixels we want to cover with some stars. so we arbitrarily decided 6 stars was enough. + const NB_STARS: usize = 6; + + // Choose arbitrarily positions for the stars + const STAR_POSITIONS: [(u8, u8); NB_STARS] = + [(10, 4), (6, 4), (13, 5), (8, 6), (2, 6), (12, 7)]; + + // Color palette of the stars, we are going to randomly choose one among these + const COLORS: [Color; 10] = [ + 0x030300, 0x040402, 0x050502, 0x060602, 0x070702, 0x080802, 0x090902, 0x0a0a02, + 0x0b0b02, 0x0c0c03, + ]; + + let refresh_color = frame % (fps) == 0; + + for (i, (x, y)) in STAR_POSITIONS.iter().enumerate() { + // We have an unsafe block because of the static mut array. It declared here so it + // won't be visible from anywhere else, so it is safe to use in a single-threaded case. + unsafe { + static mut STAR_COLOR: [Color; NB_STARS * 2] = [0x0; NB_STARS * 2]; + + if refresh_color { + STAR_COLOR[i] = COLORS[(rng.next_u32() % 10) as usize]; + STAR_COLOR[i + NB_STARS] = COLORS[(rng.next_u32() % 10) as usize]; + } + // Screens 1 & 2 + self.point(self.position(*x as usize, *y as usize), STAR_COLOR[i]); + + // Screens 3 & 4 + self.point( + self.position((*x + 16) as usize, *y as usize), + STAR_COLOR[i + NB_STARS], + ); + } + } + } + + fn moon(&mut self, frame: i32, fps: i32) { + const COLOR: Option = Some(0x1b1a1a); + let x = Self::dx(frame, fps, 256); + let dy = Self::dy(frame, fps, 128); + let y = if dy < 4 { dy + 2 } else { 11 - dy }; + let origin = self.position(x as usize, y as usize); + self.fill_circle(origin, 2, COLOR); + } + + fn stars_close(&mut self, frame: i32, fps: i32) { + // We repeat the same 2-screen pseudo-random pattern. We have an area of (3 * 8 * 2 =) 48 + // pixels we want to cover with some stars. so we arbitrarily decided 6 stars was enough. + const NB_STARS: usize = 3; + + // Choose arbitrarily positions for the stars + const STAR_POSITIONS: [(u8, u8); NB_STARS] = [(4, 3), (16, 5), (26, 4)]; + // [(5, 3), (10, 4), (16, 5), (20, 4), (24, 6), (30, 5)]; + + // Color palette of the stars, we are going to randomly choose one among these + //const COLORS: [Color; NB_STARS] = + // [0x070702, 0x080802, 0x090902, 0x0a0a02, 0x0b0b02, 0x0c0c03]; + const COLORS: [Color; NB_STARS] = [0x171712, 0x181812, 0x191912]; + // [0x171712, 0x181812, 0x191912, 0x1a1a12, 0x1b1b12, 0x1c1c13]; + + const STAR: [[bool; 3]; 3] = [ + [false, true, false], + [true, true, true], + [false, true, false], + ]; + + for (i, (x, y)) in STAR_POSITIONS.iter().enumerate() { + let x = ((*x as i32 + Self::dx(frame, fps, 64)) % 32) as usize; + let y = *y as usize; + self.paint_with_color(x, y, &STAR, COLORS[i]); + } + } + + fn snow(&mut self) { + self.fill_rectangle(self.position(0, 0), self.position(31, 2), Some(0x0a0a0a)); + } + + fn trees(&mut self, frame: i32, fps: i32) { + const TREE2_L: [[bool; 2]; 2] = [[true, true], [true, false]]; + + const TREE2_R: [[bool; 2]; 2] = [[true, false], [true, true]]; + + const TREE3: [[bool; 2]; 3] = [[true, false], [true, true], [true, false]]; + + const TREE4: [[bool; 4]; 3] = [ + [true, false, true, false], + [true, true, true, true], + [true, false, true, false], + ]; + + const TREES_2L: [Option<(u8, u8, Color)>; 4] = [ + Some((2, 3, 0x0a0000)), + Some((12, 3, 0x0a0000)), + Some((18, 3, 0x0a0000)), + Some((24, 3, 0x0a0000)), + ]; + + const TREES_2R: [Option<(u8, u8, Color)>; 4] = [ + Some((3, 3, 0x140000)), + Some((10, 3, 0x140000)), + Some((20, 3, 0x140000)), + Some((26, 3, 0x140000)), + ]; + + const TREES_3L: [Option<(u8, u8, Color)>; 3] = [ + Some((6, 2, 0x250000)), + Some((14, 2, 0x250000)), + Some((27, 2, 0x250000)), + ]; + + const TREES_4: [Option<(u8, u8, Color)>; 2] = + [Some((6, 1, 0x3b0000)), Some((26, 1, 0x3d0000))]; + + self.paint_list(&TREES_2L, Self::dx(frame, fps, 128) as u8, 0, &TREE2_L); + self.paint_list(&TREES_2R, Self::dx(frame, fps, 128) as u8, 0, &TREE2_R); + self.paint_list(&TREES_3L, Self::dx(frame, fps, 256) as u8, 0, &TREE3); + self.paint_list(&TREES_4, Self::dx(frame, fps, 512) as u8, 0, &TREE4); + } + + fn houses(&mut self, frame: i32, fps: i32) { + const BLANK_: Option = None; + const RED___: Option = Some(0x003300); + const BLUE__: Option = Some(0x000033); + const PURPLE: Option = Some(0x004433); + const YELLOW: Option = Some(0x444400); + const ORANGE: Option = Some(0x448800); + const BROWN_: Option = Some(0x8b4513); + const HOUSE1: Sprite, 5, 5> = [ + [BLANK_, BLANK_, RED___, BLANK_, BLANK_], + [PURPLE, PURPLE, PURPLE, RED___, BLANK_], + [ORANGE, PURPLE, YELLOW, PURPLE, RED___], + [PURPLE, PURPLE, PURPLE, RED___, BLANK_], + [BLANK_, BLANK_, RED___, BLANK_, BLANK_], + ]; + + const HOUSE2: Sprite, 5, 5> = [ + [BLANK_, BLANK_, RED___, BLANK_, BLANK_], + [BLUE__, BLUE__, BLUE__, RED___, BLANK_], + [ORANGE, BLUE__, YELLOW, BLUE__, RED___], + [BLUE__, BLUE__, BLUE__, RED___, BLANK_], + [BLANK_, BLANK_, RED___, BLANK_, BLANK_], + ]; + + self.copy( + self.position((3 + Self::dx(frame, fps, 1024)) as usize % 32, 0), + &HOUSE2, + ); + self.copy( + self.position((7 + Self::dx(frame, fps, 1024)) as usize % 32, 2), + &HOUSE1, + ); + self.copy( + self.position((14 + Self::dx(frame, fps, 1024)) as usize % 32, 1), + &HOUSE2, + ); + self.copy( + self.position((20 + Self::dx(frame, fps, 1024)) as usize % 32, 0), + &HOUSE2, + ); + self.copy( + self.position((27 + Self::dx(frame, fps, 1024)) as usize % 32, 1), + &HOUSE2, + ); + } + fn sled(&self, _frame: usize) { + todo!() + } +} + +pub fn main() { + const FRAME_PER_S: i32 = 50; + const DELAY_NS: i32 = 1_000_000_000 / FRAME_PER_S; + const DELAY: u32 = DELAY_NS as u32 / hardware::NS_PER_CYCLE; + + let mut display = hardware::setup(); + let mut rng = SmallRng::seed_from_u64(0xdead_beef); + let mut frame = 0; + loop { + display.clear(); + + // 10 Background + display.stars_far(frame, FRAME_PER_S, &mut rng); // OK + display.stars_close(frame, FRAME_PER_S); // OK + display.moon(frame, FRAME_PER_S); // OK + display.snow(); // OK + + display.trees(frame, FRAME_PER_S); // OK + display.houses(frame, FRAME_PER_S); + // + // display.sled(frame); + + display.show(); + frame += 1; + delay(DELAY); + } +} diff --git a/src/main.rs b/src/main.rs index cf5ea63..e9b5efd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,93 +1,60 @@ #![no_main] #![no_std] -use crate::neo_pixel::CompositeNeoPixelDisplay; -use cortex_m::asm; -use cortex_m::asm::delay; -use defmt_rtt as _; -use led_display::PixelDisplay; -use microbit::hal::gpio::Level::Low; -use microbit::Board; +//use cortex_m_semihosting::debug; + +use defmt_rtt as _; // global logger + +// use some_hal as _; // memory layout +use microbit as _; + use panic_probe as _; +use template_microbit as _; -mod bit_string; -mod led_display; -mod neo_pixel; +// same panicking *behavior* as `panic-probe` but doesn't print a panic message +// this prevents the panic message being printed *twice* when `defmt::panic` is invoked +// #[defmt::panic_handler] +// fn panic() -> ! { +// cortex_m::asm::udf() +// } -// Bit delay, starting always from set HIGH, wait, go to LOW, wait, repeat -const T0H_NS: u32 = 250; // 350+/-150 [ns] -const T1H_NS: u32 = 900; // 700+/-150 [ns] +/// Terminates the application and makes a semihosting-capable debug tool exit +/// with status code 0. +// pub fn exit() -> ! { +// loop { +// debug::exit(debug::EXIT_SUCCESS); +// } +// } -// Latch reset delay -const TLL_NS: u32 = 250_000; // 6_000 [ns] min, newer pixels require ~250_000 [ns] +/// Hardfault handler. +/// +/// Terminates the application and makes a semihosting-capable debug tool exit +/// with an error. This seems better than the default, which is to spin in a +/// loop. +// #[cortex_m_rt::exception] +// unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! { +// loop { +// debug::exit(debug::EXIT_FAILURE); +// } +// } -const T0L_NS: u32 = 900; // 800+/-150 [ns] -const T1L_NS: u32 = 600; // 600+/-150 [ns] - -// Rounded down, from 20.8333 -const NS_PER_CYCLE: u32 = 114; // empirically measured to be about 114ns - -const T0H: u32 = T0H_NS / NS_PER_CYCLE; -const T0L: u32 = T0L_NS / NS_PER_CYCLE; -const T1H: u32 = T1H_NS / NS_PER_CYCLE; -const T1L: u32 = T1L_NS / NS_PER_CYCLE; -const TLL: u32 = TLL_NS / NS_PER_CYCLE; - -// TODO RETRIEVE THIS FROM PAC -const EDGE_PAD0_MASK: u32 = 1 << 3; -const EDGE_PAD0_SET_ADDRESS: u32 = 0x5000_0508; // Address of the SET register -const EDGE_PAD0_CLEAR_ADDRESS: u32 = 0x5000_050c; // Address of the toggle register - -fn setup() -> CompositeNeoPixelDisplay<8, 8, 4, 1, 2192, 8, 1342178568, 1342178572, 2, 7, 7, 5> { - let board = Board::take().unwrap(); - let _pin = board.edge.e00.into_push_pull_output(Low); - delay(TLL * 2); - - CompositeNeoPixelDisplay::< - 8, - 8, - 4, /*NX*/ - 1, /*NY*/ - TLL, - EDGE_PAD0_MASK, - EDGE_PAD0_SET_ADDRESS, - EDGE_PAD0_CLEAR_ADDRESS, - T0H, - T0L, - T1H, - T1L, - >::new() -} +// // defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used +// // once within a crate. the module can be in any file but there can only be at most +// // one `#[tests]` module in this library crate +// #[cfg(test)] +// #[defmt_test::tests] +// mod unit_tests { +// use defmt::assert; +// +// #[test] +// fn it_works() { +// assert!(true) +// } +// } #[cortex_m_rt::entry] -fn main() -> ! { - const SPACING: usize = 32; - let mut display = setup(); - let mut frame = 0; +fn start() -> ! { + template_microbit::main(); - loop { - display.clear(); - - display.vbar_shift_left(0x020000, frame, SPACING); - display.vbar_shift_left(0x020000, frame + 1, SPACING); - display.vbar_shift_left(0x020000, frame + 2, SPACING); - display.vbar_shift_left(0x000200, frame + 3, SPACING); - display.vbar_shift_left(0x000200, frame + 4, SPACING); - display.vbar_shift_left(0x000002, frame + 5, SPACING); - display.fill_circle( - display.position((frame + 32 - 4) % { 4 * 8 }, 3), - 2, - 0x020000, - ); - display.circle( - display.position((frame + 32 - 4) % { 4 * 8 }, 3), - 3, - 0x040000, - ); - - display.show(); - frame += 1; - - asm::delay(200_000); // TODO: REPLACE WITH TIMER DELAY - } + defmt::panic!("main should not return") } diff --git a/src/neo_pixel/mod.rs b/src/neo_pixel/mod.rs index 7d929c8..236044a 100644 --- a/src/neo_pixel/mod.rs +++ b/src/neo_pixel/mod.rs @@ -1,20 +1,11 @@ -use crate::bit_string; -use crate::bit_string::Sender; -use crate::led_display::PixelDisplay; -use cortex_m::asm; -use defmt::{warn, Format}; +mod screen; -#[derive(Eq, Format, Ord, PartialEq, PartialOrd)] -pub struct Position { - x: usize, - y: usize, -} +pub use crate::bit_string::{self, Sender}; +pub use crate::led_display::{DisplayCircles, DisplayRectangles, DisplaySprites, Screen, Sprite}; +use defmt::Format; +pub use screen::CompositeNeoPixelDisplay; -impl Position { - fn new(x: usize, y: usize) -> Self { - Self { x, y } - } -} +pub type Color = u32; pub type NeoPixelSender< const PIN_MASK: u32, @@ -36,323 +27,14 @@ pub type NeoPixelSender< DELAY_ONE_LOW, >; -struct BufferPosition { - row: usize, - col: usize, - y: usize, +#[derive(Eq, Format, Ord, PartialEq, PartialOrd)] +pub struct Position { x: usize, + y: usize, } -impl TryFrom> - for BufferPosition -{ - type Error = (); - - fn try_from(position: Option) -> Result { - if let Some(position) = position { - position.try_into() - } else { - Err(()) - } - } -} - -impl TryFrom - for BufferPosition -{ - type Error = (); - - fn try_from(position: Position) -> Result { - let col = position.x / X; - let row = position.y / Y; - - if col > NX && row > NY { - return Err(()); - } - if col > NX { - return Err(()); - } - if row > NY { - return Err(()); - } - - let x = position.x % X; - let y = position.y % Y; - Ok(BufferPosition { row, col, y, x }) - } -} - -pub struct CompositeNeoPixelDisplay< - const X: usize, - const Y: usize, - const NX: usize, - const NY: usize, - const DELAY_RESET: u32, - const PIN_MASK: u32, - const PIN_LEVEL_HIGH_ADDRESS: u32, - const PIN_LEVEL_LOW_ADDRESS: u32, - const DELAY_ZERO_HIGH: u32, - const DELAY_ZERO_LOW: u32, - const DELAY_ONE_HIGH: u32, - const DELAY_ONE_LOW: u32, -> { - buffer: [[[[u32; X]; Y]; NX]; NY], - sender: NeoPixelSender< - PIN_MASK, - PIN_LEVEL_HIGH_ADDRESS, - PIN_LEVEL_LOW_ADDRESS, - DELAY_ZERO_HIGH, - DELAY_ZERO_LOW, - DELAY_ONE_HIGH, - DELAY_ONE_LOW, - >, -} - -impl< - const X: usize, - const Y: usize, - const NX: usize, - const NY: usize, - const DELAY_RESET: u32, - const PIN_MASK: u32, - const PIN_LEVEL_HIGH_ADDRESS: u32, - const PIN_LEVEL_LOW_ADDRESS: u32, - const DELAY_ZERO_HIGH: u32, - const DELAY_ZERO_LOW: u32, - const DELAY_ONE_HIGH: u32, - const DELAY_ONE_LOW: u32, - > - CompositeNeoPixelDisplay< - X, - Y, - NX, - NY, - DELAY_RESET, - PIN_MASK, - PIN_LEVEL_HIGH_ADDRESS, - PIN_LEVEL_LOW_ADDRESS, - DELAY_ZERO_HIGH, - DELAY_ZERO_LOW, - DELAY_ONE_HIGH, - DELAY_ONE_LOW, - > -{ - pub fn new() -> Self { - let display = Self { - buffer: [[[[0x0; X]; Y]; NX]; NY], - sender: NeoPixelSender::new(), - }; - - display.show(); - display - } - - pub fn position(&self, x: usize, y: usize) -> Option { - if (x < { X * NX }) && (y < { Y * NY }) { - Some(Position::new(x, y)) - } else { - warn!( - "Position({},{}) is outside the display range [(0,0);({},{})[.", - x, - y, - { X * NX }, - { Y * NY }, - ); - None - } - } - - // funzies - pub fn vbar_shift_left(&mut self, color: u32, offset: usize, spacing: usize) { - for i in 0..(X * Y * NX * NY) { - if (i % spacing) == 0 { - self.point(self.position((i + offset) % (X * NX), i / (X * NX)), color); - } - } - } -} - -impl< - const X: usize, - const Y: usize, - const NX: usize, - const NY: usize, - const DELAY_RESET: u32, - const PIN_MASK: u32, - const PIN_LEVEL_HIGH_ADDRESS: u32, - const PIN_LEVEL_LOW_ADDRESS: u32, - const DELAY_ZERO_HIGH: u32, - const DELAY_ZERO_LOW: u32, - const DELAY_ONE_HIGH: u32, - const DELAY_ONE_LOW: u32, - > PixelDisplay> - for CompositeNeoPixelDisplay< - X, - Y, - NX, - NY, - DELAY_RESET, - PIN_MASK, - PIN_LEVEL_HIGH_ADDRESS, - PIN_LEVEL_LOW_ADDRESS, - DELAY_ZERO_HIGH, - DELAY_ZERO_LOW, - DELAY_ONE_HIGH, - DELAY_ONE_LOW, - > -{ - fn show(&self) { - for r in 0..NY { - for c in 0..NX { - for y in 0..Y { - for x in 0..X { - self.sender.send(self.buffer[r][c][y][x]); - } - } - } - } - asm::delay(DELAY_RESET); - } - - fn clear(&mut self) { - self.fill(0x0); - } - - fn fill(&mut self, value: u32) { - self.fill_rectangle( - self.position(0, 0), - self.position(X * NX - 1, Y * NY - 1), - value, - ); - } - - fn point(&mut self, position: Option, value: u32) { - if let Ok(BufferPosition:: { row, col, y, x }) = position.try_into() { - self.buffer[row][col][y][x] = value; - } - } - - fn fill_rectangle(&mut self, a: Option, b: Option, value: u32) { - if let Some(a) = a { - if let Some(b) = b { - let bottom_left; - let upper_right; - if a < b { - bottom_left = a; - upper_right = b; - } else { - bottom_left = b; - upper_right = a; - } - let Position { x: x0, y: y0 } = bottom_left; - let Position { x: x1, y: y1 } = upper_right; - - for y in y0..=y1 { - for x in x0..=x1 { - self.point(self.position(x, y), value) - } - } - } - } - } - - fn fill_circle(&mut self, origin: Option, radius: usize, value: u32) { - if let Some(origin) = origin { - let Position { x: cx, y: cy } = origin; - let y0 = if cy < radius { 0 } else { cy - radius }; - let x0 = if cx < radius { 0 } else { cx - radius }; - - for y in y0..=(cy + radius) { - for x in x0..=(cx + radius) { - let dx = if x < cx { cx - x } else { x - cx }; - let dy = if y < cy { cy - y } else { y - cy }; - let h = dx * dx + dy * dy; - let epsilon = (radius * 2) + 1 / radius + 1; // todo simplify or tune some more - if h <= radius + epsilon { - self.point(self.position(x, y), value); - } - } - } - } - } - // fn segment(&mut self, a: Option, b: Option, value: u32) { - // if let Some(a) = &a { - // if let Some(b) = &b { - // let start = if a < b { a } else { b }; - // let end = if a < b { b } else { a }; - // let dx = end.x - start.x; - // let dy = end.y - start.y; - // for x in start.x..=end.x {} - // } - // } - // } - // - // fn copy(&mut self, origin: Option, data: &[[u32]]) { - // if let Some(origin) = origin { - // let Position { x: x0, y: y0 } = origin; - // let mut iy = 0; - // let mut ix = 0; - // for y in data.iter() { - // ix = 0; - // for x in y.iter() { - // self.point(self.position(ix, iy), *x); - // ix += 1; - // } - // iy += 1; - // } - // } - // } - - fn rectangle(&mut self, a: Option, b: Option, value: u32) { - if let Some(a) = a { - if let Some(b) = b { - let bottom_left; - let upper_right; - if a < b { - bottom_left = a; - upper_right = b; - } else { - bottom_left = b; - upper_right = a; - } - let Position { x: x0, y: y0 } = bottom_left; - let Position { x: x1, y: y1 } = upper_right; - - for y in y0..=y1 { - if (y != y0) && (y != y1) { - continue; - } - for x in x0..=x1 { - if (x != x0) && (x != x1) { - continue; - } - self.point(self.position(x, y), value); - } - } - } - } - } - - fn circle(&mut self, origin: Option, radius: usize, value: u32) { - if let Some(origin) = origin { - let Position { x: cx, y: cy } = origin; - let y0 = if cy < radius { 0 } else { cy - radius }; - let x0 = if cx < radius { 0 } else { cx - radius }; - - for y in y0..=(cy + radius) { - for x in x0..=(cx + radius) { - let dx = if x < cx { cx - x } else { x - cx }; - let dy = if y < cy { cy - y } else { y - cy }; - let r2 = radius * radius; - let h = dx * dx + dy * dy; - let d = if r2 < h { h - r2 } else { r2 - h }; - let epsilon = (radius * 2 + 1) / radius + 1; - if d < epsilon { - // we use a larger epsilon to have a full line - self.point(self.position(x, y), value); - } - } - } - } +impl Position { + fn new(x: usize, y: usize) -> Self { + Self { x, y } } } diff --git a/src/neo_pixel/screen.rs b/src/neo_pixel/screen.rs new file mode 100644 index 0000000..726c126 --- /dev/null +++ b/src/neo_pixel/screen.rs @@ -0,0 +1,293 @@ +use cortex_m::asm::delay; +use defmt::warn; + +use super::{Color, DisplayCircles, DisplayRectangles, DisplaySprites, Position, Screen, Sprite}; + +struct BufferPosition { + row: usize, + col: usize, + y: usize, + x: usize, +} + +impl TryFrom> + for BufferPosition +{ + type Error = (); + + fn try_from(position: Option) -> Result { + if let Some(position) = position { + position.try_into() + } else { + Err(()) + } + } +} + +impl TryFrom + for BufferPosition +{ + type Error = (); + + fn try_from(position: Position) -> Result { + let col = position.x / X; + let row = position.y / Y; + + if col > NX && row > NY { + return Err(()); + } + if col > NX { + return Err(()); + } + if row > NY { + return Err(()); + } + + let x = position.x % X; + let y = position.y % Y; + Ok(BufferPosition { row, col, y, x }) + } +} + +pub struct CompositeNeoPixelDisplay< + Sender: super::Sender, + const DELAY_RESET: u32, + const X: usize, + const Y: usize, + const NX: usize, + const NY: usize, +> { + buffer: [[[[u32; X]; Y]; NX]; NY], + sender: Sender, +} + +impl< + Sender: super::Sender, + const DELAY_RESET: u32, + const X: usize, + const Y: usize, + const NX: usize, + const NY: usize, + > CompositeNeoPixelDisplay +{ + pub fn new(sender: Sender) -> Self { + let display = Self { + buffer: [[[[0x0; X]; Y]; NX]; NY], + sender, + }; + + display.show(); + display + } + + pub fn position(&self, x: usize, y: usize) -> Option { + if (x < { X * NX }) && (y < { Y * NY }) { + Some(Position::new(x, y)) + } else { + warn!( + "Position({},{}) is outside the display range [(0,0);({},{})[.", + x, + y, + { X * NX }, + { Y * NY }, + ); + None + } + } +} + +impl< + Sender: super::Sender, + const DELAY_RESET: u32, + const X: usize, + const Y: usize, + const NX: usize, + const NY: usize, + > Screen, Color> + for CompositeNeoPixelDisplay +{ + fn show(&self) { + for r in 0..NY { + for c in 0..NX { + for y in 0..Y { + for x in 0..X { + self.sender.send(self.buffer[r][c][y][x]); + } + } + } + } + delay(DELAY_RESET); + } + + fn clear(&mut self) { + self.fill(0x0); + } + + fn fill(&mut self, color: Color) { + self.fill_rectangle( + self.position(0, 0), + self.position(X * NX - 1, Y * NY - 1), + Some(color), + ); + } + + fn point(&mut self, position: Option, color: Color) { + if let Ok(BufferPosition:: { row, col, y, x }) = position.try_into() { + self.buffer[row][col][y][x] = color; + } + } +} + +impl< + Sender: super::Sender, + const DELAY_RESET: u32, + const X: usize, + const Y: usize, + const NX: usize, + const NY: usize, + > DisplayRectangles, Option> + for CompositeNeoPixelDisplay +{ + fn fill_rectangle(&mut self, a: Option, b: Option, color: Option) { + let color = if let Some(c) = color { c } else { return }; + + if let Some(a) = a { + if let Some(b) = b { + let bottom_left; + let upper_right; + if a < b { + bottom_left = a; + upper_right = b; + } else { + bottom_left = b; + upper_right = a; + } + let Position { x: x0, y: y0 } = bottom_left; + let Position { x: x1, y: y1 } = upper_right; + + for y in y0..=y1 { + for x in x0..=x1 { + self.point(self.position(x, y), color) + } + } + } + } + } + + fn rectangle(&mut self, a: Option, b: Option, color: Option) { + if let Some(color) = color { + if let Some(a) = a { + if let Some(b) = b { + let bottom_left; + let upper_right; + if a < b { + bottom_left = a; + upper_right = b; + } else { + bottom_left = b; + upper_right = a; + } + let Position { x: x0, y: y0 } = bottom_left; + let Position { x: x1, y: y1 } = upper_right; + + for y in y0..=y1 { + if (y != y0) && (y != y1) { + continue; + } + for x in x0..=x1 { + if (x != x0) && (x != x1) { + continue; + } + self.point(self.position(x, y), color); + } + } + } + } + } + } +} + +impl< + Sender: super::Sender, + const DELAY_RESET: u32, + const X: usize, + const Y: usize, + const NX: usize, + const NY: usize, + > DisplayCircles, Option> + for CompositeNeoPixelDisplay +{ + fn fill_circle(&mut self, origin: Option, radius: usize, color: Option) { + if let Some(color) = color { + if let Some(origin) = origin { + let Position { x: cx, y: cy } = origin; + let y0 = if cy < radius { 0 } else { cy - radius }; + let x0 = if cx < radius { 0 } else { cx - radius }; + + for y in y0..=(cy + radius) { + for x in x0..=(cx + radius) { + let dx = if x < cx { cx - x } else { x - cx }; + let dy = if y < cy { cy - y } else { y - cy }; + let h = dx * dx + dy * dy; + let epsilon = (radius * 2) + 1 / radius + 1; // todo simplify or tune some more + if h <= radius + epsilon { + self.point(self.position(x, y), color); + } + } + } + } + } + } + + fn circle(&mut self, origin: Option, radius: usize, color: Option) { + if let Some(color) = color { + if let Some(origin) = origin { + let Position { x: cx, y: cy } = origin; + let y0 = if cy < radius { 0 } else { cy - radius }; + let x0 = if cx < radius { 0 } else { cx - radius }; + + for y in y0..=(cy + radius) { + for x in x0..=(cx + radius) { + let dx = if x < cx { cx - x } else { x - cx }; + let dy = if y < cy { cy - y } else { y - cy }; + let r2 = radius * radius; + let h = dx * dx + dy * dy; + let d = if r2 < h { h - r2 } else { r2 - h }; + let epsilon = (radius * 2 + 1) / radius + 1; + if d < epsilon { + // we use a larger epsilon to have a full line + self.point(self.position(x, y), color); + } + } + } + } + } + } +} + +impl< + Sender: super::Sender, + const DELAY_RESET: u32, + const X: usize, + const Y: usize, + const NX: usize, + const NY: usize, + > DisplaySprites, Option> + for CompositeNeoPixelDisplay +{ + fn copy( + &mut self, + origin: Option, + sprite: &Sprite, SW, SH>, + ) { + if let Some(Position { x: x0, y: y0 }) = origin { + for (x, line) in sprite.iter().enumerate() { + for (y, color) in line.iter().enumerate() { + if let &Some(color) = color { + self.point(self.position(x0 + x, y0 + y), color); + } + } + } + } + } +}