From dde67e46fe26737682e659bf961dff4c6a243572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hlusi=C4=8Dka?= Date: Mon, 23 Feb 2026 23:08:27 +0100 Subject: [PATCH] WIP: Higher prio --- firmware/acid-firmware/Cargo.toml | 1 + firmware/acid-firmware/src/logging.rs | 4 +- firmware/acid-firmware/src/main.rs | 37 +- .../src/peripherals/st7701s/mod.rs | 2 +- firmware/acid-firmware/src/ui/dpi.rs | 367 +++++++++++------- firmware/acid-firmware/src/ui/mod.rs | 9 +- 6 files changed, 269 insertions(+), 151 deletions(-) diff --git a/firmware/acid-firmware/Cargo.toml b/firmware/acid-firmware/Cargo.toml index f32166c..1fbda72 100644 --- a/firmware/acid-firmware/Cargo.toml +++ b/firmware/acid-firmware/Cargo.toml @@ -86,6 +86,7 @@ tinyvec = { version = "1.10.0", default-features = false, features = ["alloc"] } esp-metadata-generated = { version = "0.3.0", features = ["esp32s3"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } indoc = "2.0.7" +ouroboros = "0.18.5" # A fork of slint with patches for `allocator_api` support. # Don't forget to change `slint-build` in build dependencies, if this is changed. diff --git a/firmware/acid-firmware/src/logging.rs b/firmware/acid-firmware/src/logging.rs index 0a5c184..6fa6a61 100644 --- a/firmware/acid-firmware/src/logging.rs +++ b/firmware/acid-firmware/src/logging.rs @@ -4,7 +4,9 @@ use log::LevelFilter; pub const LOG_LEVEL_FILTER: LevelFilter = { if let Some(string) = option_env!("ESP_LOG") { - if string.eq_ignore_ascii_case("ERROR") { + if string.eq_ignore_ascii_case("OFF") { + LevelFilter::Off + } else if string.eq_ignore_ascii_case("ERROR") { LevelFilter::Error } else if string.eq_ignore_ascii_case("WARN") { LevelFilter::Warn diff --git a/firmware/acid-firmware/src/main.rs b/firmware/acid-firmware/src/main.rs index ce0c9e0..04c9591 100644 --- a/firmware/acid-firmware/src/main.rs +++ b/firmware/acid-firmware/src/main.rs @@ -32,7 +32,7 @@ use alloc::vec::Vec; use cfg_if::cfg_if; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::flash::partition::Partition; -use embassy_executor::Spawner; +use embassy_executor::{SendSpawner, Spawner, SpawnerTraceExt}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; use embassy_sync::mutex::Mutex; @@ -224,7 +224,7 @@ async fn test_bounce_buffers( burst_config, true, ); - buf.send().await; + buf.launch_interrupt_driven_task().await; error!("TEST BOUNCE BUFFERS SECTION DONE"); } @@ -321,11 +321,17 @@ async fn main(_spawner: Spawner) { static EXECUTOR_CORE_0: StaticCell> = StaticCell::new(); let executor_core_0 = InterruptExecutor::new(software_interrupt.software_interrupt2); let executor_core_0 = EXECUTOR_CORE_0.init(executor_core_0); - let interrupt_core_0_spawner = executor_core_0.start(interrupt::Priority::Priority3); + let interrupt_core_0_spawner = executor_core_0.start(interrupt::Priority::Priority1); + + // static EXECUTOR_CORE_1: StaticCell> = StaticCell::new(); + // let executor_core_1 = InterruptExecutor::new(software_interrupt.software_interrupt3); + // let executor_core_1 = EXECUTOR_CORE_1.init(executor_core_1); + // let interrupt_core_1_spawner = executor_core_1.start(interrupt::Priority::Priority2); info!("ESP-RTOS started!"); let main_task_peripherals = MainPeripherals { + // high_priority_task_spawner: interrupt_core_1_spawner, uart_rx, software_interrupt1: software_interrupt.software_interrupt1, RNG: peripherals.RNG, @@ -376,6 +382,7 @@ async fn main(_spawner: Spawner) { /// Peripherals passed to the main task. #[allow(non_snake_case)] struct MainPeripherals { + // high_priority_task_spawner: SendSpawner, uart_rx: UartRx<'static, Blocking>, software_interrupt1: SoftwareInterrupt<'static, 1>, RNG: esp_hal::peripherals::RNG<'static>, @@ -706,8 +713,8 @@ async fn main_task(peripherals: MainPeripherals) { 112, 368 - 112, 960, - 16, - false, + 8, + true, )); info!("Framebuffer created!"); @@ -751,6 +758,19 @@ async fn main_task(peripherals: MainPeripherals) { info!("Awaiting on all tasks..."); + // let spawner = unsafe { Spawner::for_current_executor() }.await; + + // peripherals + // .high_priority_task_spawner + // .must_spawn(run_bounce_buffers(framebuffer)); + + framebuffer + .bounce_buffers + .take() + .unwrap() + .launch_interrupt_driven_task() + .await; + // TODO: Probably want to select! instead and re-try. join_all![ run_alloc_stats_reporter(), @@ -762,7 +782,7 @@ async fn main_task(peripherals: MainPeripherals) { // warn!("Waited."); // framebuffer.bounce_buffers.send().await; // }, - framebuffer.bounce_buffers.send(), + // framebuffer.bounce_buffers.send(), // ui::dpi::run_lcd(st7701s, framebuffer), // lcd_task, run_devices! ( @@ -785,6 +805,11 @@ async fn main_task(peripherals: MainPeripherals) { .await; } +#[embassy_executor::task] +async fn run_bounce_buffers(framebuffer: &'static mut Framebuffer) { + framebuffer.bounce_buffers.as_mut().unwrap().send().await; +} + async fn run_alloc_stats_reporter() { let mut psram_used_prev = 0; let mut heap_used_prev = 0; diff --git a/firmware/acid-firmware/src/peripherals/st7701s/mod.rs b/firmware/acid-firmware/src/peripherals/st7701s/mod.rs index 604e48b..221538b 100644 --- a/firmware/acid-firmware/src/peripherals/st7701s/mod.rs +++ b/firmware/acid-firmware/src/peripherals/st7701s/mod.rs @@ -1276,7 +1276,7 @@ where // // Adafruit would use 11 MHz. // I had lowered the frequency, so that `DmaBounce` could keep up. - .with_frequency(Rate::from_mhz(7)) // From Adafruit + .with_frequency(Rate::from_mhz(5)) // 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 cf0339e..a43df86 100644 --- a/firmware/acid-firmware/src/ui/dpi.rs +++ b/firmware/acid-firmware/src/ui/dpi.rs @@ -1,5 +1,6 @@ use core::{ alloc::Layout, + cell::UnsafeCell, pin::Pin, sync::atomic::{self, AtomicBool, AtomicU32, AtomicUsize}, }; @@ -20,7 +21,7 @@ use esp_hal::{ dma::{ self, AnyGdmaChannel, BufView, BurstConfig, DmaChannel, DmaChannelConvert, DmaDescriptor, DmaDescriptorFlags, DmaEligible, DmaRxStreamBuf, DmaTxBuf, DmaTxBuffer, DmaTxInterrupt, - ExternalBurstConfig, InternalBurstConfig, Mem2Mem, SimpleMem2MemTransfer, + ExternalBurstConfig, InternalBurstConfig, Mem2Mem, SimpleMem2Mem, SimpleMem2MemTransfer, }, dma_descriptors, handler, interrupt::{self, Priority}, @@ -33,8 +34,9 @@ use esp_sync::RawMutex; use i_slint_core::software_renderer::{Rgb565Pixel, TargetPixel}; use indoc::{formatdoc, indoc}; use log::{error, info, warn}; +use ouroboros::self_referencing; use rmk::{ - futures::{FutureExt, pin_mut}, + futures::{self, FutureExt, pin_mut}, join_all, }; @@ -76,6 +78,14 @@ pub unsafe fn cache_invalidate_addr(addr: u32, size: u32) { const DMA_CHANNEL_OUTBOUND: usize = 2; const INTERRUPT_OUTBOUND: Interrupt = Interrupt::DMA_OUT_CH2; +#[self_referencing] +struct ReceivingTransfer { + mem2mem: SimpleMem2Mem<'static, Blocking>, + #[borrows(mut mem2mem)] + #[covariant] + transfer: Option>, +} + pub struct DmaBounce { // TODO: Make these generic. // They currently cannot be generic, because they lacks a `reborrow` method. @@ -84,6 +94,8 @@ pub struct DmaBounce { peripheral_src: AnySpi<'static>, // This can also be more generic, see `DmaEligible` in `Mem2Mem::new`. peripheral_dst: Option>, + // TODO: Combine with peripheral_dst using an enum? + transfer_dst: Option>, // TODO: Consider having a separate burst config for the two transfers. burst_config: BurstConfig, @@ -105,9 +117,11 @@ 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], + descriptors_per_window: usize, // The index of the next window about to be received into the destination bounce buffer. window_index_next: usize, frame_index_next: usize, + receiving_transfer: Option, } impl DmaBounce { @@ -183,39 +197,26 @@ 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_cyclic( - row_front_porch_bytes, - row_width_bytes, - window_size_rows, - unsafe { - ( - &mut *(bounce_buffer_dst as *mut _), - &mut *(bounce_buffer_src as *mut _), - ) - }, - burst_config, - ) - } else { - 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 _), - &mut *(bounce_buffer_src as *mut _), - ) - }, - burst_config, - ) - }; + let (bounce_src_descs, descriptors_per_window) = Self::bounce_descriptors_for_buffer( + windows_len, + row_front_porch_bytes, + row_width_bytes, + window_size_rows, + unsafe { + ( + &mut *(bounce_buffer_dst as *mut _), + &mut *(bounce_buffer_src as *mut _), + ) + }, + burst_config, + cyclic, + ); Self { channel, peripheral_src, peripheral_dst: Some(peripheral_dst), + transfer_dst: None, burst_config, cyclic, window_size, @@ -226,8 +227,10 @@ impl DmaBounce { src_descs, bounce_dst_descs, bounce_src_descs, + descriptors_per_window, window_index_next: 0, frame_index_next: 0, + receiving_transfer: None, } } @@ -358,81 +361,15 @@ impl DmaBounce { ); } - fn bounce_descriptors_for_buffer_cyclic( - 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] { - assert_eq!( - bounce_buffers.0.len(), - bounce_buffers.1.len(), - "bounce buffers must be equal in size" - ); - - let buffer_len = bounce_buffers.0.len(); - - assert_eq!( - buffer_len, - row_width_bytes * window_size_rows, - "the provided bounce buffers have an invalid size" - ); - - 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_combined = - Box::leak(vec![DmaDescriptor::EMPTY; 2 * descriptors_per_window].into_boxed_slice()); - let descriptors_pair = descriptors_combined.split_at_mut(descriptors_per_window); - - // Link up the descriptors. - fn link_up_descriptors( - descriptors: &mut [DmaDescriptor], - descriptors_other: &mut [DmaDescriptor], - ) { - let mut next = descriptors_other.first_mut().unwrap(); - for desc in descriptors.iter_mut().rev() { - desc.next = next; - next = desc; - } - } - - link_up_descriptors(descriptors_pair.0, descriptors_pair.1); - link_up_descriptors(descriptors_pair.1, descriptors_pair.0); - - // Prepare each descriptor's buffer size. - for (bounce_buffer, descriptors) in [ - (bounce_buffers.0, descriptors_pair.0), - (bounce_buffers.1, descriptors_pair.1), - ] { - Self::prepare_descriptors_window( - bounce_buffer, - descriptors, - row_front_porch_bytes, - row_width_bytes, - window_size_rows, - max_chunk_size, - descriptors_per_row, - descriptors_per_row_front_porch, - ); - } - - descriptors_combined - } - - fn bounce_descriptors_for_buffer_single( + fn bounce_descriptors_for_buffer( 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] { + cyclic: bool, + ) -> (&'static mut [DmaDescriptor], usize) { assert_eq!( bounce_buffers.0.len(), bounce_buffers.1.len(), @@ -449,6 +386,15 @@ impl DmaBounce { "the provided bounce buffers have an invalid size" ); + // Implementation note: + // A cyclic descriptor could consist of just a set of descriptors per window, + // so two sets in total, because there are two bounce buffers. + // However, we can also access the pointer of the EOF descriptor within the + // EOF interrupt handler, which lets us compute which descriptor generated that + // interrupt. + // This is useful in the case when an interrupt is missed. Then the number of interrupts + // handled doesn't correspond to the number of windows sent to the destination peripheral. + // In that case, the number of windows sent can be computed from the address of the descriptor. 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); @@ -459,9 +405,15 @@ impl DmaBounce { let descriptors_per_frame = descriptors_per_window * windows_len; let descriptors_frame = Box::leak(vec![DmaDescriptor::EMPTY; descriptors_per_frame].into_boxed_slice()); + let descriptors_frame_ptr = descriptors_frame.as_ptr(); // Link up the descriptors. - let mut next = core::ptr::null_mut(); + let mut next = if cyclic { + descriptors_frame.first_mut().unwrap() as *mut _ + } else { + core::ptr::null_mut() + }; + for desc in descriptors_frame.iter_mut().rev() { desc.next = next; next = desc; @@ -497,7 +449,7 @@ impl DmaBounce { windows_len * window_size_rows * (row_front_porch_bytes + row_width_bytes) ); - descriptors_frame + (descriptors_frame, descriptors_per_window) } fn linear_descriptors_prepare( @@ -549,6 +501,61 @@ impl DmaBounce { .modify(|_, w| w.out_eof().bit(true)); } + /// Receive a window of bytes into the current dst bounce buffer. + /// Finally, swaps the bounce buffers. + /// + /// # Safety: + /// TODO + unsafe fn receive_window_start(&mut self) -> ReceivingTransfer { + // 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), |_desc| { + // No need to call `DmaDescriptor::reset_for_tx`, because + // 1. we don't rely on the ownership flag; + // 2. the EOF flag is already set during the construction of this buffer. + }); + // 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: &'static mut [DmaDescriptor] = + unsafe { &mut *(self.bounce_dst_descs as *mut _) }; + let src_descs: &'static mut [DmaDescriptor] = unsafe { &mut *(self.src_descs as *mut _) }; + + let mem2mem = unsafe { + Mem2Mem::new( + self.channel.clone_unchecked(), + self.peripheral_src.clone_unchecked(), + ) + } + .with_descriptors(bounce_dst_descs, src_descs, self.burst_config) + .unwrap(); + + ReceivingTransferBuilder { + mem2mem, + transfer_builder: |mem2mem| { + Some( + mem2mem + .start_transfer(&mut self.bounce_buffer_dst, buffer_src_window) + .unwrap(), + ) + }, + } + .build() + } + /// Receive a window of bytes into the current dst bounce buffer. /// Finally, swaps the bounce buffers. async fn receive_window(&mut self) { @@ -595,14 +602,39 @@ impl DmaBounce { self.increase_window_counter(1); } - fn increase_window_counter(&mut self, windows: usize) { - if windows % 2 == 1 { + fn increase_window_counter(&mut self, windows: isize) { + if windows.rem_euclid(2) == 1 { core::mem::swap(&mut self.bounce_buffer_dst, &mut self.bounce_buffer_src); } - 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; + let window_index_next = self.window_index_next as isize + windows; + self.frame_index_next = (self.frame_index_next as isize + + window_index_next / self.windows_len as isize) + as usize; + self.window_index_next = window_index_next.rem_euclid(self.windows_len as isize) as usize; + } + + pub async fn launch_interrupt_driven_task(mut self) { + Self::enable_interrupts(); + + // Receive the first 2 windows, so that the outbound transfer can read valid data. + self.receive_window().await; + + let dma_tx_buffer = self.get_dma_tx_buffer(); + let transfer = self + .peripheral_dst + .take() + .unwrap() + .send(self.cyclic /* Send perpetually */, dma_tx_buffer) + .unwrap_or_else(|(error, _, _)| { + panic!("failed to begin the transmission of the first frame: {error:?}"); + }); + self.transfer_dst = Some(transfer); + self.receiving_transfer = Some(unsafe { self.receive_window_start() }); + + unsafe { + *DMA_STATE.0.get() = Some(self); + } } pub async fn send(&mut self) { @@ -633,31 +665,17 @@ impl DmaBounce { // "Window received: {} {}", // self.window_index_next, self.frame_index_next // ); - let windows_skipped = WINDOWS_SKIPPED + let windows_sent = WINDOWS_SENT .wait() .with_timeout(Duration::from_millis(100)) .await .unwrap_or_else(|_| { error!("Timed out when waiting for skipped windows."); - 0 // TODO: This should be -1 to repeat the same window. + 0 }); + let windows_skipped = windows_sent as isize - 1; - // let windows_skipped = match windows_skipped { - // Ok(windows_skipped) => windows_skipped, - // Err(_) => { - // warn!( - // "Waiting for skipped windows timed out. Transfer done: {}", - // transfer.is_done() - // ); - // if transfer.is_done() { - // let (result, _, _) = transfer.wait(); - // panic!("Transfer result: {result:?}"); - // } - // 0 - // } - // }; - - if windows_skipped > 0 { + if windows_skipped != 0 { self.increase_window_counter(windows_skipped); windows_skipped_total += windows_skipped; // error!( @@ -674,7 +692,9 @@ impl DmaBounce { if !self.cyclic && (self.window_index_next == 1 || transfer.is_done()) { if self.window_index_next > 1 { - self.increase_window_counter(self.windows_len - self.window_index_next + 1); + self.increase_window_counter( + self.windows_len as isize - self.window_index_next as isize + 1, + ); } else if self.window_index_next == 0 { self.increase_window_counter(1); } @@ -763,25 +783,94 @@ unsafe impl DmaTxBuffer for DmaTxBounceBuf { /// Intended to be listened on by the renderer, to synchronize the refresh frequency with. pub static FRAMES_SKIPPED: Signal = Signal::new(); -static WINDOWS_SKIPPED: Signal = Signal::new(); -// static INBOUND_TRANSFER_FINISHED: Signal = Signal::new(); +static WINDOWS_SENT: Signal = Signal::new(); +static DMA_STATE: SyncUnsafeCell> = SyncUnsafeCell(UnsafeCell::new(None)); + +#[repr(transparent)] +pub struct SyncUnsafeCell(UnsafeCell); + +unsafe impl Sync for SyncUnsafeCell {} + +// #[derive(Clone, Copy)] +// struct DmaState { +// descriptors_ptr: *const DmaDescriptor, +// descriptors_per_window: usize, +// windows_per_frame: usize, +// last_window_index: usize, +// } + +// unsafe impl Sync for DmaState {} #[handler(priority = Priority::Priority3)] #[ram] // Improves performance. fn dma_outbound_interrupt_handler() { let interrupt = DMA::regs().ch(DMA_CHANNEL_OUTBOUND).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)); + let bounce_buffer_sent = interrupt.st().read().out_eof().bit_is_set(); - WINDOWS_SKIPPED.signal( - WINDOWS_SKIPPED - .try_take() - .map(|windows_skipped| windows_skipped + 1) - .unwrap_or_default(), + if !bounce_buffer_sent { + return; + } + + // Clear the bit by writing 1 to the clear bits. + interrupt.clr().write(|w| w.out_eof().bit(true)); + + // SAFETY: This value is only ever read in our interrupt handler, + // and interrupts are disabled, and we only use this in one thread. + let Some(dma_state) = unsafe { &mut *DMA_STATE.0.get() }.as_mut() else { + error!("no DMA state available when executing DMA interrupt handler"); + return; + }; + + // The descriptor of the buffer with an EOF flag that just finished being sent. + let descriptor_ptr = DMA::regs() + .ch(DMA_CHANNEL_OUTBOUND) + .out_eof_des_addr() + .read() + .out_eof_des_addr() + .bits() as *const DmaDescriptor; + // This is the index of the window that just finished being transmitted to the destination peripheral. + let window_sent_index = + unsafe { descriptor_ptr.offset_from_unsigned(dma_state.bounce_src_descs.as_ptr()) } + / dma_state.descriptors_per_window; + // warn!("{window_sent_index}"); + // The next window to be sent is `(window_sent_index + 1) % dma_state.windows_len`. + // That is not the window we want to buffer, because the transmissions would race. + // We instead want to buffer the next window: + let window_index_next = (window_sent_index + 2) % dma_state.windows_len; + + // Swap bounce buffers. + if (dma_state.windows_len + window_index_next - dma_state.window_index_next) % 2 == 1 { + core::mem::swap( + &mut dma_state.bounce_buffer_dst, + &mut dma_state.bounce_buffer_src, ); } + + dma_state.window_index_next = window_index_next; + + // warn!("{window_sent_index} {window_index_next}"); + + let mut receiving_transfer = dma_state + .receiving_transfer + .take() + .expect("no ongoing transfer to a bounce buffer present"); + let receiving_transfer = receiving_transfer + .with_mut(|x| x.transfer.take()) + .expect("no ongoing inner transfer to a bounce buffer present"); + + // if !receiving_transfer.is_done() { + // error!("{window_sent_index}"); + // error!("the transfer to a bounce buffer has not finished yet, aborting"); + // } + + if receiving_transfer.is_done() { + drop(receiving_transfer); + } else { + receiving_transfer.wait().unwrap(); + } + + // If there is any ongoing transfer, cancel it and start a new one. + dma_state.receiving_transfer = Some(unsafe { dma_state.receive_window_start() }); } // #[handler(priority = Priority::Priority3)] @@ -882,7 +971,7 @@ pub fn allocate_dma_buffer_in( pub struct Framebuffer { pub width: u32, pub height: u32, - pub bounce_buffers: DmaBounce, + pub bounce_buffers: Option, } impl Framebuffer { @@ -920,11 +1009,11 @@ impl Framebuffer { Self { width: width_pixels, height: height_pixels, - bounce_buffers, + bounce_buffers: Some(bounce_buffers), } } pub fn as_target_pixels(&mut self) -> &mut [Rgb565Pixel] { - bytemuck::cast_slice_mut::<_, Rgb565Pixel>(self.bounce_buffers.buffer_src) + bytemuck::cast_slice_mut::<_, Rgb565Pixel>(self.bounce_buffers.as_mut().unwrap().buffer_src) } } diff --git a/firmware/acid-firmware/src/ui/mod.rs b/firmware/acid-firmware/src/ui/mod.rs index 06f1307..ba82cf8 100644 --- a/firmware/acid-firmware/src/ui/mod.rs +++ b/firmware/acid-firmware/src/ui/mod.rs @@ -377,11 +377,12 @@ impl State { // SIGNAL_LCD_SUBMIT.signal(()); #[cfg(feature = "limit-fps")] embassy_time::Timer::after(FRAME_DURATION_MIN).await; - let frames_skipped = FRAMES_SKIPPED.wait().await; + // let frames_skipped = FRAMES_SKIPPED.wait().await; + + // if frames_skipped > 0 { + // error!("Renderer missed {frames_skipped} frames."); + // } - if frames_skipped > 0 { - error!("Renderer missed {frames_skipped} frames."); - } // SIGNAL_UI_RENDER.wait().await; }