WIP: Higher prio

This commit is contained in:
Jakub Hlusička 2026-02-23 23:08:27 +01:00
parent 376416c32e
commit dde67e46fe
6 changed files with 269 additions and 151 deletions

View file

@ -86,6 +86,7 @@ tinyvec = { version = "1.10.0", default-features = false, features = ["alloc"] }
esp-metadata-generated = { version = "0.3.0", features = ["esp32s3"] } esp-metadata-generated = { version = "0.3.0", features = ["esp32s3"] }
hex = { version = "0.4.3", default-features = false, features = ["alloc"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
indoc = "2.0.7" indoc = "2.0.7"
ouroboros = "0.18.5"
# A fork of slint with patches for `allocator_api` support. # A fork of slint with patches for `allocator_api` support.
# Don't forget to change `slint-build` in build dependencies, if this is changed. # Don't forget to change `slint-build` in build dependencies, if this is changed.

View file

@ -4,7 +4,9 @@ use log::LevelFilter;
pub const LOG_LEVEL_FILTER: LevelFilter = { pub const LOG_LEVEL_FILTER: LevelFilter = {
if let Some(string) = option_env!("ESP_LOG") { 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 LevelFilter::Error
} else if string.eq_ignore_ascii_case("WARN") { } else if string.eq_ignore_ascii_case("WARN") {
LevelFilter::Warn LevelFilter::Warn

View file

@ -32,7 +32,7 @@ use alloc::vec::Vec;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_embedded_hal::flash::partition::Partition; 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::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::Channel; use embassy_sync::channel::Channel;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
@ -224,7 +224,7 @@ async fn test_bounce_buffers(
burst_config, burst_config,
true, true,
); );
buf.send().await; buf.launch_interrupt_driven_task().await;
error!("TEST BOUNCE BUFFERS SECTION DONE"); error!("TEST BOUNCE BUFFERS SECTION DONE");
} }
@ -321,11 +321,17 @@ async fn main(_spawner: Spawner) {
static EXECUTOR_CORE_0: StaticCell<InterruptExecutor<2>> = StaticCell::new(); static EXECUTOR_CORE_0: StaticCell<InterruptExecutor<2>> = StaticCell::new();
let executor_core_0 = InterruptExecutor::new(software_interrupt.software_interrupt2); let executor_core_0 = InterruptExecutor::new(software_interrupt.software_interrupt2);
let executor_core_0 = EXECUTOR_CORE_0.init(executor_core_0); 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<InterruptExecutor<3>> = 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!"); info!("ESP-RTOS started!");
let main_task_peripherals = MainPeripherals { let main_task_peripherals = MainPeripherals {
// high_priority_task_spawner: interrupt_core_1_spawner,
uart_rx, uart_rx,
software_interrupt1: software_interrupt.software_interrupt1, software_interrupt1: software_interrupt.software_interrupt1,
RNG: peripherals.RNG, RNG: peripherals.RNG,
@ -376,6 +382,7 @@ async fn main(_spawner: Spawner) {
/// Peripherals passed to the main task. /// Peripherals passed to the main task.
#[allow(non_snake_case)] #[allow(non_snake_case)]
struct MainPeripherals { struct MainPeripherals {
// high_priority_task_spawner: SendSpawner,
uart_rx: UartRx<'static, Blocking>, uart_rx: UartRx<'static, Blocking>,
software_interrupt1: SoftwareInterrupt<'static, 1>, software_interrupt1: SoftwareInterrupt<'static, 1>,
RNG: esp_hal::peripherals::RNG<'static>, RNG: esp_hal::peripherals::RNG<'static>,
@ -706,8 +713,8 @@ async fn main_task(peripherals: MainPeripherals) {
112, 112,
368 - 112, 368 - 112,
960, 960,
16, 8,
false, true,
)); ));
info!("Framebuffer created!"); info!("Framebuffer created!");
@ -751,6 +758,19 @@ async fn main_task(peripherals: MainPeripherals) {
info!("Awaiting on all tasks..."); 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. // TODO: Probably want to select! instead and re-try.
join_all![ join_all![
run_alloc_stats_reporter(), run_alloc_stats_reporter(),
@ -762,7 +782,7 @@ async fn main_task(peripherals: MainPeripherals) {
// warn!("Waited."); // warn!("Waited.");
// framebuffer.bounce_buffers.send().await; // framebuffer.bounce_buffers.send().await;
// }, // },
framebuffer.bounce_buffers.send(), // framebuffer.bounce_buffers.send(),
// ui::dpi::run_lcd(st7701s, framebuffer), // ui::dpi::run_lcd(st7701s, framebuffer),
// lcd_task, // lcd_task,
run_devices! ( run_devices! (
@ -785,6 +805,11 @@ async fn main_task(peripherals: MainPeripherals) {
.await; .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() { async fn run_alloc_stats_reporter() {
let mut psram_used_prev = 0; let mut psram_used_prev = 0;
let mut heap_used_prev = 0; let mut heap_used_prev = 0;

View file

@ -1276,7 +1276,7 @@ where
// //
// Adafruit would use 11 MHz. // Adafruit would use 11 MHz.
// I had lowered the frequency, so that `DmaBounce` could keep up. // 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 { .with_clock_mode(ClockMode {
polarity: Polarity::IdleLow, // From Adafruit polarity: Polarity::IdleLow, // From Adafruit
phase: Phase::ShiftHigh, // From Adafruit phase: Phase::ShiftHigh, // From Adafruit

View file

@ -1,5 +1,6 @@
use core::{ use core::{
alloc::Layout, alloc::Layout,
cell::UnsafeCell,
pin::Pin, pin::Pin,
sync::atomic::{self, AtomicBool, AtomicU32, AtomicUsize}, sync::atomic::{self, AtomicBool, AtomicU32, AtomicUsize},
}; };
@ -20,7 +21,7 @@ use esp_hal::{
dma::{ dma::{
self, AnyGdmaChannel, BufView, BurstConfig, DmaChannel, DmaChannelConvert, DmaDescriptor, self, AnyGdmaChannel, BufView, BurstConfig, DmaChannel, DmaChannelConvert, DmaDescriptor,
DmaDescriptorFlags, DmaEligible, DmaRxStreamBuf, DmaTxBuf, DmaTxBuffer, DmaTxInterrupt, DmaDescriptorFlags, DmaEligible, DmaRxStreamBuf, DmaTxBuf, DmaTxBuffer, DmaTxInterrupt,
ExternalBurstConfig, InternalBurstConfig, Mem2Mem, SimpleMem2MemTransfer, ExternalBurstConfig, InternalBurstConfig, Mem2Mem, SimpleMem2Mem, SimpleMem2MemTransfer,
}, },
dma_descriptors, handler, dma_descriptors, handler,
interrupt::{self, Priority}, interrupt::{self, Priority},
@ -33,8 +34,9 @@ use esp_sync::RawMutex;
use i_slint_core::software_renderer::{Rgb565Pixel, TargetPixel}; use i_slint_core::software_renderer::{Rgb565Pixel, TargetPixel};
use indoc::{formatdoc, indoc}; use indoc::{formatdoc, indoc};
use log::{error, info, warn}; use log::{error, info, warn};
use ouroboros::self_referencing;
use rmk::{ use rmk::{
futures::{FutureExt, pin_mut}, futures::{self, FutureExt, pin_mut},
join_all, join_all,
}; };
@ -76,6 +78,14 @@ pub unsafe fn cache_invalidate_addr(addr: u32, size: u32) {
const DMA_CHANNEL_OUTBOUND: usize = 2; const DMA_CHANNEL_OUTBOUND: usize = 2;
const INTERRUPT_OUTBOUND: Interrupt = Interrupt::DMA_OUT_CH2; const INTERRUPT_OUTBOUND: Interrupt = Interrupt::DMA_OUT_CH2;
#[self_referencing]
struct ReceivingTransfer {
mem2mem: SimpleMem2Mem<'static, Blocking>,
#[borrows(mut mem2mem)]
#[covariant]
transfer: Option<SimpleMem2MemTransfer<'this, 'static, Blocking>>,
}
pub struct DmaBounce { pub struct DmaBounce {
// TODO: Make these generic. // TODO: Make these generic.
// They currently cannot be generic, because they lacks a `reborrow` method. // They currently cannot be generic, because they lacks a `reborrow` method.
@ -84,6 +94,8 @@ pub struct DmaBounce {
peripheral_src: AnySpi<'static>, peripheral_src: AnySpi<'static>,
// This can also be more generic, see `DmaEligible` in `Mem2Mem::new`. // This can also be more generic, see `DmaEligible` in `Mem2Mem::new`.
peripheral_dst: Option<Dpi<'static, Blocking>>, peripheral_dst: Option<Dpi<'static, Blocking>>,
// TODO: Combine with peripheral_dst using an enum?
transfer_dst: Option<DpiTransfer<'static, DmaTxBounceBuf, Blocking>>,
// TODO: Consider having a separate burst config for the two transfers. // TODO: Consider having a separate burst config for the two transfers.
burst_config: BurstConfig, burst_config: BurstConfig,
@ -105,9 +117,11 @@ pub struct DmaBounce {
bounce_dst_descs: &'static mut [DmaDescriptor], bounce_dst_descs: &'static mut [DmaDescriptor],
// A cyclic descriptor list that spans the buffers `bounce_buffer_dst` and `bounce_buffer_src`. // A cyclic descriptor list that spans the buffers `bounce_buffer_dst` and `bounce_buffer_src`.
bounce_src_descs: &'static mut [DmaDescriptor], 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. // The index of the next window about to be received into the destination bounce buffer.
window_index_next: usize, window_index_next: usize,
frame_index_next: usize, frame_index_next: usize,
receiving_transfer: Option<ReceivingTransfer>,
} }
impl DmaBounce { impl DmaBounce {
@ -183,21 +197,7 @@ impl DmaBounce {
}); });
let bounce_dst_descs = let bounce_dst_descs =
Self::linear_descriptors_for_buffer(window_size, burst_config, |_| {}); Self::linear_descriptors_for_buffer(window_size, burst_config, |_| {});
let bounce_src_descs = if cyclic { let (bounce_src_descs, descriptors_per_window) = Self::bounce_descriptors_for_buffer(
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, windows_len,
row_front_porch_bytes, row_front_porch_bytes,
row_width_bytes, row_width_bytes,
@ -209,13 +209,14 @@ impl DmaBounce {
) )
}, },
burst_config, burst_config,
) cyclic,
}; );
Self { Self {
channel, channel,
peripheral_src, peripheral_src,
peripheral_dst: Some(peripheral_dst), peripheral_dst: Some(peripheral_dst),
transfer_dst: None,
burst_config, burst_config,
cyclic, cyclic,
window_size, window_size,
@ -226,8 +227,10 @@ impl DmaBounce {
src_descs, src_descs,
bounce_dst_descs, bounce_dst_descs,
bounce_src_descs, bounce_src_descs,
descriptors_per_window,
window_index_next: 0, window_index_next: 0,
frame_index_next: 0, frame_index_next: 0,
receiving_transfer: None,
} }
} }
@ -358,81 +361,15 @@ impl DmaBounce {
); );
} }
fn bounce_descriptors_for_buffer_cyclic( fn bounce_descriptors_for_buffer(
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(
windows_len: usize, windows_len: usize,
row_front_porch_bytes: usize, row_front_porch_bytes: usize,
row_width_bytes: usize, row_width_bytes: usize,
window_size_rows: usize, window_size_rows: usize,
bounce_buffers: (&'static mut [u8], &'static mut [u8]), bounce_buffers: (&'static mut [u8], &'static mut [u8]),
burst_config: BurstConfig, burst_config: BurstConfig,
) -> &'static mut [DmaDescriptor] { cyclic: bool,
) -> (&'static mut [DmaDescriptor], usize) {
assert_eq!( assert_eq!(
bounce_buffers.0.len(), bounce_buffers.0.len(),
bounce_buffers.1.len(), bounce_buffers.1.len(),
@ -449,6 +386,15 @@ impl DmaBounce {
"the provided bounce buffers have an invalid size" "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 max_chunk_size = burst_config.max_compatible_chunk_size();
let descriptors_per_row_front_porch = let descriptors_per_row_front_porch =
dma::descriptor_count(row_front_porch_bytes, max_chunk_size, false); 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_per_frame = descriptors_per_window * windows_len;
let descriptors_frame = let descriptors_frame =
Box::leak(vec![DmaDescriptor::EMPTY; descriptors_per_frame].into_boxed_slice()); Box::leak(vec![DmaDescriptor::EMPTY; descriptors_per_frame].into_boxed_slice());
let descriptors_frame_ptr = descriptors_frame.as_ptr();
// Link up the descriptors. // 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() { for desc in descriptors_frame.iter_mut().rev() {
desc.next = next; desc.next = next;
next = desc; next = desc;
@ -497,7 +449,7 @@ impl DmaBounce {
windows_len * window_size_rows * (row_front_porch_bytes + row_width_bytes) windows_len * window_size_rows * (row_front_porch_bytes + row_width_bytes)
); );
descriptors_frame (descriptors_frame, descriptors_per_window)
} }
fn linear_descriptors_prepare( fn linear_descriptors_prepare(
@ -549,6 +501,61 @@ impl DmaBounce {
.modify(|_, w| w.out_eof().bit(true)); .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. /// Receive a window of bytes into the current dst bounce buffer.
/// Finally, swaps the bounce buffers. /// Finally, swaps the bounce buffers.
async fn receive_window(&mut self) { async fn receive_window(&mut self) {
@ -595,14 +602,39 @@ impl DmaBounce {
self.increase_window_counter(1); self.increase_window_counter(1);
} }
fn increase_window_counter(&mut self, windows: usize) { fn increase_window_counter(&mut self, windows: isize) {
if windows % 2 == 1 { if windows.rem_euclid(2) == 1 {
core::mem::swap(&mut self.bounce_buffer_dst, &mut self.bounce_buffer_src); core::mem::swap(&mut self.bounce_buffer_dst, &mut self.bounce_buffer_src);
} }
self.window_index_next += windows; let window_index_next = self.window_index_next as isize + windows;
self.frame_index_next += self.window_index_next / self.windows_len; self.frame_index_next = (self.frame_index_next as isize
self.window_index_next = self.window_index_next % self.windows_len; + 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) { pub async fn send(&mut self) {
@ -633,31 +665,17 @@ impl DmaBounce {
// "Window received: {} {}", // "Window received: {} {}",
// self.window_index_next, self.frame_index_next // self.window_index_next, self.frame_index_next
// ); // );
let windows_skipped = WINDOWS_SKIPPED let windows_sent = WINDOWS_SENT
.wait() .wait()
.with_timeout(Duration::from_millis(100)) .with_timeout(Duration::from_millis(100))
.await .await
.unwrap_or_else(|_| { .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. 0
}); });
let windows_skipped = windows_sent as isize - 1;
// let windows_skipped = match windows_skipped { if windows_skipped != 0 {
// 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 {
self.increase_window_counter(windows_skipped); self.increase_window_counter(windows_skipped);
windows_skipped_total += windows_skipped; windows_skipped_total += windows_skipped;
// error!( // error!(
@ -674,7 +692,9 @@ impl DmaBounce {
if !self.cyclic && (self.window_index_next == 1 || transfer.is_done()) { if !self.cyclic && (self.window_index_next == 1 || transfer.is_done()) {
if self.window_index_next > 1 { 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 { } else if self.window_index_next == 0 {
self.increase_window_counter(1); 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. /// Intended to be listened on by the renderer, to synchronize the refresh frequency with.
pub static FRAMES_SKIPPED: Signal<RawMutex, usize> = Signal::new(); pub static FRAMES_SKIPPED: Signal<RawMutex, usize> = Signal::new();
static WINDOWS_SKIPPED: Signal<RawMutex, usize> = Signal::new(); static WINDOWS_SENT: Signal<RawMutex, usize> = Signal::new();
// static INBOUND_TRANSFER_FINISHED: Signal<RawMutex, ()> = Signal::new(); static DMA_STATE: SyncUnsafeCell<Option<DmaBounce>> = SyncUnsafeCell(UnsafeCell::new(None));
#[repr(transparent)]
pub struct SyncUnsafeCell<T>(UnsafeCell<T>);
unsafe impl<T> Sync for SyncUnsafeCell<T> {}
// #[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)] #[handler(priority = Priority::Priority3)]
#[ram] // Improves performance. #[ram] // Improves performance.
fn dma_outbound_interrupt_handler() { fn dma_outbound_interrupt_handler() {
let interrupt = DMA::regs().ch(DMA_CHANNEL_OUTBOUND).out_int(); let interrupt = DMA::regs().ch(DMA_CHANNEL_OUTBOUND).out_int();
let bounce_buffer_processed = interrupt.st().read().out_eof().bit_is_set(); let bounce_buffer_sent = interrupt.st().read().out_eof().bit_is_set();
if bounce_buffer_processed {
if !bounce_buffer_sent {
return;
}
// Clear the bit by writing 1 to the clear bits. // Clear the bit by writing 1 to the clear bits.
interrupt.clr().write(|w| w.out_eof().bit(true)); interrupt.clr().write(|w| w.out_eof().bit(true));
WINDOWS_SKIPPED.signal( // SAFETY: This value is only ever read in our interrupt handler,
WINDOWS_SKIPPED // and interrupts are disabled, and we only use this in one thread.
.try_take() let Some(dma_state) = unsafe { &mut *DMA_STATE.0.get() }.as_mut() else {
.map(|windows_skipped| windows_skipped + 1) error!("no DMA state available when executing DMA interrupt handler");
.unwrap_or_default(), 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)] // #[handler(priority = Priority::Priority3)]
@ -882,7 +971,7 @@ pub fn allocate_dma_buffer_in<A: Allocator>(
pub struct Framebuffer { pub struct Framebuffer {
pub width: u32, pub width: u32,
pub height: u32, pub height: u32,
pub bounce_buffers: DmaBounce, pub bounce_buffers: Option<DmaBounce>,
} }
impl Framebuffer { impl Framebuffer {
@ -920,11 +1009,11 @@ impl Framebuffer {
Self { Self {
width: width_pixels, width: width_pixels,
height: height_pixels, height: height_pixels,
bounce_buffers, bounce_buffers: Some(bounce_buffers),
} }
} }
pub fn as_target_pixels(&mut self) -> &mut [Rgb565Pixel] { 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)
} }
} }

View file

@ -377,11 +377,12 @@ impl State {
// SIGNAL_LCD_SUBMIT.signal(()); // SIGNAL_LCD_SUBMIT.signal(());
#[cfg(feature = "limit-fps")] #[cfg(feature = "limit-fps")]
embassy_time::Timer::after(FRAME_DURATION_MIN).await; 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; // SIGNAL_UI_RENDER.wait().await;
} }