diff --git a/src/bit_string/mod.rs b/src/bit_string/mod.rs index 9f494cf..b254b7d 100644 --- a/src/bit_string/mod.rs +++ b/src/bit_string/mod.rs @@ -1,7 +1,7 @@ mod thumbv6m; -pub trait Sender { - fn send(&self, value: u32); +pub trait SendBits { + fn send_bits(&self, value: u32); } pub use thumbv6m::Msb2LsbSender; diff --git a/src/bit_string/thumbv6m.rs b/src/bit_string/thumbv6m.rs index aecdbdb..0af2d3d 100644 --- a/src/bit_string/thumbv6m.rs +++ b/src/bit_string/thumbv6m.rs @@ -1,4 +1,4 @@ -use super::Sender; +use super::SendBits; use core::arch::asm; #[derive(Default)] @@ -52,7 +52,7 @@ impl< const T0B: u32, const T1A: u32, const T1B: u32, - > Sender + > SendBits for Msb2LsbSender< BITS, SHIFT, @@ -72,7 +72,7 @@ impl< // T0A, T0B, T1A, T1B are the number of cycles to wait after writing to A START, and B STOP address. // At a minimum there is always a 3cycle-delay, and due to rounding there might be a +/- 1 cycle // difference for non-multiple of 3. - fn send(&self, value: u32) { + fn send_bits(&self, value: u32) { unsafe { let mut primask: u32; asm!( diff --git a/src/led_display/draw.rs b/src/led_display/draw.rs index 2c92245..3070e47 100644 --- a/src/led_display/draw.rs +++ b/src/led_display/draw.rs @@ -1,19 +1,19 @@ -pub trait DisplayRectangles { - fn fill_rectangle(&mut self, a: Position, b: Position, color: Color); +pub trait DrawRectangles { + fn draw_filled_rectangle(&mut self, a: Position, b: Position, color: Color); - fn rectangle(&mut self, bottom_left: Position, upper_right: Position, color: Color); + fn draw_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); +pub trait DrawCircles { + fn draw_filled_circle(&mut self, origin: Position, radius: usize, color: Color); - fn circle(&mut self, origin: Position, radius: usize, color: Color); + fn draw_circle(&mut self, origin: Position, radius: usize, color: Color); } pub type Sprite = [[Color; X]; Y]; -pub trait DisplaySprites { - fn copy( +pub trait DrawSprites { + fn draw_sprite( &mut self, origin: Position, data: &Sprite, diff --git a/src/led_display/mod.rs b/src/led_display/mod.rs index dc9aa0d..af2a6ab 100644 --- a/src/led_display/mod.rs +++ b/src/led_display/mod.rs @@ -2,12 +2,12 @@ mod draw; pub use draw::*; -pub trait Screen { +pub trait Display { fn show(&self); fn clear(&mut self); fn fill(&mut self, color: Color); - fn point(&mut self, position: Position, color: Color); + fn draw_dot(&mut self, position: Position, color: Color); } diff --git a/src/lib.rs b/src/lib.rs index dc314fa..2ed6c31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,14 +5,14 @@ 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 crate::led_display::{Display, DrawSprites, Sprite}; +use crate::neo_pixel::{Color, DrawCircles, DrawRectangles, NeoPixelScreen}; use cortex_m::asm::delay; use rand::rngs::SmallRng; use rand::{RngCore, SeedableRng}; mod hardware { - use super::neo_pixel::{CompositeNeoPixelDisplay, NeoPixelSender}; + use super::neo_pixel::{NeoPixelScreen, NeoPixelSender}; use cortex_m::asm::delay; use microbit::hal::gpio::Level::Low; use microbit::Board; @@ -21,13 +21,12 @@ mod hardware { 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 + // Latch reset delay + const TLL_NS: u32 = 250_000; // 6_000 [ns] min, newer pixels require ~250_000 [ns] + pub const NS_PER_CYCLE: u32 = 114; // empirically measured to be about 114ns const T0H: u32 = T0H_NS / NS_PER_CYCLE; @@ -41,7 +40,7 @@ mod hardware { 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< + pub fn setup() -> NeoPixelScreen< NeoPixelSender< EDGE_PAD0_MASK, EDGE_PAD0_SET_ADDRESS, @@ -71,26 +70,47 @@ mod hardware { T1L, >::new(); - CompositeNeoPixelDisplay::<_, TLL, 8, 8, 4 /*NX*/, 1 /*NY*/>::new(sender) + NeoPixelScreen::<_, TLL, 8, 8, 4 /*NX*/, 1 /*NY*/>::new(sender) } } -// const BLANK: Option = None; +trait Paint { + fn dx(frame: i32, fps: i32, speed: i32) -> i32; + + fn dy(frame: i32, fps: i32, speed: i32) -> i32; + + fn paint_with_color( + &mut self, + x0: usize, + y0: usize, + sprite: &[[bool; SPRITE_Y]; SPRITE_X], + color: u32, + ); + + fn paint_list( + &mut self, + list: &[Option<(u8, u8, Color)>; S], + dx: u8, + dy: u8, + tree: &[[bool; ELEM_X]; ELEM_Y], + ); +} + impl< - Sender: neo_pixel::Sender, + Sender: neo_pixel::SendBits, const DELAY_RESET: u32, const X: usize, const Y: usize, const NX: usize, const NY: usize, - > CompositeNeoPixelDisplay + > Paint for NeoPixelScreen { fn dx(frame: i32, fps: i32, speed: i32) -> i32 { - ((speed * frame / fps) / 512) % 32 + ((speed * frame / fps) / 512) % { (X * NX) as i32 } } fn dy(frame: i32, fps: i32, speed: i32) -> i32 { - ((speed * frame / fps) / 512) % 8 + ((speed * frame / fps) / 512) % { (Y * NY) as i32 } } fn paint_with_color( @@ -103,7 +123,10 @@ impl< 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); + self.draw_dot( + self.position(((x0 + x) % { X * NX }) as u8, ((y0 + y) % Y) as u8), + color, + ); } } } @@ -120,7 +143,17 @@ impl< self.paint_with_color((*x0 + dx) as usize, (*y0 + dy) as usize, tree, *color); } } +} +impl< + Sender: neo_pixel::SendBits, + const DELAY_RESET: u32, + const X: usize, + const Y: usize, + const NX: usize, + const NY: usize, + > NeoPixelScreen +{ 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. @@ -149,24 +182,21 @@ impl< 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]); + self.draw_dot(self.position(*x, *y), STAR_COLOR[i]); // Screens 3 & 4 - self.point( - self.position((*x + 16) as usize, *y as usize), - STAR_COLOR[i + NB_STARS], - ); + self.draw_dot(self.position(*x + 16, *y), 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 x = Self::dx(frame, fps, 256) as u8; + let dy = Self::dy(frame, fps, 128) as u8; 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); + let origin = self.position(x, y); + self.draw_filled_circle(origin, 2, COLOR); } fn stars_close(&mut self, frame: i32, fps: i32) { @@ -191,14 +221,14 @@ impl< ]; for (i, (x, y)) in STAR_POSITIONS.iter().enumerate() { - let x = ((*x as i32 + Self::dx(frame, fps, 64)) % 32) as usize; + let x = ((*x as i32 + Self::dx(frame, fps, 64)) % { (X * NX) as i32 }) 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)); + self.draw_filled_rectangle(self.position(0, 0), self.position(31, 2), Some(0x0a0a0a)); } fn trees(&mut self, frame: i32, fps: i32) { @@ -244,13 +274,16 @@ impl< } fn houses(&mut self, frame: i32, fps: i32) { + let max_x: i32 = { (X * NX) as 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 BROWN_: Option = Some(0x8b4513); + const HOUSE1: Sprite, 5, 5> = [ [BLANK_, BLANK_, RED___, BLANK_, BLANK_], [PURPLE, PURPLE, PURPLE, RED___, BLANK_], @@ -267,30 +300,27 @@ impl< [BLANK_, BLANK_, RED___, BLANK_, BLANK_], ]; - self.copy( - self.position((3 + Self::dx(frame, fps, 1024)) as usize % 32, 0), + self.draw_sprite( + self.position(((3 + Self::dx(frame, fps, 1024)) % max_x) as u8, 0), &HOUSE2, ); - self.copy( - self.position((7 + Self::dx(frame, fps, 1024)) as usize % 32, 2), + self.draw_sprite( + self.position(((7 + Self::dx(frame, fps, 1024)) % max_x) as u8, 2), &HOUSE1, ); - self.copy( - self.position((14 + Self::dx(frame, fps, 1024)) as usize % 32, 1), + self.draw_sprite( + self.position(((14 + Self::dx(frame, fps, 1024)) % max_x) as u8, 1), &HOUSE2, ); - self.copy( - self.position((20 + Self::dx(frame, fps, 1024)) as usize % 32, 0), + self.draw_sprite( + self.position(((20 + Self::dx(frame, fps, 1024)) % max_x) as u8, 0), &HOUSE2, ); - self.copy( - self.position((27 + Self::dx(frame, fps, 1024)) as usize % 32, 1), + self.draw_sprite( + self.position(((27 + Self::dx(frame, fps, 1024)) % max_x) as u8, 1), &HOUSE2, ); } - fn sled(&self, _frame: usize) { - todo!() - } } pub fn main() { @@ -304,16 +334,13 @@ pub fn main() { 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.stars_far(frame, FRAME_PER_S, &mut rng); + display.stars_close(frame, FRAME_PER_S); + display.moon(frame, FRAME_PER_S); + display.snow(); - display.trees(frame, FRAME_PER_S); // OK + display.trees(frame, FRAME_PER_S); display.houses(frame, FRAME_PER_S); - // - // display.sled(frame); display.show(); frame += 1; diff --git a/src/neo_pixel/mod.rs b/src/neo_pixel/mod.rs index 236044a..304be62 100644 --- a/src/neo_pixel/mod.rs +++ b/src/neo_pixel/mod.rs @@ -1,9 +1,9 @@ mod screen; -pub use crate::bit_string::{self, Sender}; -pub use crate::led_display::{DisplayCircles, DisplayRectangles, DisplaySprites, Screen, Sprite}; +pub use crate::bit_string::{self, SendBits}; +pub use crate::led_display::{Display, DrawCircles, DrawRectangles, DrawSprites, Sprite}; use defmt::Format; -pub use screen::CompositeNeoPixelDisplay; +pub use screen::CompositeNeoPixelScreen as NeoPixelScreen; pub type Color = u32; @@ -29,12 +29,12 @@ pub type NeoPixelSender< #[derive(Eq, Format, Ord, PartialEq, PartialOrd)] pub struct Position { - x: usize, - y: usize, + x: u8, + y: u8, } impl Position { - fn new(x: usize, y: usize) -> Self { + pub(crate) fn new(x: u8, y: u8) -> Self { Self { x, y } } } diff --git a/src/neo_pixel/screen.rs b/src/neo_pixel/screen.rs index 726c126..eb19dfa 100644 --- a/src/neo_pixel/screen.rs +++ b/src/neo_pixel/screen.rs @@ -1,7 +1,7 @@ use cortex_m::asm::delay; use defmt::warn; -use super::{Color, DisplayCircles, DisplayRectangles, DisplaySprites, Position, Screen, Sprite}; +use super::{Color, Display, DrawCircles, DrawRectangles, DrawSprites, Position, Sprite}; struct BufferPosition { row: usize, @@ -30,27 +30,24 @@ impl TryFrom

Result { - let col = position.x / X; - let row = position.y / Y; + let px = position.x as usize; + let py = position.y as usize; - if col > NX && row > NY { - return Err(()); - } - if col > NX { - return Err(()); - } - if row > NY { - return Err(()); - } + let col = px / X; + let row = py / Y; + let x = px % X; + let y = py % Y; - let x = position.x % X; - let y = position.y % Y; - Ok(BufferPosition { row, col, y, x }) + if col > NX || row > NY { + Err(()) + } else { + Ok(BufferPosition { row, col, y, x }) + } } } -pub struct CompositeNeoPixelDisplay< - Sender: super::Sender, +pub struct CompositeNeoPixelScreen< + Sender: super::SendBits, const DELAY_RESET: u32, const X: usize, const Y: usize, @@ -62,13 +59,13 @@ pub struct CompositeNeoPixelDisplay< } impl< - Sender: super::Sender, + Sender: super::SendBits, const DELAY_RESET: u32, const X: usize, const Y: usize, const NX: usize, const NY: usize, - > CompositeNeoPixelDisplay + > CompositeNeoPixelScreen { pub fn new(sender: Sender) -> Self { let display = Self { @@ -80,8 +77,8 @@ impl< display } - pub fn position(&self, x: usize, y: usize) -> Option { - if (x < { X * NX }) && (y < { Y * NY }) { + pub fn position(&self, x: u8, y: u8) -> Option { + if ((x as usize) < { X * NX }) && ((y as usize) < { Y * NY }) { Some(Position::new(x, y)) } else { warn!( @@ -94,24 +91,54 @@ impl< None } } + + fn get_rectangle_parameters( + a: Option, + b: Option, + color: Option, + ) -> Option<(Position, Position, Color)> { + let color = color?; + let a = a?; + let b = b?; + + if b > a { + Some((a, b, color)) + } else { + Some((b, a, color)) + } + } + + fn get_circle_parameters( + origin: Option, + radius: u8, + color: Option, + ) -> Option<(u8, u8, u8, u8, Color)> { + let color = color?; + 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 }; + + Some((cx, cy, x0, y0, color)) + } } impl< - Sender: super::Sender, + Sender: super::SendBits, const DELAY_RESET: u32, const X: usize, const Y: usize, const NX: usize, const NY: usize, - > Screen, Color> - for CompositeNeoPixelDisplay + > Display, Color> + for CompositeNeoPixelScreen { 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]); + self.sender.send_bits(self.buffer[r][c][y][x]); } } } @@ -124,14 +151,14 @@ impl< } fn fill(&mut self, color: Color) { - self.fill_rectangle( + self.draw_filled_rectangle( self.position(0, 0), - self.position(X * NX - 1, Y * NY - 1), + self.position((X * NX - 1) as u8, (Y * NY - 1) as u8), Some(color), ); } - fn point(&mut self, position: Option, color: Color) { + fn draw_dot(&mut self, position: Option, color: Color) { if let Ok(BufferPosition:: { row, col, y, x }) = position.try_into() { self.buffer[row][col][y][x] = color; } @@ -139,67 +166,104 @@ impl< } impl< - Sender: super::Sender, + Sender: super::SendBits, const DELAY_RESET: u32, const X: usize, const Y: usize, const NX: usize, const NY: usize, - > DisplayRectangles, Option> - for CompositeNeoPixelDisplay + > DrawRectangles, Option> + for CompositeNeoPixelScreen { - fn fill_rectangle(&mut self, a: Option, b: Option, color: Option) { - let color = if let Some(c) = color { c } else { return }; + fn draw_filled_rectangle( + &mut self, + a: Option, + b: Option, + color: Option, + ) { + if let Some((bottom_left, upper_right, color)) = Self::get_rectangle_parameters(a, b, color) + { + let Position { x: x0, y: y0 } = bottom_left; + let Position { x: x1, y: y1 } = upper_right; - 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; + for y in y0..=y1 { + for x in x0..=x1 { + self.draw_dot(self.position(x, y), color) } - 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 draw_rectangle(&mut self, a: Option, b: Option, color: Option) { + if let Some((bottom_left, upper_right, color)) = Self::get_rectangle_parameters(a, b, color) + { + 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.draw_dot(self.position(x, y), color); + } + } + } + } +} + +impl< + Sender: super::SendBits, + const DELAY_RESET: u32, + const X: usize, + const Y: usize, + const NX: usize, + const NY: usize, + > DrawCircles, Option> + for CompositeNeoPixelScreen +{ + fn draw_filled_circle( + &mut self, + origin: Option, + radius: usize, + color: Option, + ) { + let radius = radius as u8; + + if let Some((cx, cy, x0, y0, color)) = Self::get_circle_parameters(origin, radius, color) { + 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.draw_dot(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; + fn draw_circle(&mut self, origin: Option, radius: usize, color: Option) { + let radius = radius as u8; - 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); - } + if let Some((cx, cy, x0, y0, color)) = Self::get_circle_parameters(origin, radius, color) { + 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.draw_dot(self.position(x, y), color); } } } @@ -208,74 +272,16 @@ impl< } impl< - Sender: super::Sender, + Sender: super::SendBits, const DELAY_RESET: u32, const X: usize, const Y: usize, const NX: usize, const NY: usize, - > DisplayCircles, Option> - for CompositeNeoPixelDisplay + > DrawSprites, Option> + for CompositeNeoPixelScreen { - 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( + fn draw_sprite( &mut self, origin: Option, sprite: &Sprite, SW, SH>, @@ -284,7 +290,7 @@ impl< 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); + self.draw_dot(self.position(x0 + x as u8, y0 + y as u8), color); } } }