diff --git a/firmware/acid-firmware/src/main.rs b/firmware/acid-firmware/src/main.rs index 15a77a7..b53c982 100644 --- a/firmware/acid-firmware/src/main.rs +++ b/firmware/acid-firmware/src/main.rs @@ -149,8 +149,11 @@ async fn test_bounce_buffers( st7701s: St7701s<'static, Blocking>, ) -> DpiTransfer<'static, DmaTxBounceBuf, Blocking> { error!("TEST BOUNCE BUFFERS SECTION ENTERED"); - let windows_len = 960; - let window_size = 8 * 368 * core::mem::size_of::(); + const WIDTH: usize = 368; + const HEIGHT: usize = 960; + const ROWS_PER_WINDOW: usize = 8; + let windows_len = HEIGHT / ROWS_PER_WINDOW; + let window_size = ROWS_PER_WINDOW * WIDTH * core::mem::size_of::(); let buffer_src = Box::leak(allocate_dma_buffer_in( windows_len * window_size, &PSRAM_ALLOCATOR, @@ -158,7 +161,19 @@ async fn test_bounce_buffers( 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)) + .map(|val| { + // Rgb565Pixel::from_rgb( + // (val % 2) * (0b11111 / (2 - 1)), + // (val % 8) * (0b111111 / (8 - 1)), + // (val % 32) * (0b11111 / (32 - 1)), + // ) + // Rgb565Pixel::from_rgb( + // (0b11111 as f32 * (val as f32 / 119.0)) as u8, + // (0b111111 as f32 * (val as f32 / 119.0)) as u8, + // (0b11111 as f32 * (val as f32 / 119.0)) as u8, + // ) + Rgb565Pixel::from_rgb(0xFF, val * 2, 0) + }) .collect::>(); for (index, pixel) in buffer_src.iter_mut().enumerate() { let mut x = (index % 368) as i16 - 120; @@ -191,6 +206,7 @@ async fn test_bounce_buffers( internal_memory: InternalBurstConfig::Enabled, external_memory: ExternalBurstConfig::Size32, }, + false, ); let _ = buf.send().await; error!("TEST BOUNCE BUFFERS SECTION DONE"); diff --git a/firmware/acid-firmware/src/peripherals/st7701s/mod.rs b/firmware/acid-firmware/src/peripherals/st7701s/mod.rs index 6543176..35e9366 100644 --- a/firmware/acid-firmware/src/peripherals/st7701s/mod.rs +++ b/firmware/acid-firmware/src/peripherals/st7701s/mod.rs @@ -1273,7 +1273,10 @@ where // - prevent flash from being used whilst your program is running. (There's a PR to make // this easy to do) // https://github.com/esp-rs/esp-hal/pull/3024 - .with_frequency(Rate::from_mhz(11)) // From Adafruit + // + // Adafruit would use 11 MHz. + // I had lowered the frequency, so that `DmaBounce` could keep up. + .with_frequency(Rate::from_mhz(8)) // From Adafruit .with_clock_mode(ClockMode { polarity: Polarity::IdleLow, // From Adafruit phase: Phase::ShiftHigh, // From Adafruit diff --git a/firmware/acid-firmware/src/ui/dpi.rs b/firmware/acid-firmware/src/ui/dpi.rs index cda7ef4..e7b3564 100644 --- a/firmware/acid-firmware/src/ui/dpi.rs +++ b/firmware/acid-firmware/src/ui/dpi.rs @@ -13,7 +13,7 @@ use embassy_sync::{ channel::{Channel, TrySendError}, signal::Signal, }; -use embassy_time::Timer; +use embassy_time::{Instant, Timer}; use esp_alloc::MemoryCapability; use esp_hal::{ Blocking, @@ -38,7 +38,10 @@ use rmk::{ join_all, }; -use crate::{PSRAM_ALLOCATOR, SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, peripherals::st7701s::St7701s}; +use crate::{ + PSRAM_ALLOCATOR, SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, peripherals::st7701s::St7701s, + util::DurationExt, +}; /// THIS IS TAKEN FROM https://github.com/esp-rs/esp-hal/blob/main/esp-hal/src/soc/esp32s3/mod.rs /// Write back a specific range of data in the cache. @@ -82,7 +85,7 @@ pub struct DmaBounce { // TODO: Consider having a separate burst config for the two transfers. burst_config: BurstConfig, - // rx: DmaRxStreamBuf, + cyclic: bool, /// The size of each window. window_size: usize, /// The number of windows. @@ -113,6 +116,7 @@ impl DmaBounce { buffer_src: &'static mut [u8], window_size: usize, burst_config: BurstConfig, + cyclic: bool, ) -> Self { assert_eq!( buffer_src.len() % window_size, @@ -120,6 +124,7 @@ impl DmaBounce { "the size of a source buffer must be a multiple of the window size ({window_size} bytes), but it is {len} bytes large", len = buffer_src.len() ); + let windows_len = buffer_src.len() / window_size; let bounce_buffer_dst = Box::leak(allocate_dma_buffer_in(window_size, Global)); let bounce_buffer_src = Box::leak(allocate_dma_buffer_in(window_size, Global)); let src_descs = Self::linear_descriptors_for_buffer(window_size, burst_config, |desc| { @@ -130,23 +135,37 @@ impl DmaBounce { }); let bounce_dst_descs = Self::linear_descriptors_for_buffer(window_size, burst_config, |_| {}); - let bounce_src_descs = Self::bounce_descriptors_for_buffer( - unsafe { - ( - &mut *(bounce_buffer_dst as *mut _), - &mut *(bounce_buffer_src as *mut _), - ) - }, - burst_config, - ); + let bounce_src_descs = if cyclic { + Self::bounce_descriptors_for_buffer_single( + windows_len, + unsafe { + ( + &mut *(bounce_buffer_dst as *mut _), + &mut *(bounce_buffer_src as *mut _), + ) + }, + burst_config, + ) + } else { + Self::bounce_descriptors_for_buffer_cyclic( + unsafe { + ( + &mut *(bounce_buffer_dst as *mut _), + &mut *(bounce_buffer_src as *mut _), + ) + }, + burst_config, + ) + }; Self { channel, peripheral_src, peripheral_dst: Some(peripheral_dst), burst_config, + cyclic, window_size, - windows_len: buffer_src.len() / window_size, + windows_len, buffer_src, bounce_buffer_dst, bounce_buffer_src, @@ -190,7 +209,7 @@ impl DmaBounce { descriptors } - fn bounce_descriptors_for_buffer( + fn bounce_descriptors_for_buffer_cyclic( bounce_buffers: (&'static mut [u8], &'static mut [u8]), burst_config: BurstConfig, ) -> &'static mut [DmaDescriptor] { @@ -202,8 +221,13 @@ impl DmaBounce { let buffer_len = bounce_buffers.0.len(); let max_chunk_size = burst_config.max_compatible_chunk_size(); - let descriptors_len = dma::descriptor_count(buffer_len, max_chunk_size, false); - // TODO: This leaks memory. Ensure it's only called during setup. + let descriptors_len = dma::descriptor_count( + buffer_len, + max_chunk_size, + // TODO: This might need to be set to true? + // I don't know why cyclic descriptor lists must be at least 3 descriptors long. + false, + ); let descriptors_combined = Box::leak(vec![DmaDescriptor::EMPTY; 2 * descriptors_len].into_boxed_slice()); let descriptors_pair = descriptors_combined.split_at_mut(descriptors_len); @@ -246,6 +270,58 @@ impl DmaBounce { descriptors_combined } + fn bounce_descriptors_for_buffer_single( + windows_len: usize, + bounce_buffers: (&'static mut [u8], &'static mut [u8]), + burst_config: BurstConfig, + ) -> &'static mut [DmaDescriptor] { + assert_eq!( + bounce_buffers.0.len(), + bounce_buffers.1.len(), + "bounce buffers must be equal in size" + ); + // If an odd number of windows were needed, two descriptor lists would be needed, + assert_eq!(windows_len % 2, 0, "the number of windows must be even"); + + let buffer_len = bounce_buffers.0.len(); + let max_chunk_size = burst_config.max_compatible_chunk_size(); + let descriptors_per_window = dma::descriptor_count(buffer_len, max_chunk_size, false); + let descriptors_frame = Box::leak( + vec![DmaDescriptor::EMPTY; descriptors_per_window * windows_len].into_boxed_slice(), + ); + + // Link up the descriptors. + let mut next = core::ptr::null_mut(); + for desc in descriptors_frame.iter_mut().rev() { + desc.next = next; + next = desc; + } + + // Prepare each descriptor's buffer size. + let bounce_buffers = [bounce_buffers.0, bounce_buffers.1]; + + for (window_index, descriptors) in descriptors_frame + .chunks_mut(descriptors_per_window) + .enumerate() + { + let mut descriptors_it = descriptors.iter_mut(); + let mut remaining_bounce_buffer = &mut *bounce_buffers[window_index % 2]; + + while !remaining_bounce_buffer.is_empty() { + let chunk_size = core::cmp::min(max_chunk_size, remaining_bounce_buffer.len()); + let desc = descriptors_it.next().unwrap(); + desc.buffer = remaining_bounce_buffer.as_mut_ptr(); + remaining_bounce_buffer = &mut remaining_bounce_buffer[chunk_size..]; + let is_last = remaining_bounce_buffer.is_empty(); + desc.set_size(chunk_size); + desc.set_length(chunk_size); + desc.reset_for_tx(is_last); + } + } + + descriptors_frame + } + fn linear_descriptors_prepare( descriptors: &mut [DmaDescriptor], mut buffer: Option<&mut [u8]>, @@ -274,55 +350,10 @@ impl DmaBounce { // Enable interrupts for the peripheral 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(), - // ) - // .unwrap(); // Bind the handler unsafe { 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(), - // ); } // Enable interrupts in the peripheral. @@ -330,74 +361,19 @@ impl DmaBounce { .ch(channel_number) .out_int() .ena() - .modify(|_, w| { - // 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 - // }); - // SPI2::regs().dma_int_ena().modify(|_, w| { - // w.slv_rd_dma_done().bit(true); - // w.slv_wr_dma_done().bit(true); - // w.dma_seg_trans_done().bit(true); - // w.trans_done().bit(true); - // w - // }); - } - - fn print_regs() { - // 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} - eof: {out_eof} - done: {out_done} - in: - suc_eof: {in_suc_eof} - done: {in_done} - "}, - 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() - ); - } + .modify(|_, w| w.out_eof().bit(true)); } /// Receive a window of bytes into the current dst bounce buffer. /// Finally, swaps the bounce buffers. - async fn receive_window(&mut self) { + fn receive_window_blocking(&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), |_| {}); + Self::linear_descriptors_prepare(self.src_descs, Some(buffer_src_window), |desc| { + // desc.reset_for_tx(desc.next.is_null()); + }); // 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, @@ -425,15 +401,6 @@ impl DmaBounce { 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); } @@ -452,42 +419,22 @@ impl DmaBounce { Self::enable_interrupts(); // Receive the first window, so that the outbound transfer can read valid data. - self.receive_window().await; + self.receive_window_blocking(); let mut dma_tx_buffer = self.get_dma_tx_buffer(); - let transfer = match self + let mut transfer = self .peripheral_dst .take() .unwrap() - .send(true /* Send perpetually */, dma_tx_buffer) - { - Ok(transfer) => { - // let result; - // let peripheral_dst; - // (result, peripheral_dst, dma_tx_buffer) = transfer.wait(); - // self.peripheral_dst = Some(peripheral_dst); - - // if let Err(error) = result { - // error!("DPI error during sending: {error:?}"); - // } - warn!("Sending data to DPI!"); - transfer - } - Err(error_tuple) => { - let error; - let peripheral_dst; - (error, peripheral_dst, dma_tx_buffer) = error_tuple; - self.peripheral_dst = Some(peripheral_dst); - panic!("DPI error when starting transfer: {error:?}"); - } - }; + .send(self.cyclic /* Send perpetually */, dma_tx_buffer) + .unwrap_or_else(|(error, _, _)| { + panic!("failed to begin the transmission of the first frame: {error:?}"); + }); let mut windows_skipped_total = 0; loop { - // warn!("Iteration. Done = {}", transfer.is_done()); - self.receive_window().await; - // let windows_sent = BOUNCE_BUFFER_SENT.receive().await; + self.receive_window_blocking(); let windows_skipped = WINDOWS_SKIPPED.wait().await; if windows_skipped > 0 { @@ -498,6 +445,38 @@ impl DmaBounce { 100.0 * windows_skipped_total as f32 / (self.frame_index_next + 1) as f32 ); } + + if !self.cyclic && self.window_index_next == 1 { + // TODO: Investigate why the DPI transfer isn't done at this point. + // The `DpiTransfer::wait()` below takes 0.001039 s. + // Perhaps it's the minimum screen refresh period? + // + // assert!(transfer.is_done()); + // if !transfer.is_done() { + // error!( + // "transfer is not done yet. {} {}", + // self.frame_index_next, self.window_index_next + // ); + // } + + let result; + let peripheral_dst; + // let start = Instant::now(); + (result, peripheral_dst, dma_tx_buffer) = transfer.wait(); + // let duration = Instant::now().duration_since(start); + // warn!("Waited for {} seconds", duration.display_as_secs()); + + if let Err(error) = result { + error!("DPI error during sending: {error:?}"); + } + + transfer = + peripheral_dst + .send(false, dma_tx_buffer) + .unwrap_or_else(|(error, _, _)| { + panic!("failed to begin the transmission of a frame: {error:?}"); + }); + } } // loop { @@ -568,10 +547,6 @@ 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)] @@ -588,45 +563,7 @@ fn dma_interrupt_handler() { .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 }; - - // // Check and clear VSYNC interrupt - // if lcd_cam - // .lc_dma_int_raw() - // .read() - // .lcd_vsync_int_raw() - // .bit_is_set() - // { - // lcd_cam - // .lc_dma_int_clr() - // .write(|w| w.lcd_vsync_int_clr().set_bit()); - - // INT_CHANNEL.send(); - // // VSYNC_SIGNAL.signal(()); - - // // Signal the event - // // critical_section::with(|cs| { - // // *VSYNC_FLAG.borrow_ref_mut(cs) = true; - // // }); - // } } pub async fn run_lcd(