diff --git a/firmware/acid-firmware/src/main.rs b/firmware/acid-firmware/src/main.rs index b916c0c..49549be 100644 --- a/firmware/acid-firmware/src/main.rs +++ b/firmware/acid-firmware/src/main.rs @@ -152,78 +152,80 @@ 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"); - 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::(); + const BYTES_PER_PIXEL: usize = core::mem::size_of::(); + // Assume highest burst config setting. + const EXTERNAL_BURST_CONFIG: ExternalBurstConfig = ExternalBurstConfig::Size32; + const ALIGNMENT_PIXELS: usize = EXTERNAL_BURST_CONFIG as usize / BYTES_PER_PIXEL; + // The total number of pixels demanded by the DPI, per row. + const WIDTH_TOTAL_PIXELS: usize = 368; + // The total number of rows demanded by the DPI, per frame. + const HEIGHT_PIXELS: usize = 960; + // The number of unused pixels at the start of the row. + const FRONT_PORCH_ACTUAL_PIXELS: usize = 120; + // The number of actually visible pixels, per row. + const WIDTH_VISIBLE_PIXELS: usize = 240; + // The number of pixels not stored in a bounce buffer, per row. + // This many arbitrary pixels are sent to the DPI. + const FRONT_PORCH_SKIPPED_PIXELS: usize = + (FRONT_PORCH_ACTUAL_PIXELS / ALIGNMENT_PIXELS) * ALIGNMENT_PIXELS; + const WIDTH_STORED_PIXELS: usize = WIDTH_TOTAL_PIXELS - FRONT_PORCH_SKIPPED_PIXELS; + const VISIBLE_OFFSET_IN_BUFFER_PIXELS: usize = + FRONT_PORCH_ACTUAL_PIXELS - FRONT_PORCH_SKIPPED_PIXELS; + const ROWS_PER_WINDOW: usize = 16; let burst_config = BurstConfig { internal_memory: InternalBurstConfig::Enabled, - external_memory: ExternalBurstConfig::Size64, + external_memory: EXTERNAL_BURST_CONFIG, }; let buffer_src = Box::leak(allocate_dma_buffer_in( - windows_len * window_size, + HEIGHT_PIXELS * WIDTH_STORED_PIXELS * BYTES_PER_PIXEL, burst_config, &PSRAM_ALLOCATOR, )); - let buffer_src = bytemuck::cast_slice_mut::(buffer_src); - let colors = (0..120_u8) - .rev() - .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 % WIDTH) as i16 - 120; - let mut y = (index / WIDTH) 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); + { + let buffer_src = bytemuck::cast_slice_mut::(buffer_src); + let colors = (0..WIDTH_VISIBLE_PIXELS as u8 / 2) + .rev() + .map(|val| Rgb565Pixel::from_rgb(0xFF, val * 2, 0)) + .collect::>(); + for (index, pixel) in buffer_src.iter_mut().enumerate() { + let mut x = + (index % WIDTH_STORED_PIXELS) as i16 - VISIBLE_OFFSET_IN_BUFFER_PIXELS as i16; + let mut y = (index / WIDTH_STORED_PIXELS) as i16; - *pixel = colors[min as usize % colors.len()].clone(); - continue; + if x < WIDTH_VISIBLE_PIXELS as i16 { + x = core::cmp::min(x, WIDTH_VISIBLE_PIXELS as i16 - 1 - x); + y = core::cmp::min(y, HEIGHT_PIXELS as i16 - 1 - y); + let min = core::cmp::min(x, y); + + *pixel = colors[min as usize % colors.len()].clone(); + continue; + } + + *pixel = Rgb565Pixel::default(); } - - *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 - // }); + + warn!("FRONT_PORCH_SKIPPED_PIXELS: {FRONT_PORCH_SKIPPED_PIXELS}"); + warn!("WIDTH_STORED_PIXELS: {WIDTH_STORED_PIXELS}"); + warn!("ROWS_PER_WINDOW: {ROWS_PER_WINDOW}"); + let mut buf = DmaBounce::new( Global, channel, AnySpi::from(peripheral), st7701s.dpi, buffer_src, - window_size, - BurstConfig { - internal_memory: InternalBurstConfig::Enabled, - external_memory: ExternalBurstConfig::Size64, - }, + FRONT_PORCH_SKIPPED_PIXELS * BYTES_PER_PIXEL, + WIDTH_STORED_PIXELS * BYTES_PER_PIXEL, + ROWS_PER_WINDOW, + burst_config, false, ); - let _ = buf.send().await; + buf.send().await; error!("TEST BOUNCE BUFFERS SECTION DONE"); - loop { - Timer::after_secs(10).await; - } } #[esp_rtos::main] @@ -598,16 +600,8 @@ async fn main_task(peripherals: MainPeripherals) { info!("ST7701S-based LCD display initialized!"); - // interrupt_core_0_spawner.must_spawn(test_bounce_buffers_task( - // peripherals.DMA_CH0, - // peripherals.SPI2, - // st7701s, - // )); - - // let lcd_task = test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2, st7701s); - - // let _ = lcd_task.await; - // return; + test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2, st7701s).await; + return; // RMK config let vial_config = VialConfig::new(VIAL_KEYBOARD_ID, VIAL_KEYBOARD_DEF, &[(0, 0), (1, 1)]); @@ -705,7 +699,8 @@ async fn main_task(peripherals: MainPeripherals) { // TODO: Compute the appropriate ranges to pass to the renderer and DPI peripheral. // The renderer should pass the size of the `pad`ding to the GUI is parameters, // to align the content to the displayed range. - 368, + 112, + 368 - 112, 960, 16, false, diff --git a/firmware/acid-firmware/src/ui/dpi.rs b/firmware/acid-firmware/src/ui/dpi.rs index 6eb2ec0..ecdc402 100644 --- a/firmware/acid-firmware/src/ui/dpi.rs +++ b/firmware/acid-firmware/src/ui/dpi.rs @@ -30,7 +30,7 @@ use esp_hal::{ spi::master::AnySpi, }; use esp_sync::RawMutex; -use i_slint_core::software_renderer::Rgb565Pixel; +use i_slint_core::software_renderer::{Rgb565Pixel, TargetPixel}; use indoc::{formatdoc, indoc}; use log::{error, info, warn}; use rmk::{ @@ -111,16 +111,30 @@ pub struct DmaBounce { } impl DmaBounce { + /// * `allocator` - The allocator used to allocate the bounce buffers. + /// * `channel` - The DMA channel used to transfer data from the source buffer to the bounce buffers. + /// * `peripheral_src` - The peripheral to transfer data from the source buffer to the bounce buffers. + /// * `peripheral_dst` - The peripheral to transfer data to, from the bounce buffers. + /// * `buffer_src` - The source buffer, typically allocated in external memory. + /// * `row_front_porch_bytes` - The number of arbitrary-valued bytes to be sent in front of each row to the destination peripheral. + /// * `row_width_bytes` - The width of a row, in bytes. + /// * `window_size_rows` - The size of a single bounce buffer, in rows. + /// * `burst_config` - The burst config to use for memory transfers (both in and out). TODO: This could be split. + /// * `cyclic` - Experimental! Whether to use a cyclic descriptor list for transfer from the bounce buffers to the destination peripheral. pub fn new( allocator: impl Allocator + Copy + 'static, channel: DMA_CH0<'static>, peripheral_src: AnySpi<'static>, peripheral_dst: Dpi<'static, Blocking>, buffer_src: &'static mut [u8], - window_size: usize, + row_front_porch_bytes: usize, + row_width_bytes: usize, + window_size_rows: usize, burst_config: BurstConfig, cyclic: bool, ) -> Self { + let window_size = row_width_bytes * window_size_rows; + assert_eq!( buffer_src.len() % window_size, 0, @@ -137,9 +151,21 @@ impl DmaBounce { "the source buffer must be sufficiently aligned to {alignment} bytes for the burst config", ); assert_eq!( - window_size % alignment, + row_width_bytes % alignment, 0, - "the size of the source buffer must be sufficiently aligned to {alignment} bytes for the burst config", + "the size of a row in bytes must be sufficiently aligned to {alignment} bytes for the burst config", + ); + assert_eq!( + row_front_porch_bytes % alignment, + 0, + "the size of a row's front porch in bytes must be sufficiently aligned to {alignment} bytes for the burst config", + ); + // We need to make the destination peripheral read the front porch data from somewhere, + // and that somewhere is currently the bounce buffer. + // Therefore the front porch must be in bounds. + assert!( + row_front_porch_bytes <= window_size, + "front porch too large" ); let windows_len = buffer_src.len() / window_size; @@ -158,8 +184,7 @@ impl DmaBounce { let bounce_dst_descs = Self::linear_descriptors_for_buffer(window_size, burst_config, |_| {}); let bounce_src_descs = if cyclic { - Self::bounce_descriptors_for_buffer_single( - windows_len, + Self::bounce_descriptors_for_buffer_cyclic( unsafe { ( &mut *(bounce_buffer_dst as *mut _), @@ -169,7 +194,11 @@ impl DmaBounce { burst_config, ) } else { - Self::bounce_descriptors_for_buffer_cyclic( + Self::bounce_descriptors_for_buffer_single( + windows_len, + row_front_porch_bytes, + row_width_bytes, + window_size_rows, unsafe { ( &mut *(bounce_buffer_dst as *mut _), @@ -294,6 +323,9 @@ impl DmaBounce { fn bounce_descriptors_for_buffer_single( windows_len: usize, + row_front_porch_bytes: usize, + row_width_bytes: usize, + window_size_rows: usize, bounce_buffers: (&'static mut [u8], &'static mut [u8]), burst_config: BurstConfig, ) -> &'static mut [DmaDescriptor] { @@ -306,12 +338,28 @@ impl DmaBounce { 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(), + + assert_eq!( + buffer_len, + row_width_bytes * window_size_rows, + "the provided bounce buffers have an invalid size" ); + warn!( + "windows_len: {windows_len}\nrow_front_porch_bytes: {row_front_porch_bytes}\nrow_width_bytes: {row_width_bytes}\nwindow_size_rows: {window_size_rows}\nbuffer_len: {buffer_len}", + ); + + let max_chunk_size = burst_config.max_compatible_chunk_size(); + let descriptors_per_row_front_porch = + dma::descriptor_count(row_front_porch_bytes, max_chunk_size, false); + let descriptors_per_row_stored = + dma::descriptor_count(row_width_bytes, max_chunk_size, false); + let descriptors_per_row = descriptors_per_row_stored + descriptors_per_row_front_porch; + let descriptors_per_window = window_size_rows * descriptors_per_row; + let descriptors_per_frame = descriptors_per_window * windows_len; + let descriptors_frame = + Box::leak(vec![DmaDescriptor::EMPTY; descriptors_per_frame].into_boxed_slice()); + // Link up the descriptors. let mut next = core::ptr::null_mut(); for desc in descriptors_frame.iter_mut().rev() { @@ -322,25 +370,109 @@ impl DmaBounce { // Prepare each descriptor's buffer size. let bounce_buffers = [bounce_buffers.0, bounce_buffers.1]; - for (window_index, descriptors) in descriptors_frame + for (window_index, descriptors_window) 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]; + let bounce_buffer_index = window_index % 2; + let bounce_buffer = &mut *bounce_buffers[bounce_buffer_index]; + // let bounce_buffer_ptr = bounce_buffers[bounce_buffer_index].as_mut_ptr(); + // let mut remaining_bounce_buffer = &mut *bounce_buffers[bounce_buffer_index]; - 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); + for (row_index_in_window, descriptors_row) in descriptors_window + .chunks_mut(descriptors_per_row) + .enumerate() + { + // let row_index = row_index_in_window + window_index * window_size_rows; + let (descriptors_row_front_porch, descriptors_row_stored) = + descriptors_row.split_at_mut(descriptors_per_row_front_porch); + + // Prepare front porch descriptors. + { + let mut descriptors_it = descriptors_row_front_porch.iter_mut(); + let mut remaining_front_porch = row_front_porch_bytes; + + while remaining_front_porch > 0 { + let desc = descriptors_it.next().unwrap(); + let chunk_size = core::cmp::min(max_chunk_size, remaining_front_porch); + remaining_front_porch -= chunk_size; + // Just make it point at a bounce buffer. + // It is guaranteed to have enough bytes by `DmaBounce::new`. + desc.buffer = bounce_buffer.as_mut_ptr(); + desc.set_size(chunk_size); + desc.set_length(chunk_size); + desc.reset_for_tx(false); + } + + assert!( + descriptors_it.next().is_none(), + "front porch descriptors must be used up" + ); + assert_eq!( + descriptors_row_front_porch + .iter() + .map(|desc| desc.size()) + .sum::(), + row_front_porch_bytes + ); + } + + // Prepare window descriptors. + { + let mut remaining_bounce_buffer = &mut bounce_buffer + [row_index_in_window * row_width_bytes..][..row_width_bytes]; + + // if remaining_bounce_buffer.len() > row_width_bytes { + // remaining_bounce_buffer = &mut remaining_bounce_buffer[..row_width_bytes]; + // } + + for desc in &mut *descriptors_row_stored { + let chunk_size = + core::cmp::min(max_chunk_size, remaining_bounce_buffer.len()); + desc.buffer = remaining_bounce_buffer.as_mut_ptr(); + remaining_bounce_buffer = &mut remaining_bounce_buffer[chunk_size..]; + desc.set_size(chunk_size); + desc.set_length(chunk_size); + desc.reset_for_tx(false); + } + + assert!( + remaining_bounce_buffer.is_empty(), + "bounce buffer must be used up" + ); + assert_eq!( + descriptors_row_stored + .iter() + .map(|desc| desc.size()) + .sum::(), + row_width_bytes + ); + } } + + // Set EOF bit on the last descriptor of the window, to signal + // that the bounce buffer is done being read from. + if let Some(last_desc) = descriptors_window.last_mut() { + last_desc.reset_for_tx(true); + } + + assert_eq!( + descriptors_window + .iter() + .map(|desc| desc.size()) + .sum::(), + window_size_rows * (row_front_porch_bytes + row_width_bytes) + ); } + assert_eq!( + descriptors_frame + .iter() + .map(|desc| desc.size()) + .sum::(), + windows_len * window_size_rows * (row_front_porch_bytes + row_width_bytes) + ); + descriptors_frame } @@ -482,7 +614,7 @@ impl DmaBounce { .with_timeout(Duration::from_millis(100)) .await .unwrap_or_else(|_| { - // error!("Timed out when waiting for skipped windows."); + error!("Timed out when waiting for skipped windows."); 0 // TODO: This should be -1 to repeat the same window. }); @@ -506,7 +638,8 @@ impl DmaBounce { 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 + // 100.0 * windows_skipped_total as f32 + // / (self.windows_len * (self.frame_index_next + 1)) as f32 // ); } @@ -569,8 +702,8 @@ impl DmaBounce { direction: dma::TransferDirection::Out, accesses_psram: false, burst_transfer: self.burst_config, - check_owner: Some(true), // Possibly want to set this to false - auto_write_back: false, // Possibly true + check_owner: Some(false), // Possibly want to set this to false + auto_write_back: false, // Possibly true }, } } @@ -733,13 +866,14 @@ impl Framebuffer { channel: DMA_CH0<'static>, peripheral_src: AnySpi<'static>, peripheral_dst: Dpi<'static, Blocking>, - width: u32, - height: u32, + front_porch_pixels: u32, + width_pixels: u32, + height_pixels: u32, rows_per_window: usize, cyclic: bool, ) -> Self { - let buffer_size = width as usize * height as usize * core::mem::size_of::(); - let window_size = rows_per_window * width as usize * core::mem::size_of::(); + const BYTES_PER_PIXEL: usize = core::mem::size_of::(); + let buffer_size = width_pixels as usize * height_pixels as usize * BYTES_PER_PIXEL; let burst_config = BurstConfig { internal_memory: InternalBurstConfig::Enabled, external_memory: ExternalBurstConfig::Size64, @@ -755,14 +889,16 @@ impl Framebuffer { peripheral_src, peripheral_dst, psram_buffer, - window_size, + front_porch_pixels as usize * BYTES_PER_PIXEL, + width_pixels as usize * BYTES_PER_PIXEL, + rows_per_window, burst_config, cyclic, ); Self { - width, - height, + width: width_pixels, + height: height_pixels, bounce_buffers, } }