// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial extern crate alloc; use alloc::boxed::Box; use alloc::rc::Rc; use alloc::vec; use alloc_cortex_m::CortexMHeap; use core::cell::RefCell; use core::convert::Infallible; use cortex_m::interrupt::Mutex; use cortex_m::singleton; pub use cortex_m_rt::entry; use defmt_rtt as _; use embedded_hal::digital::v2::OutputPin; use embedded_hal::spi::FullDuplex; use fugit::{Hertz, RateExtU32}; use hal::dma::{DMAExt, SingleChannel, WriteTarget}; use renderer::Rgb565Pixel; use rp_pico::hal::gpio::{self, Interrupt as GpioInterrupt}; use rp_pico::hal::pac::interrupt; use rp_pico::hal::timer::{Alarm, Alarm0}; use rp_pico::hal::{self, pac, prelude::*, Timer}; use slint::platform::software_renderer as renderer; use slint::platform::{PointerEventButton, WindowEvent}; #[cfg(feature = "panic-probe")] use panic_probe as _; mod display_interface_spi; const HEAP_SIZE: usize = 200 * 1024; static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE]; #[global_allocator] static ALLOCATOR: CortexMHeap = CortexMHeap::empty(); type IrqPin = gpio::Pin; static IRQ_PIN: Mutex>> = Mutex::new(RefCell::new(None)); static ALARM0: Mutex>> = Mutex::new(RefCell::new(None)); static TIMER: Mutex>> = Mutex::new(RefCell::new(None)); // 16ns for serial clock cycle (write), page 43 of https://www.waveshare.com/w/upload/a/ae/ST7789_Datasheet.pdf const SPI_ST7789VW_MAX_FREQ: Hertz = Hertz::::Hz(62_500_000); const DISPLAY_SIZE: slint::PhysicalSize = slint::PhysicalSize::new(320, 240); /// The Pixel type of the backing store pub type TargetPixel = Rgb565Pixel; pub fn init() { unsafe { ALLOCATOR.init(&mut HEAP as *const u8 as usize, core::mem::size_of_val(&HEAP)) } slint::platform::set_platform(Box::new(PicoBackend::default())) .expect("backend already initialized"); } #[derive(Default)] struct PicoBackend { window: RefCell>>, } impl slint::platform::Platform for PicoBackend { fn create_window_adapter( &self, ) -> Result, slint::PlatformError> { let window = renderer::MinimalSoftwareWindow::new(renderer::RepaintBufferType::ReusedBuffer); self.window.replace(Some(window.clone())); Ok(window) } fn duration_since_start(&self) -> core::time::Duration { let counter = cortex_m::interrupt::free(|cs| { TIMER.borrow(cs).borrow().as_ref().map(|t| t.get_counter().ticks()).unwrap_or_default() }); core::time::Duration::from_micros(counter) } fn run_event_loop(&self) -> Result<(), slint::PlatformError> { let mut pac = pac::Peripherals::take().unwrap(); let core = pac::CorePeripherals::take().unwrap(); let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); let clocks = hal::clocks::init_clocks_and_plls( rp_pico::XOSC_CRYSTAL_FREQ, pac.XOSC, pac.CLOCKS, pac.PLL_SYS, pac.PLL_USB, &mut pac.RESETS, &mut watchdog, ) .ok() .unwrap(); let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().raw()); 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); let _spi_sclk = pins.gpio10.into_mode::(); let _spi_mosi = pins.gpio11.into_mode::(); let _spi_miso = pins.gpio12.into_mode::(); let spi = hal::spi::Spi::<_, _, 8>::new(pac.SPI1); let spi = spi.init( &mut pac.RESETS, clocks.peripheral_clock.freq(), SPI_ST7789VW_MAX_FREQ, &embedded_hal::spi::MODE_3, ); let spi = singleton!(:shared_bus::BusManagerSimple> = shared_bus::BusManagerSimple::new(spi)).unwrap(); let rst = pins.gpio15.into_push_pull_output(); let bl = pins.gpio13.into_push_pull_output(); let dc = pins.gpio8.into_push_pull_output(); let cs = pins.gpio9.into_push_pull_output(); let (dc_copy, cs_copy) = unsafe { (core::ptr::read(&dc as *const _), core::ptr::read(&cs as *const _)) }; let di = display_interface_spi::SPIInterface::new(spi.acquire_spi(), dc, cs); let mut display = st7789::ST7789::new( di, Some(rst), Some(bl), DISPLAY_SIZE.width as _, DISPLAY_SIZE.height as _, ); display.init(&mut delay).unwrap(); display.set_orientation(st7789::Orientation::Landscape).unwrap(); let touch_irq = pins.gpio17.into_pull_up_input(); touch_irq.set_interrupt_enabled(GpioInterrupt::LevelLow, true); cortex_m::interrupt::free(|cs| { IRQ_PIN.borrow(cs).replace(Some(touch_irq)); }); let mut touch = xpt2046::XPT2046::new(&IRQ_PIN, pins.gpio16.into_push_pull_output(), spi.acquire_spi()) .unwrap(); let mut timer = Timer::new(pac.TIMER, &mut pac.RESETS); let mut alarm0 = timer.alarm_0().unwrap(); alarm0.enable_interrupt(); cortex_m::interrupt::free(|cs| { ALARM0.borrow(cs).replace(Some(alarm0)); TIMER.borrow(cs).replace(Some(timer)); }); unsafe { pac::NVIC::unmask(pac::Interrupt::IO_IRQ_BANK0); pac::NVIC::unmask(pac::Interrupt::TIMER_IRQ_0); } let dma = pac.DMA.split(&mut pac.RESETS); // SAFETY: This is not safe :-( let stolen_spi = unsafe { hal::spi::Spi::<_, _, 8>::new(rp_pico::hal::pac::Peripherals::steal().SPI1).init( &mut pac.RESETS, clocks.peripheral_clock.freq(), SPI_ST7789VW_MAX_FREQ, &embedded_hal::spi::MODE_3, ) }; let pio = PioTransfer::Idle( dma.ch0, vec![Rgb565Pixel::default(); DISPLAY_SIZE.width as _].leak(), stolen_spi, ); let mut buffer_provider = DrawBuffer { display, buffer: vec![Rgb565Pixel::default(); DISPLAY_SIZE.width as _].leak(), pio: Some(pio), stolen_pin: (dc_copy, cs_copy), }; let mut last_touch = None; self.window.borrow().as_ref().unwrap().set_size(DISPLAY_SIZE); loop { slint::platform::update_timers_and_animations(); if let Some(window) = self.window.borrow().clone() { window.draw_if_needed(|renderer| { renderer.render_by_line(&mut buffer_provider); buffer_provider.flush_frame(); }); // handle touch event let button = PointerEventButton::Left; if let Some(event) = touch .read() .map_err(|_| ()) .unwrap() .map(|point| { let position = slint::PhysicalPosition::new( (point.x * DISPLAY_SIZE.width as f32) as _, (point.y * DISPLAY_SIZE.height as f32) as _, ) .to_logical(window.scale_factor()); match last_touch.replace(position) { Some(_) => WindowEvent::PointerMoved { position }, None => WindowEvent::PointerPressed { position, button }, } }) .or_else(|| { last_touch .take() .map(|position| WindowEvent::PointerReleased { position, button }) }) { let is_pointer_release_event = matches!(event, WindowEvent::PointerReleased { .. }); window.dispatch_event(event); // removes hover state on widgets if is_pointer_release_event { window.dispatch_event(WindowEvent::PointerExited); } // Don't go to sleep after a touch event that forces a redraw continue; } if window.has_active_animations() { continue; } } let sleep_duration = match slint::platform::duration_until_next_timer_update() { None => None, Some(d) => { let micros = d.as_micros() as u32; if micros < 10 { // Cannot wait for less than 10µs, or `schedule()` panics continue; } else { Some(fugit::MicrosDurationU32::micros(micros)) } } }; cortex_m::interrupt::free(|cs| { if let Some(duration) = sleep_duration { ALARM0.borrow(cs).borrow_mut().as_mut().unwrap().schedule(duration).unwrap(); } IRQ_PIN .borrow(cs) .borrow() .as_ref() .unwrap() .set_interrupt_enabled(GpioInterrupt::LevelLow, true); }); cortex_m::asm::wfe(); } } fn debug_log(&self, arguments: core::fmt::Arguments) { use alloc::string::ToString; defmt::println!("{=str}", arguments.to_string()); } } enum PioTransfer { Idle(CH, &'static mut [TargetPixel], TO), Running(hal::dma::single_buffer::Transfer), } impl + FullDuplex, CH: SingleChannel> PioTransfer { fn wait(self) -> (CH, &'static mut [TargetPixel], TO) { match self { PioTransfer::Idle(a, b, c) => (a, b, c), PioTransfer::Running(dma) => { let (a, b, mut to) = dma.wait(); // After the DMA operated, we need to empty the receive FIFO, otherwise the touch screen // driver will pick wrong values. Continue to read as long as we don't get a Err(WouldBlock) while !to.read().is_err() {} (a, b.0, to) } } } } struct DrawBuffer { display: Display, buffer: &'static mut [TargetPixel], pio: Option, stolen_pin: Stolen, } impl< DI: display_interface::WriteOnlyDataCommand, RST: OutputPin, BL: OutputPin, TO: WriteTarget + FullDuplex, CH: SingleChannel, DC_: OutputPin, CS_: OutputPin, > renderer::LineBufferProvider for &mut DrawBuffer, PioTransfer, (DC_, CS_)> { type TargetPixel = TargetPixel; fn process_line( &mut self, line: usize, range: core::ops::Range, render_fn: impl FnOnce(&mut [TargetPixel]), ) { render_fn(&mut self.buffer[range.clone()]); // convert from little to big indian before sending to the DMA channel for x in &mut self.buffer[range.clone()] { *x = Rgb565Pixel(x.0.to_be()) } let (ch, mut b, spi) = self.pio.take().unwrap().wait(); self.stolen_pin.1.set_high().unwrap(); /*self.display.set_pixels( dirty_region.min_x() as _, line.get() as _, dirty_region.max_x() as u16, line.get() as u16, self.buffer[dirty_region.origin.x as usize ..dirty_region.origin.x as usize + dirty_region.size.width as usize] .iter() .map(|x| embedded_graphics::pixelcolor::raw::RawU16::from(*x).into_inner()), );*/ core::mem::swap(&mut self.buffer, &mut b); // We send empty data just to get the device in the right window self.display .set_pixels( range.start as u16, line as _, range.end as u16, line as u16, core::iter::empty(), ) .unwrap(); self.stolen_pin.1.set_low().unwrap(); self.stolen_pin.0.set_high().unwrap(); let mut dma = hal::dma::single_buffer::Config::new(ch, PartialReadBuffer(b, range), spi); dma.pace(hal::dma::Pace::PreferSink); self.pio = Some(PioTransfer::Running(dma.start())); /*let (a, b, c) = dma.start().wait(); self.pio = Some(PioTransfer::Idle(a, b.0, c));*/ } } impl< DI: display_interface::WriteOnlyDataCommand, RST: OutputPin, BL: OutputPin, TO: WriteTarget + FullDuplex, CH: SingleChannel, DC_: OutputPin, CS_: OutputPin, > DrawBuffer, PioTransfer, (DC_, CS_)> { fn flush_frame(&mut self) { let (ch, b, spi) = self.pio.take().unwrap().wait(); self.pio = Some(PioTransfer::Idle(ch, b, spi)); self.stolen_pin.1.set_high().unwrap(); } } struct PartialReadBuffer(&'static mut [Rgb565Pixel], core::ops::Range); unsafe impl embedded_dma::ReadBuffer for PartialReadBuffer { type Word = u8; unsafe fn read_buffer(&self) -> (*const ::Word, usize) { let act_slice = &self.0[self.1.clone()]; (act_slice.as_ptr() as *const u8, act_slice.len() * core::mem::size_of::()) } } mod xpt2046 { use core::cell::RefCell; use cortex_m::interrupt::Mutex; use embedded_hal::blocking::spi::Transfer; use embedded_hal::digital::v2::{InputPin, OutputPin}; use euclid::default::Point2D; use fugit::RateExtU32; pub struct XPT2046> { irq: &'static Mutex>>, cs: CS, spi: SPI, pressed: bool, } impl, CS: OutputPin, SPI: Transfer> XPT2046 { pub fn new( irq: &'static Mutex>>, mut cs: CS, spi: SPI, ) -> Result { cs.set_high()?; Ok(Self { irq, cs, spi, pressed: false }) } pub fn read(&mut self) -> Result>, Error> { const PRESS_THRESHOLD: i32 = -25_000; const RELEASE_THRESHOLD: i32 = -30_000; let threshold = if self.pressed { RELEASE_THRESHOLD } else { PRESS_THRESHOLD }; self.pressed = false; if cortex_m::interrupt::free(|cs| { self.irq.borrow(cs).borrow().as_ref().unwrap().is_low() }) .map_err(|e| Error::Pin(e))? { const CMD_X_READ: u8 = 0b10010000; const CMD_Y_READ: u8 = 0b11010000; const CMD_Z1_READ: u8 = 0b10110000; const CMD_Z2_READ: u8 = 0b11000000; // These numbers were measured approximately. const MIN_X: u32 = 1900; const MAX_X: u32 = 30300; const MIN_Y: u32 = 2300; const MAX_Y: u32 = 30300; // FIXME! how else set the frequency to this device unsafe { set_spi_freq(3_000_000u32.Hz()) }; self.cs.set_low().map_err(|e| Error::Pin(e))?; macro_rules! xchg { ($byte:expr) => { match self .spi .transfer(&mut [$byte, 0, 0]) .map_err(|e| Error::Transfer(e))? { [_, h, l] => ((*h as u32) << 8) | (*l as u32), _ => return Err(Error::InternalError), } }; } let z1 = xchg!(CMD_Z1_READ); let z2 = xchg!(CMD_Z2_READ); let z = z1 as i32 - z2 as i32; if z < threshold { xchg!(0); self.cs.set_high().map_err(|e| Error::Pin(e))?; unsafe { set_spi_freq(super::SPI_ST7789VW_MAX_FREQ) }; return Ok(None); } xchg!(CMD_X_READ | 1); // Dummy read, first read is a outlier let mut point = Point2D::new(0u32, 0u32); for _ in 0..10 { let y = xchg!(CMD_Y_READ); let x = xchg!(CMD_X_READ); point += euclid::vec2(i16::MAX as u32 - x, y) } let z1 = xchg!(CMD_Z1_READ); let z2 = xchg!(CMD_Z2_READ); let z = z1 as i32 - z2 as i32; xchg!(0); self.cs.set_high().map_err(|e| Error::Pin(e))?; unsafe { set_spi_freq(super::SPI_ST7789VW_MAX_FREQ) }; if z < RELEASE_THRESHOLD { return Ok(None); } point /= 10; self.pressed = true; Ok(Some(euclid::point2( point.x.saturating_sub(MIN_X) as f32 / (MAX_X - MIN_X) as f32, point.y.saturating_sub(MIN_Y) as f32 / (MAX_Y - MIN_Y) as f32, ))) } else { Ok(None) } } } pub enum Error { Pin(PinE), Transfer(TransferE), InternalError, } unsafe fn set_spi_freq(freq: impl Into>) { use rp_pico::hal; // FIXME: the touchscreen and the LCD have different frequencies, but we cannot really set different frequencies to different SpiProxy without this hack hal::spi::Spi::<_, _, 8>::new(hal::pac::Peripherals::steal().SPI1) .set_baudrate(125_000_000u32.Hz(), freq); } } #[interrupt] fn IO_IRQ_BANK0() { cortex_m::interrupt::free(|cs| { let mut pin = IRQ_PIN.borrow(cs).borrow_mut(); let pin = pin.as_mut().unwrap(); pin.set_interrupt_enabled(GpioInterrupt::LevelLow, false); pin.clear_interrupt(GpioInterrupt::LevelLow); }); } #[interrupt] fn TIMER_IRQ_0() { cortex_m::interrupt::free(|cs| { ALARM0.borrow(cs).borrow_mut().as_mut().unwrap().clear_interrupt(); }); } #[cfg(not(feature = "panic-probe"))] #[inline(never)] #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { // Safety: it's ok to steal here since we are in the panic handler, and the rest of the code will not be run anymore let (mut pac, core) = unsafe { (pac::Peripherals::steal(), pac::CorePeripherals::steal()) }; 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); let mut led = pins.led.into_push_pull_output(); led.set_high().unwrap(); // Re-init the display let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); let clocks = hal::clocks::init_clocks_and_plls( rp_pico::XOSC_CRYSTAL_FREQ, pac.XOSC, pac.CLOCKS, pac.PLL_SYS, pac.PLL_USB, &mut pac.RESETS, &mut watchdog, ) .ok() .unwrap(); let _spi_sclk = pins.gpio10.into_mode::(); let _spi_mosi = pins.gpio11.into_mode::(); let _spi_miso = pins.gpio12.into_mode::(); let spi = hal::spi::Spi::<_, _, 8>::new(pac.SPI1); let spi = spi.init( &mut pac.RESETS, clocks.peripheral_clock.freq(), 4_000_000u32.Hz(), &embedded_hal::spi::MODE_3, ); let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().raw()); let rst = pins.gpio15.into_push_pull_output(); let bl = pins.gpio13.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 mut display = st7789::ST7789::new(di, Some(rst), Some(bl), 320, 240); use core::fmt::Write; use embedded_graphics::{ mono_font::{ascii::FONT_6X10, MonoTextStyle}, pixelcolor::Rgb565, prelude::*, text::Text, }; display.init(&mut delay).unwrap(); display.set_orientation(st7789::Orientation::Landscape).unwrap(); display.fill_solid(&display.bounding_box(), Rgb565::new(0x00, 0x25, 0xff)).unwrap(); struct WriteToScreen<'a, D> { x: i32, y: i32, width: i32, style: MonoTextStyle<'a, Rgb565>, display: &'a mut D, } let mut writer = WriteToScreen { x: 0, y: 1, width: display.bounding_box().size.width as i32 / 6 - 1, style: MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE), display: &mut display, }; impl<'a, D: DrawTarget> Write for WriteToScreen<'a, D> { fn write_str(&mut self, mut s: &str) -> Result<(), core::fmt::Error> { while !s.is_empty() { let (x, y) = (self.x, self.y); let end_of_line = s .find(|c| { if c == '\n' || self.x > self.width { self.x = 0; self.y += 1; true } else { self.x += 1; false } }) .unwrap_or(s.len()); let (line, rest) = s.split_at(end_of_line); let sz = self.style.font.character_size; Text::new(line, Point::new(x * sz.width as i32, y * sz.height as i32), self.style) .draw(self.display) .map_err(|_| core::fmt::Error)?; s = rest.strip_prefix('\n').unwrap_or(rest); } Ok(()) } } write!(writer, "{}", info).unwrap(); loop { delay.delay_ms(100); led.set_low().unwrap(); delay.delay_ms(100); led.set_high().unwrap(); } }