From b7f919cdc65637d4effb3faeb93d2d70bbba1199 Mon Sep 17 00:00:00 2001 From: Stephen D <webmaster@scd31.com> Date: Sat, 23 Mar 2024 12:30:16 -0300 Subject: [PATCH] buffered display that uses way too much RAM --- src/buffered_display.rs | 142 ++++++++++++++++++++++++++++++++++++++++ src/keyboard.rs | 10 ++- src/main.rs | 129 +++++++++++++++++------------------- 3 files changed, 211 insertions(+), 70 deletions(-) create mode 100644 src/buffered_display.rs diff --git a/src/buffered_display.rs b/src/buffered_display.rs new file mode 100644 index 0000000..51a629f --- /dev/null +++ b/src/buffered_display.rs @@ -0,0 +1,142 @@ +// Uses too much RAM to be useful ): +// 320 * 240 * 2 * 2 bytes/pixel = 307KB of RAM + +use core::convert::Infallible; + +use embedded_graphics::{ + draw_target::DrawTarget, + geometry::{Dimensions, Point}, + pixelcolor::PixelColor, + Pixel, +}; + +pub struct BufferedDisplay<const L: usize, C: 'static, E, DT: DrawTarget<Color = C, Error = E>> { + target: DT, + width: usize, + height: usize, + last: &'static mut [C; L], + cur: &'static mut [C; L], +} + +impl<const L: usize, C: PixelColor, E, DT: DrawTarget<Color = C, Error = E>> + BufferedDisplay<L, C, E, DT> +{ + pub fn new( + mut target: DT, + color: C, + last: &'static mut [C; L], + cur: &'static mut [C; L], + ) -> Result<Self, E> { + let size = target.bounding_box().size; + let width = size.width.try_into().unwrap(); + let height = size.height.try_into().unwrap(); + assert_eq!(L, width * height); + target.fill_solid(&target.bounding_box(), color)?; + + Ok(Self { + target, + width, + height, + last, + cur, + }) + } + + pub fn flush(&mut self) -> Result<(), E> { + let iter = BufferedDisplayIter::new(self.width, self.height, self.last, self.cur); + self.target.draw_iter(iter)?; + + Ok(()) + } + + #[inline(always)] + fn index<X: TryInto<usize>, Y: TryInto<usize>>(&self, x: X, y: Y) -> Option<usize> { + let x: usize = x.try_into().ok()?; + let y: usize = y.try_into().ok()?; + + if y >= self.height || x >= self.width { + return None; + } + + Some(x + y * self.width) + } +} + +impl<const L: usize, C: PixelColor, E, DT: DrawTarget<Color = C, Error = E>> Dimensions + for BufferedDisplay<L, C, E, DT> +{ + fn bounding_box(&self) -> embedded_graphics::primitives::Rectangle { + self.target.bounding_box() + } +} + +impl<const L: usize, C: PixelColor, E, DT: DrawTarget<Color = C, Error = E>> DrawTarget + for BufferedDisplay<L, C, E, DT> +{ + type Color = C; + type Error = Infallible; + + fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator<Item = embedded_graphics::prelude::Pixel<Self::Color>>, + { + for Pixel(p, c) in pixels { + if let Some(idx) = self.index(p.x, p.y) { + self.cur[idx] = c; + } + } + + Ok(()) + } +} + +struct BufferedDisplayIter<'a, C: PixelColor> { + width: usize, + height: usize, + last: &'a mut [C], + cur: &'a mut [C], + i: usize, +} + +impl<'a, C: PixelColor> BufferedDisplayIter<'a, C> { + fn new(width: usize, height: usize, last: &'a mut [C], cur: &'a mut [C]) -> Self { + Self { + width, + height, + last, + cur, + i: 0, + } + } + + #[inline(always)] + fn get_xy(&self, idx: usize) -> (i32, i32) { + let y = (idx / self.width).try_into().unwrap(); + let x = (idx % self.width).try_into().unwrap(); + + (x, y) + } +} + +impl<'a, C: PixelColor> Iterator for BufferedDisplayIter<'a, C> { + type Item = Pixel<C>; + + fn next(&mut self) -> Option<Self::Item> { + loop { + if self.i >= self.cur.len() { + return None; + } + + let c = self.cur[self.i]; + if self.last[self.i] != c { + self.last[self.i] = c; + + let (x, y) = self.get_xy(self.i); + + return Some(Pixel(Point::new(x, y), c)); + } + + self.i += 1; + } + } +} diff --git a/src/keyboard.rs b/src/keyboard.rs index 3da7f1a..4070ae1 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -37,7 +37,7 @@ const KEYMAP: [[char; 8]; 6] = [ ], ]; -enum KeyCode { +pub enum KeyCode { CallStart, Menu, Touchpad, @@ -110,6 +110,7 @@ pub struct Keyboard< row_ser: RSER, row_ld: RLD, last_state: u64, + cur_key: Option<KeyCode>, } impl< @@ -142,6 +143,7 @@ impl< row_ld, last_state: 0, + cur_key: None, } } @@ -175,7 +177,7 @@ impl< is_alt_pressed(state), is_shift_pressed(state), ); - rprint!("{}", keycode); + self.cur_key = Some(keycode); } } } @@ -185,6 +187,10 @@ impl< state } + pub fn pushed_key(&mut self) -> Option<KeyCode> { + self.cur_key.take() + } + // 1s in val are low // 0s are high // indexes from furthest pin (QH on second shift register) diff --git a/src/main.rs b/src/main.rs index e4cc6ba..1206eef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![no_std] #![no_main] +mod buffered_display; mod keyboard; mod touchpad; @@ -15,16 +16,18 @@ pub static BOOT_LOADER: [u8; 256] = rp2040_boot2::BOOT_LOADER_GD25Q64CS; dispatchers = [TIMER_IRQ_1, TIMER_IRQ_2] )] mod app { - use crate::{keyboard::Keyboard, touchpad::Touchpad}; - use core::fmt::Write; - use cortex_m::delay::Delay; + use crate::{ + buffered_display::BufferedDisplay, + keyboard::{KeyCode, Keyboard}, + touchpad::Touchpad, + }; + use cortex_m::{delay::Delay, singleton}; use display_interface_spi::SPIInterface; use embedded_graphics::{ draw_target::DrawTarget, mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder}, pixelcolor::{Rgb565, RgbColor}, - prelude::{Point, Primitive}, - primitives::{Circle, PrimitiveStyle, Triangle}, + prelude::Point, text::{Baseline, Text}, Drawable, }; @@ -41,16 +44,18 @@ mod app { }, pac::{I2C1, SPI1}, pwm::{self, Channel, FreeRunning, Pwm4, Pwm5, Slice}, - reset, spi, + spi, timer::{monotonic::Monotonic, Timer}, Clock, I2C, }; use heapless::String; - use mipidsi::{models::ST7789, Builder}; + use mipidsi::{models::ST7789, Builder, Orientation}; use rp2040_hal::{self as hal, timer::Alarm0}; use rtic_monotonics::rp2040::*; const XTAL_FREQ_HZ: u32 = 12_000_000u32; + const WIDTH: usize = 320; + const HEIGHT: usize = 240; type KeyboardBl = Channel<Slice<Pwm4, FreeRunning>, pwm::B>; @@ -99,7 +104,9 @@ mod app { type MyMono = Monotonic<Alarm0>; #[shared] - struct Shared {} + struct Shared { + text: String<1024>, + } #[local] struct Local { @@ -179,6 +186,7 @@ mod app { let mut display_bl = pwm.channel_a; display_bl.output_to(pins.gpio10); display_bl.set_inverted(); + display_bl.set_duty(display_bl.get_max_duty()); let tp_reset = pins.gpio3.into_push_pull_output(); let mut disp_reset = pins.gpio4.into_push_pull_output_in_state(PinState::Low); @@ -236,7 +244,8 @@ mod app { let spi_cs = SPIInterface::new(spi, display_dc, display_cs); let display = Builder::st7789(spi_cs) - .with_display_size(320, 240) + .with_display_size(HEIGHT.try_into().unwrap(), WIDTH.try_into().unwrap()) + .with_orientation(Orientation::LandscapeInverted(true)) .init(&mut delay, None) .unwrap(); @@ -246,7 +255,9 @@ mod app { let mono = Monotonic::new(timer, timer.alarm_0().unwrap()); ( - Shared {}, + Shared { + text: String::new(), + }, Local { keyboard_bl, touchpad, @@ -261,7 +272,7 @@ mod app { ) } - #[task(priority = 2, local = [display_bl, keyboard_bl, touchpad, keyboard])] + #[task(priority = 2, local = [display_bl, keyboard_bl, touchpad, keyboard], shared = [text])] fn keyboard_update(ctx: keyboard_update::Context) { //while usb_dev.poll(&mut [&mut serial]) {} @@ -275,13 +286,30 @@ mod app { let max_duty = keyboard_bl.get_max_duty(); let mut cur_duty = display_bl.get_duty(); - let mut buf: String<64> = String::new(); - let state = keyboard.read_state(); if state > 0 { - write!(&mut buf, "KB: {state:x}\r\n").unwrap(); - //serial.write(&buf.as_bytes()); keyboard_bl.set_duty(max_duty); + + if let Some(k) = keyboard.pushed_key() { + let mut text = ctx.shared.text; + + match k { + KeyCode::Backspace => { + text.lock(|f| f.pop()); + } + KeyCode::Char(c) => { + text.lock(|f| { + let _ = f.push(c); + }); + } + KeyCode::Enter => { + text.lock(|f| { + let _ = f.push_str("\r\n"); + }); + } + _ => {} + } + } } else { keyboard_bl.set_duty(0); } @@ -303,37 +331,34 @@ mod app { keyboard_update::spawn_after(5.millis()).unwrap(); } - #[task(local = [display, r, g, b])] - fn display_update(ctx: display_update::Context) { + #[task(local = [display, r, g, b], shared = [text])] + fn display_update(mut ctx: display_update::Context) { let display_update::LocalResources { display, r, g, b } = ctx.local; - *r += 1; - *g += 3; - *b += 5; - *r %= 255; - *g %= 255; - *b %= 255; - + /* + *r += 1; + *g += 3; + *b += 5; + *r %= 255; + *g %= 255; + *b %= 255; + */ display.clear(Rgb565::new(*r, *g, *b)).unwrap(); - // Draw a smiley face with white eyes and a red mouth - draw_smiley(display).unwrap(); - // text! let text_style = MonoTextStyleBuilder::new() .font(&FONT_10X20) .text_color(Rgb565::YELLOW) .build(); - Text::with_baseline( - "Hello world!", - Point::new(16, 128), - text_style, - Baseline::Top, - ) - .draw(display) - .unwrap(); + ctx.shared.text.lock(|text| { + let _ = text.push('_'); + Text::with_baseline(text, Point::new(8, 8), text_style, Baseline::Top) + .draw(display) + .unwrap(); + text.pop(); + }); - display_update::spawn_after(100.millis()).unwrap(); + display_update::spawn_after(10.millis()).unwrap(); } #[idle] @@ -342,36 +367,4 @@ mod app { cortex_m::asm::wfi(); } } - - fn draw_smiley<T: DrawTarget<Color = Rgb565>>(display: &mut T) -> Result<(), T::Error> { - // Draw the left eye as a circle located at (50, 100), with a diameter of 40, filled with white - Circle::new(Point::new(50, 100), 40) - .into_styled(PrimitiveStyle::with_fill(Rgb565::WHITE)) - .draw(display)?; - - // Draw the right eye as a circle located at (50, 200), with a diameter of 40, filled with white - Circle::new(Point::new(50, 200), 40) - .into_styled(PrimitiveStyle::with_fill(Rgb565::WHITE)) - .draw(display)?; - - // Draw an upside down red triangle to represent a smiling mouth - Triangle::new( - Point::new(130, 140), - Point::new(130, 200), - Point::new(160, 170), - ) - .into_styled(PrimitiveStyle::with_fill(Rgb565::RED)) - .draw(display)?; - - // Cover the top part of the mouth with a black triangle so it looks closed instead of open - Triangle::new( - Point::new(130, 150), - Point::new(130, 190), - Point::new(150, 170), - ) - .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) - .draw(display)?; - - Ok(()) - } } -- GitLab