diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index d8510a0..f24d52b 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -317,7 +317,9 @@ dependencies = [ "defmt-rtt", "embedded-hal 0.2.7", "itertools 0.13.0", + "nb 1.1.0", "panic-probe", + "pio", "ringbuffer", "rp-pico", "rp2040-flash", diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index 27ee0ff..628bb10 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -13,7 +13,6 @@ defmt-rtt = "0.4" panic-probe = { version = "0.3", features = ["print-defmt"] } rp-pico = "0.9" usb-device = "0.3.2" -# usbd-hid = "0.8.2" usbd-human-interface-device = { version = "0.5.0" } usbd-serial = "0.2.2" rp2040-flash = "0.5.1" @@ -22,6 +21,8 @@ smart-leds = "0.4.0" smart-leds-trait = "0.2.1" itertools = { version = "0.13.0", default-features = false } ringbuffer = { version = "0.15.0", default-features = false } +pio = "0.2.1" +nb = "1.1.0" # cargo build/run diff --git a/firmware/src/color.rs b/firmware/src/color.rs new file mode 100644 index 0000000..093a28f --- /dev/null +++ b/firmware/src/color.rs @@ -0,0 +1,33 @@ +pub fn hsv2rgb(hue: f32, sat: f32, val: f32) -> (u8, u8, u8) { + let c = val * sat; + let v = (hue / 60.0) % 2.0 - 1.0; + let v = if v < 0.0 { -v } else { v }; + let x = c * (1.0 - v); + let m = val - c; + let (r, g, b) = if hue < 60.0 { + (c, x, 0.0) + } else if hue < 120.0 { + (x, c, 0.0) + } else if hue < 180.0 { + (0.0, c, x) + } else if hue < 240.0 { + (0.0, x, c) + } else if hue < 300.0 { + (x, 0.0, c) + } else { + (c, 0.0, x) + }; + + let r = ((r + m) * 255.0) as u8; + let g = ((g + m) * 255.0) as u8; + let b = ((b + m) * 255.0) as u8; + + (r, g, b) +} + +pub fn rgb565(r: u8, g: u8, b: u8) -> u16 { + let r = ((r as u16) & 0b11111000) << 8; + let g = ((g as u16) & 0b11111100) << 3; + let b = (b as u16) >> 3; + r | g | b +} diff --git a/firmware/src/main.rs b/firmware/src/main.rs index 8a2342c..5e59849 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -3,9 +3,11 @@ #![no_std] #![no_main] +mod color; mod mutex; mod peripheral; mod pins; +mod st7789; mod uid; mod modules { pub mod keyboard; @@ -35,6 +37,7 @@ use rp_pico::hal::{ Clock, Timer, }; use rp_pico::{entry, Pins}; +use st7789::St7789; use ws2812_pio::Ws2812; use usb_device::{class_prelude::*, prelude::*}; @@ -50,7 +53,7 @@ use usbd_serial::SerialPort; use defmt::*; use defmt_rtt as _; -static mut CORE1_STACK: Stack<2048> = Stack::new(); +static mut CORE1_STACK: Stack<8192> = Stack::new(); // inter-core mutexes static CONNECTED_PERIPHERALS: Mutex<0, JBPeripherals> = Mutex::new(JBPeripherals::default()); @@ -117,84 +120,102 @@ fn main() -> ! { let mut serial_mod = serial::SerialMod::new(timer.count_down()); // core 1 event loop (GPIO) - let _ = core1.spawn(unsafe { &mut CORE1_STACK.mem }, move || { - let mut pac = unsafe { Peripherals::steal() }; - let pins = Pins::new( - pac.IO_BANK0, - pac.PADS_BANK0, - sio.gpio_bank0, - &mut pac.RESETS, - ); + core1 + .spawn(unsafe { &mut CORE1_STACK.mem }, move || { + let mut pac = unsafe { Peripherals::steal() }; + let pins = Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); - // set up GPIO - let (kb_col_pins, kb_row_pins, led_pin, rgb_pin) = pins::configure_gpio(pins); + // set up GPIO + let (kb_col_pins, kb_row_pins, led_pin, rgb_pin, screen_pins) = + pins::configure_gpio(pins); - let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); - let ws = Ws2812::new( - rgb_pin, - &mut pio, - sm0, - clocks.peripheral_clock.freq(), - timer.count_down(), - ); + let (mut pio0, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let ws = Ws2812::new( + rgb_pin, + &mut pio0, + sm0, + clocks.peripheral_clock.freq(), + timer.count_down(), + ); - // set up modules - let mut keyboard_mod = - keyboard::KeyboardMod::new(kb_col_pins, kb_row_pins, timer.count_down()); + let (mut pio1, _, sm1, _, _) = pac.PIO1.split(&mut pac.RESETS); + let mut st = St7789::new( + &mut pio1, + sm1, + screen_pins.0, + screen_pins.1, + screen_pins.2, + screen_pins.3, + screen_pins.4, + screen_pins.5, + timer.count_down(), + ); + st.init(); - let mut led_mod = led::LedMod::new(led_pin, timer.count_down()); - let mut rgb_mod = rgb::RgbMod::new(ws, timer.count_down()); - let mut screen_mod = screen::ScreenMod::new(timer.count_down()); + // set up modules + let mut keyboard_mod = + keyboard::KeyboardMod::new(kb_col_pins, kb_row_pins, timer.count_down()); - loop { - // update input devices - keyboard_mod.update(); + let mut led_mod = led::LedMod::new(led_pin, timer.count_down()); + let mut rgb_mod = rgb::RgbMod::new(ws, timer.count_down()); + let mut screen_mod = screen::ScreenMod::new(st, timer.count_down()); - // update mutexes - CONNECTED_PERIPHERALS.with_mut_lock(|c| { - c.keyboard = Connection::Connected; - }); - PERIPHERAL_INPUTS.with_mut_lock(|i| { - let keys = keyboard_mod.get_pressed_keys(); - i.keyboard.key1 = keys[0].into(); - i.keyboard.key2 = keys[1].into(); - i.keyboard.key3 = keys[2].into(); - i.keyboard.key4 = keys[3].into(); - i.keyboard.key5 = keys[4].into(); - i.keyboard.key6 = keys[5].into(); - i.keyboard.key7 = keys[6].into(); - i.keyboard.key8 = keys[7].into(); - i.keyboard.key9 = keys[8].into(); - i.keyboard.key10 = keys[9].into(); - i.keyboard.key11 = keys[10].into(); - i.keyboard.key12 = keys[11].into(); - }); + loop { + // update input devices + keyboard_mod.update(); - // check if we need to shutdown "cleanly" for update - UPDATE_TRIGGER.with_lock(|u| { - if *u { - led_mod.clear(); - rgb_mod.clear(); - screen_mod.clear(); + // update mutexes + CONNECTED_PERIPHERALS.with_mut_lock(|c| { + c.keyboard = Connection::Connected; + }); + PERIPHERAL_INPUTS.with_mut_lock(|i| { + let keys = keyboard_mod.get_pressed_keys(); + i.keyboard.key1 = keys[0].into(); + i.keyboard.key2 = keys[1].into(); + i.keyboard.key3 = keys[2].into(); + i.keyboard.key4 = keys[3].into(); + i.keyboard.key5 = keys[4].into(); + i.keyboard.key6 = keys[5].into(); + i.keyboard.key7 = keys[6].into(); + i.keyboard.key8 = keys[7].into(); + i.keyboard.key9 = keys[8].into(); + i.keyboard.key10 = keys[9].into(); + i.keyboard.key11 = keys[10].into(); + i.keyboard.key12 = keys[11].into(); + }); - // wait a few cycles for the IO to finish - for _ in 0..100 { - cortex_m::asm::nop(); - } + // check if we need to shutdown "cleanly" for update + UPDATE_TRIGGER.with_lock(|u| { + if *u { + led_mod.clear(); + rgb_mod.clear(); + screen_mod.clear(); - reset_to_usb_boot(0, 0); - } - }); + // wait a few cycles for the IO to finish + for _ in 0..200 { + cortex_m::asm::nop(); + } - // update accessories - led_mod.update(); - rgb_mod.update(timer.get_counter()); - screen_mod.update(); - } - }); + reset_to_usb_boot(0, 0); + } + }); + + // update accessories + led_mod.update(); + rgb_mod.update(timer.get_counter()); + screen_mod.update(timer.get_counter(), &timer); + } + }) + .expect("failed to start core1"); // main event loop (USB comms) loop { + // info!("help 0"); // tick for hid devices if hid_tick.wait().is_ok() { // handle keyboard diff --git a/firmware/src/modules/rgb.rs b/firmware/src/modules/rgb.rs index ad496da..d9240bf 100644 --- a/firmware/src/modules/rgb.rs +++ b/firmware/src/modules/rgb.rs @@ -14,6 +14,8 @@ use smart_leds::brightness; use smart_leds_trait::{SmartLedsWrite, RGB8}; use ws2812_pio::Ws2812; +use crate::color::hsv2rgb; + const RGB_LEN: usize = 12; const FRAME_TIME: u32 = 33; @@ -21,7 +23,7 @@ pub struct RgbMod<'timer> { ws: Ws2812, Pin>, brightness: u8, buffer: [RGB8; RGB_LEN], - frame_timer: CountDown<'timer>, + timer: CountDown<'timer>, } impl<'timer> RgbMod<'timer> { @@ -35,7 +37,7 @@ impl<'timer> RgbMod<'timer> { ws: ws, brightness: 10, buffer: [(0, 0, 0).into(); RGB_LEN], - frame_timer: count_down, + timer: count_down, } } @@ -48,7 +50,7 @@ impl<'timer> RgbMod<'timer> { } pub fn update(&mut self, t: Instant) { - if !self.frame_timer.wait().is_ok() { + if !self.timer.wait().is_ok() { return; } @@ -63,30 +65,3 @@ impl<'timer> RgbMod<'timer> { .unwrap(); } } - -pub fn hsv2rgb(hue: f32, sat: f32, val: f32) -> (u8, u8, u8) { - let c = val * sat; - let v = (hue / 60.0) % 2.0 - 1.0; - let v = if v < 0.0 { -v } else { v }; - let x = c * (1.0 - v); - let m = val - c; - let (r, g, b) = if hue < 60.0 { - (c, x, 0.0) - } else if hue < 120.0 { - (x, c, 0.0) - } else if hue < 180.0 { - (0.0, c, x) - } else if hue < 240.0 { - (0.0, x, c) - } else if hue < 300.0 { - (x, 0.0, c) - } else { - (c, 0.0, x) - }; - - let r = ((r + m) * 255.0) as u8; - let g = ((g + m) * 255.0) as u8; - let b = ((b + m) * 255.0) as u8; - - (r, g, b) -} diff --git a/firmware/src/modules/screen.rs b/firmware/src/modules/screen.rs index eba0438..c00fa10 100644 --- a/firmware/src/modules/screen.rs +++ b/firmware/src/modules/screen.rs @@ -1,22 +1,66 @@ //! Screen for fun graphics +use defmt::info; use embedded_hal::timer::CountDown as _; -use rp_pico::hal::{fugit::ExtU32, timer::CountDown}; +use rp_pico::{ + hal::{ + fugit::ExtU32, + gpio::{DynPinId, FunctionPio1, Pin, PullDown}, + pio::SM1, + timer::{CountDown, Instant}, + Timer, + }, + pac::PIO1, +}; -const REFRESH_RATE: u32 = 33; +use crate::{ + color::{hsv2rgb, rgb565}, + st7789::St7789, +}; + +const REFRESH_RATE: u32 = 50; pub struct ScreenMod<'timer> { + st: St7789<'timer, PIO1, SM1, Pin>, timer: CountDown<'timer>, } impl<'timer> ScreenMod<'timer> { - pub fn new(mut count_down: CountDown<'timer>) -> Self { + pub fn new( + st: St7789<'timer, PIO1, SM1, Pin>, + mut count_down: CountDown<'timer>, + ) -> Self { count_down.start(REFRESH_RATE.millis()); - ScreenMod { timer: count_down } + ScreenMod { + st: st, + timer: count_down, + } + } + + pub fn clear(&mut self) { + self.st.clear_framebuffer(); + self.st.push_framebuffer(); + self.st.backlight_off(); } - pub fn clear(&mut self) {} + pub fn update(&mut self, t: Instant, timer: &Timer) { + if !self.timer.wait().is_ok() { + return; + } + + let t = ((t.duration_since_epoch().ticks() >> 15) % 360) as f32; + let rgb = hsv2rgb(t, 1.0, 1.0); + let rgb = rgb565(rgb.0, rgb.1, rgb.2); - pub fn update(&mut self) {} + // let time_start = timer.get_counter(); + self.st.fill_framebuffer(rgb); + // let elapse1 = (timer.get_counter() - time_start).to_micros(); + + // let time_start = timer.get_counter(); + self.st.push_framebuffer(); + // let elapse2 = (timer.get_counter() - time_start).to_micros(); + + // info!("times: fill-fb={}us, write-fb={}us", elapse1, elapse2); + } } diff --git a/firmware/src/modules/serial.rs b/firmware/src/modules/serial.rs index 53bb408..45442f3 100644 --- a/firmware/src/modules/serial.rs +++ b/firmware/src/modules/serial.rs @@ -1,7 +1,6 @@ //! Serial processing module -use defmt::info; -use defmt::warn; +use defmt::{info, warn}; use embedded_hal::timer::Cancel as _; use embedded_hal::timer::CountDown as _; use itertools::Itertools; diff --git a/firmware/src/pins.rs b/firmware/src/pins.rs index c1daf09..1b865b4 100644 --- a/firmware/src/pins.rs +++ b/firmware/src/pins.rs @@ -1,5 +1,7 @@ use rp_pico::{ - hal::gpio::{DynPinId, FunctionPio0, FunctionSioInput, FunctionSioOutput, Pin, PullDown}, + hal::gpio::{ + DynPinId, FunctionPio0, FunctionPio1, FunctionSioInput, FunctionSioOutput, Pin, PullDown, + }, Pins, }; @@ -8,12 +10,25 @@ use crate::keyboard::{KEY_COLS, KEY_ROWS}; pub fn configure_gpio( pins: Pins, ) -> ( + // keyboard keys [Pin; KEY_COLS], [Pin; KEY_ROWS], + // led pin Pin, + // rgb pin Pin, + // screen pins + ( + Pin, // data + Pin, // clock + Pin, // cs + Pin, // dc + Pin, // rst + Pin, // backlight + ), ) { ( + // keyboard keys [ pins.gpio12.into_function().into_dyn_pin().into_pull_type(), pins.gpio13.into_function().into_dyn_pin().into_pull_type(), @@ -25,7 +40,18 @@ pub fn configure_gpio( pins.gpio10.into_function().into_dyn_pin().into_pull_type(), pins.gpio11.into_function().into_dyn_pin().into_pull_type(), ], + // led pin pins.led.into_function().into_dyn_pin().into_pull_type(), + // rgb pin pins.gpio2.into_function().into_dyn_pin().into_pull_type(), + // screen pins + ( + pins.gpio21.into_function().into_dyn_pin().into_pull_type(), // data + pins.gpio20.into_function().into_dyn_pin().into_pull_type(), // clock + pins.gpio19.into_function().into_dyn_pin().into_pull_type(), // cs + pins.gpio18.into_function().into_dyn_pin().into_pull_type(), // dc + pins.gpio17.into_function().into_dyn_pin().into_pull_type(), // rst + pins.gpio16.into_function().into_dyn_pin().into_pull_type(), // backlight + ), ) } diff --git a/firmware/src/st7789.rs b/firmware/src/st7789.rs new file mode 100644 index 0000000..58fcf84 --- /dev/null +++ b/firmware/src/st7789.rs @@ -0,0 +1,219 @@ +use core::u32; + +use cortex_m::prelude::_embedded_hal_timer_CountDown; +use embedded_hal::digital::v2::OutputPin as _; +use rp_pico::hal::{ + fugit::{ExtU64, MicrosDurationU64}, + gpio::{AnyPin, DynPinId, FunctionSioOutput, Pin, PullDown}, + pio::{PIOBuilder, PIOExt, StateMachineIndex, Tx, UninitStateMachine, PIO}, + timer::CountDown, +}; + +const SCR_W: usize = 240; +const SCR_H: usize = 320; +static mut FB: [[u16; SCR_W]; SCR_H] = [[0x00FFu16; SCR_W]; SCR_H]; +// The framebuffer is a static so that it does not end up on core1's stack. + +pub struct St7789<'timer, P, SM, I> +where + I: AnyPin, + SM: StateMachineIndex, + P: PIOExt, +{ + tx: Tx<(P, SM)>, + _data_pin: I, + _clock_pin: I, + backlight_pin: Pin, + dc_pin: Pin, + cs_pin: Pin, + _rst_pin: Pin, + timer: CountDown<'timer>, +} + +impl<'timer, P, SM, I> St7789<'timer, P, SM, I> +where + I: AnyPin, + P: PIOExt, + SM: StateMachineIndex, +{ + pub fn new( + pio: &mut PIO

, + sm: UninitStateMachine<(P, SM)>, + data_pin: I, + clock_pin: I, + mut cs_pin: Pin, + mut dc_pin: Pin, + mut rst_pin: Pin, + mut backlight_pin: Pin, + timer: CountDown<'timer>, + ) -> Self { + backlight_pin.set_low().unwrap(); + dc_pin.set_low().unwrap(); + cs_pin.set_high().unwrap(); + rst_pin.set_high().unwrap(); + + let side_set = pio::SideSet::new(false, 1, false); + let mut a = pio::Assembler::new_with_side_set(side_set); + let mut wrap_target = a.label(); + let mut wrap_source = a.label(); + a.bind(&mut wrap_target); + a.out_with_side_set(pio::OutDestination::PINS, 1, 0); + a.nop_with_side_set(1); + a.bind(&mut wrap_source); + let program = a.assemble_with_wrap(wrap_source, wrap_target); + let installed = pio.install(&program).unwrap(); + + let data_pin = data_pin.into(); + let clock_pin = clock_pin.into(); + let (mut sm, _, tx) = PIOBuilder::from_installed_program(installed) + // pin config + .side_set_pin_base(clock_pin.id().num) + .out_pins(data_pin.id().num, 1) + // buffer config + .buffers(rp_pico::hal::pio::Buffers::OnlyTx) + .out_shift_direction(rp_pico::hal::pio::ShiftDirection::Left) + .autopull(true) + .pull_threshold(8) + // misc config + .clock_divisor_fixed_point(1, 0) + .build(sm); + + sm.set_pindirs([ + (data_pin.id().num, rp_pico::hal::pio::PinDir::Output), + (clock_pin.id().num, rp_pico::hal::pio::PinDir::Output), + ]); + + sm.start(); + + Self { + tx: tx, + _data_pin: data_pin.into(), + _clock_pin: clock_pin.into(), + backlight_pin: backlight_pin, + dc_pin: dc_pin, + cs_pin: cs_pin, + _rst_pin: rst_pin, + timer: timer, + } + } + + pub fn backlight_on(&mut self) { + self.backlight_pin.set_high().unwrap(); + } + + pub fn backlight_off(&mut self) { + self.backlight_pin.set_low().unwrap(); + } + + pub fn init(&mut self) { + // init sequence + + // self.write_cmd(&[0x0111, 0x3A55, 0x3600]); // reset, exit sleep, color mode 565, set madctl + // self.write_cmd(&[0x002A, 0x0000, SCR_W as u16]); // CASET: column addresses + // self.write_cmd(&[0x002B, 0x0000, SCR_H as u16]); // RASET: row addresses + // self.write_cmd(&[0x2113, 0x2900]); // inversion on, normal display on, main screen on + + self.write_cmd(&[0x01]); // Software reset + self.write_cmd(&[0x11]); // Exit sleep mode + self.write_cmd(&[0x3A, 0x55]); // Set colour mode to 16 bit + self.write_cmd(&[0x36, 0x00]); // Set MADCTL: row then column, refresh is bottom to top ???? + self.write_cmd(&[0x2A, 0x00, 0x00, (SCR_W >> 8) as u8, (SCR_W & 0xFF) as u8]); // CASET: column addresses + self.write_cmd(&[0x2B, 0x00, 0x00, (SCR_H >> 8) as u8, (SCR_H & 0xFF) as u8]); // RASET: row addresses + self.write_cmd(&[0x21]); // Inversion on (supposedly a hack?) + self.write_cmd(&[0x13]); // Normal display on + self.write_cmd(&[0x29]); // Main screen turn on + + self.push_framebuffer(); + self.backlight_on(); + } + + fn wait_idle(&mut self) { + self.tx.clear_stalled_flag(); + while !self.tx.has_stalled() {} + } + + fn sleep(&mut self, t: MicrosDurationU64) { + self.timer.start(t); + loop { + match self.timer.wait() { + Ok(_) => break, + Err(_) => {} + } + } + } + + fn write(&mut self, word: u8) { + while !self.tx.write((word as u32) << 24) { + cortex_m::asm::nop(); + } + } + + fn write_cmd(&mut self, cmd: &[u8]) { + self.wait_idle(); + self.set_dc_cs(false, false); + + self.write(cmd[0]); + if cmd.len() >= 2 { + self.wait_idle(); + self.set_dc_cs(true, false); + for c in &cmd[1..] { + self.write(*c); + } + } + + self.wait_idle(); + self.set_dc_cs(true, true); + } + + fn set_dc_cs(&mut self, dc: bool, cs: bool) { + self.sleep(1.micros().into()); + + if dc { + self.dc_pin.set_high().unwrap(); + } else { + self.dc_pin.set_low().unwrap(); + } + if cs { + self.cs_pin.set_high().unwrap(); + } else { + self.cs_pin.set_low().unwrap(); + } + + self.sleep(1.micros().into()); + } + + fn start_pixels(&mut self) { + self.write_cmd(&[0x2C]); + self.set_dc_cs(true, false); + } + + pub fn fill_framebuffer(&mut self, color: u16) { + for y in 0..SCR_H { + for x in 0..SCR_W { + // doing unchecked access did not meaningfully improve performance + unsafe { + FB[y][x] = color; + } + } + } + } + + pub fn clear_framebuffer(&mut self) { + self.fill_framebuffer(0); + } + + pub fn push_framebuffer(&mut self) { + self.start_pixels(); + for y in 0..SCR_H { + let y = unsafe { FB.get_unchecked(y) }; + for x in 0..SCR_W { + let w = unsafe { y.get_unchecked(x) }; + let w1 = (*w >> 8) as u8; + let w2 = *w as u8; + + self.write(w1); + self.write(w2); + } + } + } +}