stars, moon, trees, houses and snow
* Version of Family Christmas 2024
This commit is contained in:
@@ -35,6 +35,7 @@ panic-probe = { version = "0.3", features = ["print-defmt"] }
|
|||||||
|
|
||||||
embedded-hal = "1.0.0"
|
embedded-hal = "1.0.0"
|
||||||
embedded-alloc = "0.6.0"
|
embedded-alloc = "0.6.0"
|
||||||
|
rand = { version = "0.9.0-beta.1", features = ["small_rng"], default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
defmt-test = "0.3"
|
defmt-test = "0.3"
|
||||||
|
|||||||
2
build.rs
2
build.rs
@@ -40,4 +40,4 @@ fn main() {
|
|||||||
|
|
||||||
// Set the linker script to the one provided by cortex-m-rt.
|
// Set the linker script to the one provided by cortex-m-rt.
|
||||||
println!("cargo:rustc-link-arg=-Tlink.x");
|
println!("cargo:rustc-link-arg=-Tlink.x");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use template_microbit as _; // global logger + panicking-behavior + memory layout
|
|
||||||
use defmt::Format; // <- derive attribute
|
use defmt::Format; // <- derive attribute
|
||||||
|
use template_microbit as _; // global logger + panicking-behavior + memory layout
|
||||||
|
|
||||||
#[derive(Format)]
|
#[derive(Format)]
|
||||||
struct S1<T> {
|
struct S1<T> {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ fn main() -> ! {
|
|||||||
board.display_pins.col1.into_pulldown_input();
|
board.display_pins.col1.into_pulldown_input();
|
||||||
board.display_pins.row1.into_push_pull_output(High);
|
board.display_pins.row1.into_push_pull_output(High);
|
||||||
|
|
||||||
defmt::println!("Hello, world! freq ", );
|
defmt::println!("Hello, world! freq ",);
|
||||||
|
|
||||||
template_microbit::exit()
|
template_microbit::exit()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use super::Sender;
|
use super::Sender;
|
||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct Msb2LsbSender<
|
pub struct Msb2LsbSender<
|
||||||
const BITS: u32,
|
const BITS: u32,
|
||||||
const SHIFT: u32,
|
const SHIFT: u32,
|
||||||
|
|||||||
21
src/led_display/draw.rs
Normal file
21
src/led_display/draw.rs
Normal 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>,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,22 +1,13 @@
|
|||||||
pub trait PixelDisplay<Position> {
|
mod draw;
|
||||||
|
|
||||||
|
pub use draw::*;
|
||||||
|
|
||||||
|
pub trait Screen<Position, Color> {
|
||||||
fn show(&self);
|
fn show(&self);
|
||||||
|
|
||||||
fn clear(&mut self);
|
fn clear(&mut self);
|
||||||
|
|
||||||
fn fill(&mut self, value: u32);
|
fn fill(&mut self, color: Color);
|
||||||
|
|
||||||
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 point(&mut self, position: Position, color: Color);
|
||||||
}
|
}
|
||||||
|
|||||||
372
src/lib.rs
372
src/lib.rs
@@ -1,52 +1,322 @@
|
|||||||
// #![no_main]
|
//#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
//
|
|
||||||
// use cortex_m_semihosting::debug;
|
pub mod bit_string;
|
||||||
//
|
pub mod led_display;
|
||||||
// use defmt_rtt as _; // global logger
|
pub mod neo_pixel;
|
||||||
//
|
|
||||||
// // use some_hal as _; // memory layout
|
use crate::led_display::{DisplaySprites, Screen, Sprite};
|
||||||
// use microbit as _;
|
use crate::neo_pixel::{Color, CompositeNeoPixelDisplay, DisplayCircles, DisplayRectangles};
|
||||||
//
|
use cortex_m::asm::delay;
|
||||||
// use panic_probe as _;
|
use rand::rngs::SmallRng;
|
||||||
//
|
use rand::{RngCore, SeedableRng};
|
||||||
// // 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
|
mod hardware {
|
||||||
// #[defmt::panic_handler]
|
use super::neo_pixel::{CompositeNeoPixelDisplay, NeoPixelSender};
|
||||||
// fn panic() -> ! {
|
use cortex_m::asm::delay;
|
||||||
// cortex_m::asm::udf()
|
use microbit::hal::gpio::Level::Low;
|
||||||
// }
|
use microbit::Board;
|
||||||
//
|
|
||||||
// /// Terminates the application and makes a semihosting-capable debug tool exit
|
// Bit delay, starting always from set HIGH, wait, go to LOW, wait, repeat
|
||||||
// /// with status code 0.
|
const T0H_NS: u32 = 250; // 350+/-150 [ns]
|
||||||
// pub fn exit() -> ! {
|
const T1H_NS: u32 = 900; // 700+/-150 [ns]
|
||||||
// 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]
|
||||||
// }
|
|
||||||
//
|
const T0L_NS: u32 = 900; // 800+/-150 [ns]
|
||||||
// /// Hardfault handler.
|
const T1L_NS: u32 = 600; // 600+/-150 [ns]
|
||||||
// ///
|
|
||||||
// /// Terminates the application and makes a semihosting-capable debug tool exit
|
// Rounded down, from 20.8333
|
||||||
// /// with an error. This seems better than the default, which is to spin in a
|
pub const NS_PER_CYCLE: u32 = 114; // empirically measured to be about 114ns
|
||||||
// /// loop.
|
|
||||||
// #[cortex_m_rt::exception]
|
const T0H: u32 = T0H_NS / NS_PER_CYCLE;
|
||||||
// unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
|
const T0L: u32 = T0L_NS / NS_PER_CYCLE;
|
||||||
// loop {
|
const T1H: u32 = T1H_NS / NS_PER_CYCLE;
|
||||||
// debug::exit(debug::EXIT_FAILURE);
|
const T1L: u32 = T1L_NS / NS_PER_CYCLE;
|
||||||
// }
|
const TLL: u32 = TLL_NS / NS_PER_CYCLE;
|
||||||
// }
|
|
||||||
//
|
// TODO RETRIEVE THIS FROM PAC
|
||||||
// // defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
|
const EDGE_PAD0_MASK: u32 = 1 << 3;
|
||||||
// // once within a crate. the module can be in any file but there can only be at most
|
const EDGE_PAD0_SET_ADDRESS: u32 = 0x5000_0508; // Address of the SET register
|
||||||
// // one `#[tests]` module in this library crate
|
const EDGE_PAD0_CLEAR_ADDRESS: u32 = 0x5000_050c; // Address of the toggle register
|
||||||
// #[cfg(test)]
|
|
||||||
// #[defmt_test::tests]
|
pub fn setup() -> CompositeNeoPixelDisplay<
|
||||||
// mod unit_tests {
|
NeoPixelSender<
|
||||||
// use defmt::assert;
|
EDGE_PAD0_MASK,
|
||||||
//
|
EDGE_PAD0_SET_ADDRESS,
|
||||||
// #[test]
|
EDGE_PAD0_CLEAR_ADDRESS,
|
||||||
// fn it_works() {
|
T0H,
|
||||||
// assert!(true)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
129
src/main.rs
129
src/main.rs
@@ -1,93 +1,60 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use crate::neo_pixel::CompositeNeoPixelDisplay;
|
//use cortex_m_semihosting::debug;
|
||||||
use cortex_m::asm;
|
|
||||||
use cortex_m::asm::delay;
|
use defmt_rtt as _; // global logger
|
||||||
use defmt_rtt as _;
|
|
||||||
use led_display::PixelDisplay;
|
// use some_hal as _; // memory layout
|
||||||
use microbit::hal::gpio::Level::Low;
|
use microbit as _;
|
||||||
use microbit::Board;
|
|
||||||
use panic_probe as _;
|
use panic_probe as _;
|
||||||
|
use template_microbit as _;
|
||||||
|
|
||||||
mod bit_string;
|
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||||
mod led_display;
|
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
||||||
mod neo_pixel;
|
// #[defmt::panic_handler]
|
||||||
|
// fn panic() -> ! {
|
||||||
|
// cortex_m::asm::udf()
|
||||||
|
// }
|
||||||
|
|
||||||
// Bit delay, starting always from set HIGH, wait, go to LOW, wait, repeat
|
/// Terminates the application and makes a semihosting-capable debug tool exit
|
||||||
const T0H_NS: u32 = 250; // 350+/-150 [ns]
|
/// with status code 0.
|
||||||
const T1H_NS: u32 = 900; // 700+/-150 [ns]
|
// pub fn exit() -> ! {
|
||||||
|
// loop {
|
||||||
|
// debug::exit(debug::EXIT_SUCCESS);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Latch reset delay
|
/// Hardfault handler.
|
||||||
const TLL_NS: u32 = 250_000; // 6_000 [ns] min, newer pixels require ~250_000 [ns]
|
///
|
||||||
|
/// 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]
|
// // defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
|
||||||
const T1L_NS: u32 = 600; // 600+/-150 [ns]
|
// // 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
|
||||||
// Rounded down, from 20.8333
|
// #[cfg(test)]
|
||||||
const NS_PER_CYCLE: u32 = 114; // empirically measured to be about 114ns
|
// #[defmt_test::tests]
|
||||||
|
// mod unit_tests {
|
||||||
const T0H: u32 = T0H_NS / NS_PER_CYCLE;
|
// use defmt::assert;
|
||||||
const T0L: u32 = T0L_NS / NS_PER_CYCLE;
|
//
|
||||||
const T1H: u32 = T1H_NS / NS_PER_CYCLE;
|
// #[test]
|
||||||
const T1L: u32 = T1L_NS / NS_PER_CYCLE;
|
// fn it_works() {
|
||||||
const TLL: u32 = TLL_NS / NS_PER_CYCLE;
|
// assert!(true)
|
||||||
|
// }
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cortex_m_rt::entry]
|
#[cortex_m_rt::entry]
|
||||||
fn main() -> ! {
|
fn start() -> ! {
|
||||||
const SPACING: usize = 32;
|
template_microbit::main();
|
||||||
let mut display = setup();
|
|
||||||
let mut frame = 0;
|
|
||||||
|
|
||||||
loop {
|
defmt::panic!("main should not return")
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
use crate::bit_string;
|
mod screen;
|
||||||
use crate::bit_string::Sender;
|
|
||||||
use crate::led_display::PixelDisplay;
|
|
||||||
use cortex_m::asm;
|
|
||||||
use defmt::{warn, Format};
|
|
||||||
|
|
||||||
#[derive(Eq, Format, Ord, PartialEq, PartialOrd)]
|
pub use crate::bit_string::{self, Sender};
|
||||||
pub struct Position {
|
pub use crate::led_display::{DisplayCircles, DisplayRectangles, DisplaySprites, Screen, Sprite};
|
||||||
x: usize,
|
use defmt::Format;
|
||||||
y: usize,
|
pub use screen::CompositeNeoPixelDisplay;
|
||||||
}
|
|
||||||
|
|
||||||
impl Position {
|
pub type Color = u32;
|
||||||
fn new(x: usize, y: usize) -> Self {
|
|
||||||
Self { x, y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type NeoPixelSender<
|
pub type NeoPixelSender<
|
||||||
const PIN_MASK: u32,
|
const PIN_MASK: u32,
|
||||||
@@ -36,323 +27,14 @@ pub type NeoPixelSender<
|
|||||||
DELAY_ONE_LOW,
|
DELAY_ONE_LOW,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
struct BufferPosition<const X: usize, const Y: usize, const NX: usize, const NY: usize> {
|
#[derive(Eq, Format, Ord, PartialEq, PartialOrd)]
|
||||||
row: usize,
|
pub struct Position {
|
||||||
col: usize,
|
|
||||||
y: usize,
|
|
||||||
x: usize,
|
x: usize,
|
||||||
|
y: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const X: usize, const Y: usize, const NX: usize, const NY: usize> TryFrom<Option<Position>>
|
impl Position {
|
||||||
for BufferPosition<X, Y, NX, NY>
|
fn new(x: usize, y: usize) -> Self {
|
||||||
{
|
Self { x, y }
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
293
src/neo_pixel/screen.rs
Normal file
293
src/neo_pixel/screen.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user