diff --git a/sixtyfps_runtime/rendering_backends/mcu/Cargo.toml b/sixtyfps_runtime/rendering_backends/mcu/Cargo.toml index 13f67e409..d71461ab3 100644 --- a/sixtyfps_runtime/rendering_backends/mcu/Cargo.toml +++ b/sixtyfps_runtime/rendering_backends/mcu/Cargo.toml @@ -19,7 +19,7 @@ path = "lib.rs" simulator = ["winit", "glutin", "femtovg", "embedded-graphics-simulator", "std", "imgref", "scoped-tls-hkt"] default = ["simulator"] -pico-st7789 = ["unsafe_single_core", "rp-pico", "embedded-hal", "cortex-m-rt", "alloc-cortex-m", "embedded-time", "cortex-m", "display-interface-spi", "st7789", "defmt", "defmt-rtt", "panic-probe", "sixtyfps-corelib/defmt" ] +pico-st7789 = ["unsafe_single_core", "rp-pico", "embedded-hal", "cortex-m-rt", "alloc-cortex-m", "embedded-time", "cortex-m", "display-interface-spi", "st7789", "defmt", "defmt-rtt", "panic-probe", "sixtyfps-corelib/defmt", "shared-bus" ] unsafe_single_core = ["sixtyfps-corelib/unsafe_single_core"] @@ -54,6 +54,7 @@ embedded-time = { version = "0.12.0", optional = true } cortex-m = { version = "0.7.2", optional = true } display-interface-spi = { version = "0.4.1", optional = true } st7789 = { version = "0.6.1", optional = true } +shared-bus = { version = "0.2", optional = true } defmt = { version = "0.3.0", optional = true } defmt-rtt = { version = "0.3.0", optional = true } diff --git a/sixtyfps_runtime/rendering_backends/mcu/lib.rs b/sixtyfps_runtime/rendering_backends/mcu/lib.rs index 5b59e8d47..f38490e4a 100644 --- a/sixtyfps_runtime/rendering_backends/mcu/lib.rs +++ b/sixtyfps_runtime/rendering_backends/mcu/lib.rs @@ -40,7 +40,13 @@ mod renderer; pub trait Devices { fn screen_size(&self) -> IntSize; fn fill_region(&mut self, region: IntRect, pixels: &[Rgb888]); + fn read_touch_event(&mut self) -> Option { + None + } fn debug(&mut self, _: &str); + fn time(&mut self) -> core::time::Duration { + core::time::Duration::ZERO + } } impl crate::Devices for T @@ -68,12 +74,10 @@ where fn debug(&mut self, text: &str) { use embedded_graphics::{ mono_font::{ascii::FONT_6X10, MonoTextStyle}, - text::{Alignment, Text}, + text::Text, }; let style = MonoTextStyle::new(&FONT_6X10, Rgb888::RED.into()); - Text::with_alignment(text, Point::new(20, 30), style, Alignment::Center) - .draw(self) - .unwrap(); + Text::new(text, Point::new(20, 30), style).draw(self).unwrap(); } } @@ -210,7 +214,7 @@ mod the_backend { DEVICES.with(|devices| { let mut devices = devices.borrow_mut(); let devices = devices.as_mut().unwrap(); - let size = devices.screen_size(); + let size = devices.screen_size().to_f32() / runtime_window.scale_factor(); runtime_window.set_window_item_geometry(size.width as _, size.height as _); let background = crate::renderer::to_rgb888_color_discard_alpha(window.background_color.get()); @@ -240,6 +244,7 @@ mod the_backend { Some(McuEvent::Custom(e)) => e(), Some(McuEvent::Repaint) => { if let Some(window) = WINDOWS.with(|x| x.borrow().clone()) { + sixtyfps_corelib::animations::update_animations(); self.draw(window) } } @@ -247,6 +252,26 @@ mod the_backend { // TODO: sleep(); } } + DEVICES.with(|devices| { + let e = devices.borrow_mut().as_mut().unwrap().read_touch_event(); + //devices.borrow_mut().as_mut().unwrap().debug(&alloc::format!("EVENT: {:?}", e)); + if let Some(mut event) = e { + if let Some(window) = WINDOWS.with(|x| x.borrow().clone()) { + let w = window.self_weak.upgrade().unwrap(); + // scale the event by the scale factor: + if let Some(p) = event.pos() { + event.translate(p / w.scale_factor() - p); + } + w.process_mouse_input(event); + } + } + let t = devices.borrow_mut().as_mut().unwrap().time().as_secs(); + devices.borrow_mut().as_mut().unwrap().debug(&alloc::format!("{}", t)); + }); + sixtyfps_corelib::animations::update_animations(); + if let Some(window) = WINDOWS.with(|x| x.borrow().clone()) { + self.draw(window) + } } } @@ -298,6 +323,10 @@ mod the_backend { ) -> Result<(), Box> { unimplemented!() } + + fn duration_since_start(&'static self) -> core::time::Duration { + DEVICES.with(|devices| devices.borrow_mut().as_mut().unwrap().time()) + } } } @@ -314,7 +343,7 @@ pub fn init_simulator() { }); } -pub fn init_with_display(mut display: Display) { +pub fn init_with_display(display: Display) { DEVICES.with(|d| *d.borrow_mut() = Some(Box::new(display))); sixtyfps_corelib::backend::instance_or_init(|| { alloc::boxed::Box::new(the_backend::MCUBackend::default()) diff --git a/sixtyfps_runtime/rendering_backends/mcu/pico_st7789.rs b/sixtyfps_runtime/rendering_backends/mcu/pico_st7789.rs index 777fc3815..867971be2 100644 --- a/sixtyfps_runtime/rendering_backends/mcu/pico_st7789.rs +++ b/sixtyfps_runtime/rendering_backends/mcu/pico_st7789.rs @@ -1,15 +1,17 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial) +extern crate alloc; + +use alloc::boxed::Box; +pub use cortex_m_rt::entry; +use embedded_hal::blocking::spi::Transfer; +use embedded_hal::digital::v2::{InputPin, OutputPin}; use embedded_time::rate::*; use rp_pico::hal; use rp_pico::hal::pac; use rp_pico::hal::prelude::*; -use embedded_hal::digital::v2::OutputPin; - -pub use cortex_m_rt::entry; - use defmt_rtt as _; // global logger use panic_probe as _; @@ -18,6 +20,9 @@ fn oom(_: core::alloc::Layout) -> ! { loop {} } use alloc_cortex_m::CortexMHeap; +use rp_pico::hal::rtc::{DateTime, RealTimeClock}; + +use crate::Devices; const HEAP_SIZE: usize = 128 * 1024; static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE]; @@ -45,6 +50,8 @@ pub fn init_board() { let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); + unsafe { ALLOCATOR.init(&mut HEAP as *const u8 as usize, core::mem::size_of_val(&HEAP)) } + let sio = hal::sio::Sio::new(pac.SIO); let pins = rp_pico::Pins::new(pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut pac.RESETS); @@ -61,12 +68,14 @@ pub fn init_board() { 4_000_000u32.Hz(), &embedded_hal::spi::MODE_3, ); + // FIXME: a cleaner way to get a static reference, or be able to use non-static backend + let spi = Box::leak(Box::new(shared_bus::BusManagerSimple::new(spi))); let rst = pins.gpio15.into_push_pull_output(); let dc = pins.gpio8.into_push_pull_output(); let cs = pins.gpio9.into_push_pull_output(); - let di = display_interface_spi::SPIInterface::new(spi, dc, cs); + let di = display_interface_spi::SPIInterface::new(spi.acquire_spi(), dc, cs); let mut display = st7789::ST7789::new(di, rst, 320, 240); @@ -78,9 +87,145 @@ pub fn init_board() { bl.set_high().unwrap(); } - unsafe { ALLOCATOR.init(&mut HEAP as *const u8 as usize, core::mem::size_of_val(&HEAP)) } - display.init(&mut delay).unwrap(); display.set_orientation(st7789::Orientation::Landscape).unwrap(); - crate::init_with_display(display); + + let touch = xpt2046::XPT2046::new( + pins.gpio17.into_pull_down_input(), + pins.gpio16.into_push_pull_output(), + spi.acquire_spi(), + ) + .unwrap(); + + let clock = RealTimeClock::new( + pac.RTC, + clocks.rtc_clock, + &mut pac.RESETS, + DateTime { + year: 1970, + month: 1, + day: 1, + day_of_week: hal::rtc::DayOfWeek::Thursday, + hour: 0, + minute: 0, + second: 0, + }, + ) + .unwrap(); + + crate::init_with_display(PicoDevices { display, touch, last_touch: Default::default(), clock }); +} + +struct PicoDevices { + display: Display, + touch: Touch, + last_touch: Option, + clock: RealTimeClock, +} + +impl, SPI: Transfer> Devices + for PicoDevices> +{ + fn screen_size(&self) -> sixtyfps_corelib::graphics::IntSize { + self.display.screen_size() + } + + fn fill_region( + &mut self, + region: sixtyfps_corelib::graphics::IntRect, + pixels: &[embedded_graphics::pixelcolor::Rgb888], + ) { + self.display.fill_region(region, pixels) + } + + fn debug(&mut self, text: &str) { + self.display.debug(text) + } + + fn read_touch_event(&mut self) -> Option { + let button = sixtyfps_corelib::items::PointerEventButton::left; + self.touch + .read() + .map_err(|_| ()) + .unwrap() + .map(|point| { + let point = point.to_f32() / (i16::MAX as f32); + let size = self.display.screen_size().to_f32(); + let pos = euclid::point2(point.x * size.width, point.y * size.height); + match self.last_touch.replace(pos) { + Some(_) => sixtyfps_corelib::input::MouseEvent::MouseMoved { pos }, + None => sixtyfps_corelib::input::MouseEvent::MousePressed { pos, button }, + } + }) + .or_else(|| { + self.last_touch + .take() + .map(|pos| sixtyfps_corelib::input::MouseEvent::MouseReleased { pos, button }) + }) + } + + fn time(&mut self) -> core::time::Duration { + let time = self.clock.now(); + match time { + // FIXME! milisecond + Ok(t) => core::time::Duration::from_secs(t.second as u64 + t.minute as u64 * 60), + Err(_) => core::time::Duration::ZERO, + } + } +} + +mod xpt2046 { + use embedded_hal::blocking::spi::Transfer; + use embedded_hal::digital::v2::{InputPin, OutputPin}; + use euclid::default::Point2D; + + pub struct XPT2046> { + irq: IRQ, + cs: CS, + spi: SPI, + } + + impl, CS: OutputPin, SPI: Transfer> + XPT2046 + { + pub fn new(irq: IRQ, mut cs: CS, spi: SPI) -> Result { + cs.set_high()?; + Ok(Self { irq, cs, spi }) + } + + pub fn read(&mut self) -> Result>, Error> { + if self.irq.is_low().map_err(|e| Error::Pin(e))? { + self.cs.set_low().map_err(|e| Error::Pin(e))?; + const CMD_X_READ: u8 = 0b10010000; + const CMD_Y_READ: u8 = 0b11010000; + + macro_rules! xchg { + ($byte:expr) => { + match self.spi.transfer(&mut [$byte]).map_err(|e| Error::Transfer(e))? { + [x] => *x as i16, + _ => return Err(Error::InternalError), + } + }; + } + + xchg!(CMD_X_READ); + let mut x = xchg!(0) << 8; + x += xchg!(CMD_Y_READ); + let mut y = xchg!(0) << 8; + y += xchg!(0); + + self.cs.set_high().map_err(|e| Error::Pin(e))?; + + Ok(Some(Point2D::new(i16::MAX - x, y))) + } else { + Ok(None) + } + } + } + + pub enum Error { + Pin(PinE), + Transfer(TransferE), + InternalError, + } }