stars, moon, trees, houses and snow

* Version of Family Christmas 2024
This commit is contained in:
2024-12-25 11:23:38 +01:00
parent e70e2b2104
commit ba09e87c1e
11 changed files with 707 additions and 481 deletions

View File

@@ -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"

View File

@@ -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");
}
}

View File

@@ -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<T> {

View File

@@ -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()
}

View File

@@ -1,6 +1,7 @@
use super::Sender;
use core::arch::asm;
#[derive(Default)]
pub struct Msb2LsbSender<
const BITS: u32,
const SHIFT: u32,

21
src/led_display/draw.rs Normal file
View File

@@ -0,0 +1,21 @@
pub trait DisplayRectangles<Position, Color> {
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<Position, Color> {
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, const X: usize, const Y: usize> = [[Color; X]; Y];
pub trait DisplaySprites<Position, Color> {
fn copy<const SPRITE_X: usize, const SPRITE_Y: usize>(
&mut self,
origin: Position,
data: &Sprite<Color, SPRITE_X, SPRITE_Y>,
);
}

View File

@@ -1,22 +1,13 @@
pub trait PixelDisplay<Position> {
mod draw;
pub use draw::*;
pub trait Screen<Position, Color> {
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);
}

View File

@@ -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<Color> = None;
impl<
Sender: neo_pixel::Sender,
const DELAY_RESET: u32,
const X: usize,
const Y: usize,
const NX: usize,
const NY: usize,
> CompositeNeoPixelDisplay<Sender, DELAY_RESET, X, Y, NX, NY>
{
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<const SPRITE_X: usize, const SPRITE_Y: usize>(
&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<const S: usize, const ELEM_X: usize, const ELEM_Y: usize>(
&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<u32> = 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<Color> = None;
const RED___: Option<Color> = Some(0x003300);
const BLUE__: Option<Color> = Some(0x000033);
const PURPLE: Option<Color> = Some(0x004433);
const YELLOW: Option<Color> = Some(0x444400);
const ORANGE: Option<Color> = Some(0x448800);
const BROWN_: Option<Color> = Some(0x8b4513);
const HOUSE1: Sprite<Option<Color>, 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<Option<Color>, 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);
}
}

View File

@@ -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")
}

View File

@@ -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<const X: usize, const Y: usize, const NX: usize, const NY: usize> {
row: usize,
col: usize,
y: usize,
#[derive(Eq, Format, Ord, PartialEq, PartialOrd)]
pub struct Position {
x: usize,
y: usize,
}
impl<const X: usize, const Y: usize, const NX: usize, const NY: usize> TryFrom<Option<Position>>
for BufferPosition<X, Y, NX, NY>
{
type Error = ();
fn try_from(position: Option<Position>) -> Result<Self, Self::Error> {
if let Some(position) = position {
position.try_into()
} else {
Err(())
}
}
}
impl<const X: usize, const Y: usize, const NX: usize, const NY: usize> TryFrom<Position>
for BufferPosition<X, Y, NX, NY>
{
type Error = ();
fn try_from(position: Position) -> Result<Self, Self::Error> {
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<Position> {
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<Option<Position>>
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<Position>, value: u32) {
if let Ok(BufferPosition::<X, Y, NX, NY> { row, col, y, x }) = position.try_into() {
self.buffer[row][col][y][x] = value;
}
}
fn fill_rectangle(&mut self, a: Option<Position>, b: Option<Position>, 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<Position>, 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<Position>, b: Option<Position>, 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<Position>, 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<Position>, b: Option<Position>, 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<Position>, 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 }
}
}

293
src/neo_pixel/screen.rs Normal file
View File

@@ -0,0 +1,293 @@
use cortex_m::asm::delay;
use defmt::warn;
use super::{Color, DisplayCircles, DisplayRectangles, DisplaySprites, Position, Screen, Sprite};
struct BufferPosition<const X: usize, const Y: usize, const NX: usize, const NY: usize> {
row: usize,
col: usize,
y: usize,
x: usize,
}
impl<const X: usize, const Y: usize, const NX: usize, const NY: usize> TryFrom<Option<Position>>
for BufferPosition<X, Y, NX, NY>
{
type Error = ();
fn try_from(position: Option<Position>) -> Result<Self, Self::Error> {
if let Some(position) = position {
position.try_into()
} else {
Err(())
}
}
}
impl<const X: usize, const Y: usize, const NX: usize, const NY: usize> TryFrom<Position>
for BufferPosition<X, Y, NX, NY>
{
type Error = ();
fn try_from(position: Position) -> Result<Self, Self::Error> {
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<Sender, DELAY_RESET, X, Y, NX, NY>
{
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<Position> {
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<Option<Position>, Color>
for CompositeNeoPixelDisplay<Sender, DELAY_RESET, X, Y, NX, NY>
{
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<Position>, color: Color) {
if let Ok(BufferPosition::<X, Y, NX, NY> { 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<Position>, Option<Color>>
for CompositeNeoPixelDisplay<Sender, DELAY_RESET, X, Y, NX, NY>
{
fn fill_rectangle(&mut self, a: Option<Position>, b: Option<Position>, color: Option<Color>) {
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<Position>, b: Option<Position>, color: Option<Color>) {
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<Position>, Option<Color>>
for CompositeNeoPixelDisplay<Sender, DELAY_RESET, X, Y, NX, NY>
{
fn fill_circle(&mut self, origin: Option<Position>, radius: usize, color: Option<Color>) {
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<Position>, radius: usize, color: Option<Color>) {
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<Position>, Option<Color>>
for CompositeNeoPixelDisplay<Sender, DELAY_RESET, X, Y, NX, NY>
{
fn copy<const SW: usize, const SH: usize>(
&mut self,
origin: Option<Position>,
sprite: &Sprite<Option<Color>, 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);
}
}
}
}
}
}