diff --git a/firmware/acid-firmware/src/main.rs b/firmware/acid-firmware/src/main.rs index f1a48b0..15a77a7 100644 --- a/firmware/acid-firmware/src/main.rs +++ b/firmware/acid-firmware/src/main.rs @@ -60,6 +60,7 @@ use esp_hal::timer::timg::TimerGroup; use esp_hal::{Blocking, interrupt}; use esp_rtos::embassy::{Executor, InterruptExecutor}; use esp_storage::FlashStorage; +use i_slint_core::software_renderer::TargetPixel; use indoc::writedoc; use itertools::Itertools; use log::{error, info, warn}; @@ -83,7 +84,7 @@ use crate::matrix::IoeMatrix; use crate::peripherals::st7701s::St7701s; use crate::proxy::create_hid_report_interceptor; use crate::ui::backend::{FramebufferPtr, SlintBackend}; -use crate::ui::dpi::{DmaBounce, DmaTxBounceBuf, Framebuffer}; +use crate::ui::dpi::{DmaBounce, DmaTxBounceBuf, Framebuffer, allocate_dma_buffer_in}; use crate::vial::{ CustomKeycodes, VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID, VIAL_KEYBOARD_NAME, VIAL_PRODUCT_ID, VIAL_VENDOR_ID, @@ -133,20 +134,53 @@ static SIGNAL_LCD_SUBMIT: Signal = Signal::new(); /// Used to signal that the MCU is ready to render the GUI. static SIGNAL_UI_RENDER: Signal = Signal::new(); +#[embassy_executor::task] +async fn test_bounce_buffers_task( + channel: DMA_CH0<'static>, + peripheral: SPI2<'static>, + st7701s: St7701s<'static, Blocking>, +) { + test_bounce_buffers(channel, peripheral, st7701s).await; +} + async fn test_bounce_buffers( channel: DMA_CH0<'static>, peripheral: SPI2<'static>, st7701s: St7701s<'static, Blocking>, ) -> DpiTransfer<'static, DmaTxBounceBuf, Blocking> { error!("TEST BOUNCE BUFFERS SECTION ENTERED"); - let windows_len = 2; - let window_size = 368 * core::mem::size_of::(); - let buffer_src = Box::leak(vec![0_u8; windows_len * window_size].into_boxed_slice()); - let mut counter: u8 = 0; - buffer_src.fill_with(|| { - counter = counter.wrapping_add(1); - counter - }); + let windows_len = 960; + let window_size = 8 * 368 * core::mem::size_of::(); + let buffer_src = Box::leak(allocate_dma_buffer_in( + windows_len * window_size, + &PSRAM_ALLOCATOR, + )); + let buffer_src = bytemuck::cast_slice_mut::(buffer_src); + let colors = (0..120_u8) + .rev() + .map(|val| Rgb565Pixel::from_rgb(val * 2, val * 2, val * 2)) + .collect::>(); + for (index, pixel) in buffer_src.iter_mut().enumerate() { + let mut x = (index % 368) as i16 - 120; + let mut y = (index / 368) as i16; + + if x < 240 { + x = core::cmp::min(x, 240 - 1 - x); + y = core::cmp::min(y, 960 - 1 - y); + let min = core::cmp::min(x, y); + + *pixel = colors[min as usize % colors.len()].clone(); + continue; + } + + *pixel = Rgb565Pixel::default(); + } + let buffer_src = bytemuck::cast_slice_mut::(buffer_src); + // let mut counter: u8 = 0; + // buffer_src.fill_with(|| { + // counter = counter.wrapping_add(1); + // counter + // }); let mut buf = DmaBounce::new( channel, AnySpi::from(peripheral), @@ -421,9 +455,15 @@ async fn main(_spawner: Spawner) { info!("ST7701S-based LCD display initialized!"); - let lcd_task = test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2, st7701s); + interrupt_core_0_spawner.must_spawn(test_bounce_buffers_task( + peripherals.DMA_CH0, + peripherals.SPI2, + st7701s, + )); - let _ = lcd_task.await; + // let lcd_task = test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2, st7701s); + + // let _ = lcd_task.await; return; // RMK config diff --git a/firmware/acid-firmware/src/ui/dpi.rs b/firmware/acid-firmware/src/ui/dpi.rs index 845d347..cda7ef4 100644 --- a/firmware/acid-firmware/src/ui/dpi.rs +++ b/firmware/acid-firmware/src/ui/dpi.rs @@ -1,7 +1,7 @@ use core::{ alloc::Layout, pin::Pin, - sync::atomic::{self, AtomicBool}, + sync::atomic::{self, AtomicBool, AtomicU32, AtomicUsize}, }; use alloc::{ @@ -9,7 +9,10 @@ use alloc::{ boxed::Box, vec, }; -use embassy_sync::channel::Channel; +use embassy_sync::{ + channel::{Channel, TrySendError}, + signal::Signal, +}; use embassy_time::Timer; use esp_alloc::MemoryCapability; use esp_hal::{ @@ -30,6 +33,10 @@ use esp_sync::RawMutex; use i_slint_core::software_renderer::Rgb565Pixel; use indoc::{formatdoc, indoc}; use log::{error, info, warn}; +use rmk::{ + futures::{FutureExt, pin_mut}, + join_all, +}; use crate::{PSRAM_ALLOCATOR, SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, peripherals::st7701s::St7701s}; @@ -93,6 +100,9 @@ pub struct DmaBounce { bounce_dst_descs: &'static mut [DmaDescriptor], // A cyclic descriptor list that spans the buffers `bounce_buffer_dst` and `bounce_buffer_src`. bounce_src_descs: &'static mut [DmaDescriptor], + // The index of the next window about to be received into the destination bounce buffer. + window_index_next: usize, + frame_index_next: usize, } impl DmaBounce { @@ -143,6 +153,8 @@ impl DmaBounce { src_descs, bounce_dst_descs, bounce_src_descs, + window_index_next: 0, + frame_index_next: 0, } } @@ -256,17 +268,32 @@ impl DmaBounce { } fn enable_interrupts() { + // TODO: Get from self.channel + let channel_number = 2; + let interrupt = esp_hal::peripherals::Interrupt::DMA_OUT_CH2; + // Enable interrupts for the peripheral - interrupt::enable( - esp_hal::peripherals::Interrupt::DMA_OUT_CH0, - dma_interrupt_handler.priority(), - ) - .unwrap(); - interrupt::enable( - esp_hal::peripherals::Interrupt::DMA_IN_CH0, - dma_interrupt_handler.priority(), - ) - .unwrap(); + interrupt::enable(interrupt, dma_interrupt_handler.priority()).unwrap(); + // interrupt::enable( + // esp_hal::peripherals::Interrupt::DMA_OUT_CH0, + // dma_interrupt_handler.priority(), + // ) + // .unwrap(); + // interrupt::enable( + // esp_hal::peripherals::Interrupt::DMA_IN_CH0, + // dma_interrupt_handler.priority(), + // ) + // .unwrap(); + // interrupt::enable( + // esp_hal::peripherals::Interrupt::DMA_OUT_CH2, + // dma_interrupt_handler.priority(), + // ) + // .unwrap(); + // interrupt::enable( + // esp_hal::peripherals::Interrupt::DMA_IN_CH2, + // dma_interrupt_handler.priority(), + // ) + // .unwrap(); // interrupt::enable( // esp_hal::peripherals::Interrupt::SPI2_DMA, // dma_interrupt_handler.priority(), @@ -275,14 +302,23 @@ impl DmaBounce { // Bind the handler unsafe { - interrupt::bind_interrupt( - esp_hal::peripherals::Interrupt::DMA_OUT_CH0, - dma_interrupt_handler.handler(), - ); - interrupt::bind_interrupt( - esp_hal::peripherals::Interrupt::DMA_IN_CH0, - dma_interrupt_handler.handler(), - ); + interrupt::bind_interrupt(interrupt, dma_interrupt_handler.handler()); + // interrupt::bind_interrupt( + // esp_hal::peripherals::Interrupt::DMA_OUT_CH0, + // dma_interrupt_handler.handler(), + // ); + // interrupt::bind_interrupt( + // esp_hal::peripherals::Interrupt::DMA_IN_CH0, + // dma_interrupt_handler.handler(), + // ); + // interrupt::bind_interrupt( + // esp_hal::peripherals::Interrupt::DMA_OUT_CH2, + // dma_interrupt_handler.handler(), + // ); + // interrupt::bind_interrupt( + // esp_hal::peripherals::Interrupt::DMA_IN_CH2, + // dma_interrupt_handler.handler(), + // ); // interrupt::bind_interrupt( // esp_hal::peripherals::Interrupt::SPI2_DMA, // dma_interrupt_handler.handler(), @@ -290,26 +326,25 @@ impl DmaBounce { } // Enable interrupts in the peripheral. - let channel_number = 0; // TODO: Get from self.channel DMA::regs() .ch(channel_number) .out_int() .ena() .modify(|_, w| { - w.out_total_eof().bit(true); + // w.out_total_eof().bit(true); w.out_eof().bit(true); - w.out_done().bit(true); - w - }); - DMA::regs() - .ch(channel_number) - .in_int() - .ena() - .modify(|_, w| { - w.in_suc_eof().bit(true); - w.in_done().bit(true); + // w.out_done().bit(true); w }); + // DMA::regs() + // .ch(channel_number) + // .in_int() + // .ena() + // .modify(|_, w| { + // w.in_suc_eof().bit(true); + // w.in_done().bit(true); + // w + // }); // SPI2::regs().dma_int_ena().modify(|_, w| { // w.slv_rd_dma_done().bit(true); // w.slv_wr_dma_done().bit(true); @@ -320,16 +355,17 @@ impl DmaBounce { } fn print_regs() { - let channel_number = 0; // TODO: Get from self.channel - let out_int_raw = DMA::regs() - .ch(channel_number as usize) - .out_int() - .st() - .read(); - let in_int_raw = DMA::regs().ch(channel_number as usize).in_int().st().read(); - log::error!( - indoc! {" - int_raw: + // TODO: Get from self.channel + for channel_number in [0, 2] { + let out_int_raw = DMA::regs() + .ch(channel_number as usize) + .out_int() + .st() + .read(); + let in_int_raw = DMA::regs().ch(channel_number as usize).in_int().st().read(); + log::error!( + indoc! {" + int_raw[{channel_number}]: flag: {flag} out: total_eof: {out_total_eof} @@ -339,20 +375,85 @@ impl DmaBounce { suc_eof: {in_suc_eof} done: {in_done} "}, - flag = FLAG.load(atomic::Ordering::SeqCst), - out_total_eof = out_int_raw.out_total_eof().bit_is_set(), - out_eof = out_int_raw.out_eof().bit_is_set(), - out_done = out_int_raw.out_done().bit_is_set(), - in_suc_eof = in_int_raw.in_suc_eof().bit_is_set(), - in_done = in_int_raw.in_done().bit_is_set(), + channel_number = channel_number, + flag = FLAG.load(atomic::Ordering::SeqCst), + out_total_eof = out_int_raw.out_total_eof().bit_is_set(), + out_eof = out_int_raw.out_eof().bit_is_set(), + out_done = out_int_raw.out_done().bit_is_set(), + in_suc_eof = in_int_raw.in_suc_eof().bit_is_set(), + in_done = in_int_raw.in_done().bit_is_set(), + ); + error!( + "int_raw_msg[{channel_number}]: 0x{:08x?}", + INT_CHANNEL.try_receive() + ); + } + } + + /// Receive a window of bytes into the current dst bounce buffer. + /// Finally, swaps the bounce buffers. + async fn receive_window(&mut self) { + // Descriptors are initialized by `DmaTxBuf::new`. + let buffer_src_window = + &mut self.buffer_src[self.window_index_next * self.window_size..][..self.window_size]; + + Self::linear_descriptors_prepare(self.src_descs, Some(buffer_src_window), |_| {}); + // TODO: Precompute a descriptor list for each buffer, then use `None` instead of `Some(&mut *self.bounce_buffer_dst)`. + Self::linear_descriptors_prepare( + self.bounce_dst_descs, + Some(&mut *self.bounce_buffer_dst), + |desc| { + desc.reset_for_rx(); + }, ); - error!("int_raw_msg: 0x{:08x?}", INT_CHANNEL.try_receive()); + + { + // Extend the lifetime to 'static because it is required by Mem2Mem. + // + // Safety: + // Pointees are done being used by the driver before this scope ends, + // this is because we `SimpleMem2MemTransfer::wait()` on the transfer to finish. + let bounce_dst_descs = unsafe { &mut *(self.bounce_dst_descs as *mut _) }; + let src_descs = unsafe { &mut *(self.src_descs as *mut _) }; + let mut mem2mem = Mem2Mem::new(self.channel.reborrow(), self.peripheral_src.reborrow()) + .with_descriptors(bounce_dst_descs, src_descs, self.burst_config) + .unwrap(); + let transfer = mem2mem + .start_transfer(&mut self.bounce_buffer_dst, buffer_src_window) + .unwrap(); + + transfer.wait().unwrap(); + } + + // TODO: Get rid of this! + // unsafe { + // cache_invalidate_addr( + // self.bounce_buffer_dst.as_ptr() as u32, + // self.bounce_buffer_dst.len() as u32, + // ); + // } + // assert_eq!(self.bounce_buffer_dst, buffer_src_window); + + self.increase_window_counter(1); + } + + fn increase_window_counter(&mut self, windows: usize) { + if windows % 2 == 1 { + core::mem::swap(&mut self.bounce_buffer_dst, &mut self.bounce_buffer_src); + } + + self.window_index_next = self.window_index_next + windows; + self.frame_index_next += self.window_index_next / self.windows_len; + self.window_index_next = self.window_index_next % self.windows_len; } pub async fn send(&mut self) // -> DpiTransfer<'static, DmaTxBounceBuf, Blocking> { Self::enable_interrupts(); + // Receive the first window, so that the outbound transfer can read valid data. + self.receive_window().await; + let mut dma_tx_buffer = self.get_dma_tx_buffer(); let transfer = match self .peripheral_dst @@ -381,58 +482,47 @@ impl DmaBounce { } }; - for window_index in 0..self.windows_len { - // Descriptors are initialized by `DmaTxBuf::new`. - let buffer_src_window = - &mut self.buffer_src[window_index * self.window_size..][..self.window_size]; - - Self::linear_descriptors_prepare(self.src_descs, Some(buffer_src_window), |_| {}); - // TODO: Precompute a descriptor list for each buffer, then use `None` instead of `Some(&mut *self.bounce_buffer_dst)`. - Self::linear_descriptors_prepare( - self.bounce_dst_descs, - Some(&mut *self.bounce_buffer_dst), - |desc| { - desc.reset_for_rx(); - }, - ); - - { - // Extend the lifetime to 'static because it is required by Mem2Mem. - // - // Safety: - // Pointees are done being used by the driver before this scope ends, - // this is because we `SimpleMem2MemTransfer::wait()` on the transfer to finish. - let bounce_dst_descs = unsafe { &mut *(self.bounce_dst_descs as *mut _) }; - let src_descs = unsafe { &mut *(self.src_descs as *mut _) }; - let mut mem2mem = - Mem2Mem::new(self.channel.reborrow(), self.peripheral_src.reborrow()) - .with_descriptors(bounce_dst_descs, src_descs, self.burst_config) - .unwrap(); - let transfer = mem2mem - .start_transfer(&mut self.bounce_buffer_dst, buffer_src_window) - .unwrap(); - - Self::print_regs(); - transfer.wait().unwrap(); - } - - Self::print_regs(); - - // TODO: Get rid of this! - unsafe { - cache_invalidate_addr( - self.bounce_buffer_dst.as_ptr() as u32, - self.bounce_buffer_dst.len() as u32, - ); - } - assert_eq!(self.bounce_buffer_dst, buffer_src_window); - } + let mut windows_skipped_total = 0; loop { - warn!("Still sending data to DPI? {}", !transfer.is_done()); - Timer::after_secs(10).await; + // warn!("Iteration. Done = {}", transfer.is_done()); + self.receive_window().await; + // let windows_sent = BOUNCE_BUFFER_SENT.receive().await; + let windows_skipped = WINDOWS_SKIPPED.wait().await; + + if windows_skipped > 0 { + self.increase_window_counter(windows_skipped); + windows_skipped_total += windows_skipped; + error!( + "Skipped {windows_skipped} windows. Windows skipped per frame: {:.2}%", + 100.0 * windows_skipped_total as f32 / (self.frame_index_next + 1) as f32 + ); + } } + // loop { + // // BOUNCE_BUFFER_SENT.receive().await; + // warn!("Iteration. Done = {}", transfer.is_done()); + // let receive_window = self.receive_window().fuse(); + // pin_mut!(receive_window); + // // let mut send_buffer = BOUNCE_BUFFER_SENT.wait().fuse(); + // let mut send_buffer = BOUNCE_BUFFER_SENT.receive().fuse(); + // let window_received_first = rmk::futures::select_biased! { + // () = receive_window => Ok(()), + // windows_sent = send_buffer => Err(windows_sent), + // }; + + // match window_received_first { + // Ok(()) => { + // send_buffer.await; + // } + // Err(windows_sent) => { + // error!("Sent {windows_sent} windows before a window could be received."); + // receive_window.await; + // } + // } + // } + // transfer } @@ -480,14 +570,41 @@ unsafe impl DmaTxBuffer for DmaTxBounceBuf { static INT_CHANNEL: Channel = Channel::new(); static FLAG: AtomicBool = AtomicBool::new(false); +// static WINDOWS_SENT: AtomicU32 = AtomicU32::new(0); +// static BOUNCE_BUFFER_SENT: Channel = Channel::new(); +static WINDOWS_SKIPPED: Signal = Signal::new(); #[handler(priority = Priority::Priority3)] #[ram] // Improves performance. fn dma_interrupt_handler() { - FLAG.store(true, atomic::Ordering::SeqCst); - let int_raw = DMA::regs().ch(0).out_int().raw().read(); + let interrupt = DMA::regs().ch(2).out_int(); + let bounce_buffer_processed = interrupt.st().read().out_eof().bit_is_set(); + if bounce_buffer_processed { + // Clear the bit by writing 1 to the clear bits. + interrupt.clr().write(|w| w.out_eof().bit(true)); - INT_CHANNEL.try_send(int_raw.bits()).unwrap(); + let windows_skipped = WINDOWS_SKIPPED + .try_take() + .map(|windows_skipped| windows_skipped + 1) + .unwrap_or_default(); + WINDOWS_SKIPPED.signal(windows_skipped); + // let value = BOUNCE_BUFFER_SENT.try_receive().ok().unwrap_or_default() + 1; + // BOUNCE_BUFFER_SENT.try_send(value).expect( + // "failed to send bounce buffer signal, because the previous one wasn't processed yet", + // ); + // let value = WINDOWS_SENT.fetch_add(1, atomic::Ordering::SeqCst); + // warn!("inner int {value}"); + } + // FLAG.store(true, atomic::Ordering::SeqCst); + // let int_raw = DMA::regs() + // .ch(2) + // .out_int() + // .st() + // .read() + // .out_eof() + // .bit_is_set(); + + // INT_CHANNEL.try_send(int_raw.bits()).unwrap(); // let lcd_cam = unsafe { &*esp_hal::peripherals::LCD_CAM::PTR };