Bounce buffers with a static image working

This commit is contained in:
Jakub Hlusička 2026-02-17 00:51:02 +01:00
parent 7e1f0d1b68
commit 04f4070634
3 changed files with 159 additions and 203 deletions

View file

@ -149,8 +149,11 @@ async fn test_bounce_buffers(
st7701s: St7701s<'static, Blocking>, st7701s: St7701s<'static, Blocking>,
) -> DpiTransfer<'static, DmaTxBounceBuf, Blocking> { ) -> DpiTransfer<'static, DmaTxBounceBuf, Blocking> {
error!("TEST BOUNCE BUFFERS SECTION ENTERED"); error!("TEST BOUNCE BUFFERS SECTION ENTERED");
let windows_len = 960; const WIDTH: usize = 368;
let window_size = 8 * 368 * core::mem::size_of::<u16>(); 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::<u16>();
let buffer_src = Box::leak(allocate_dma_buffer_in( let buffer_src = Box::leak(allocate_dma_buffer_in(
windows_len * window_size, windows_len * window_size,
&PSRAM_ALLOCATOR, &PSRAM_ALLOCATOR,
@ -158,7 +161,19 @@ async fn test_bounce_buffers(
let buffer_src = bytemuck::cast_slice_mut::<u8, Rgb565Pixel>(buffer_src); let buffer_src = bytemuck::cast_slice_mut::<u8, Rgb565Pixel>(buffer_src);
let colors = (0..120_u8) let colors = (0..120_u8)
.rev() .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::<Vec<_>>(); .collect::<Vec<_>>();
for (index, pixel) in buffer_src.iter_mut().enumerate() { for (index, pixel) in buffer_src.iter_mut().enumerate() {
let mut x = (index % 368) as i16 - 120; let mut x = (index % 368) as i16 - 120;
@ -191,6 +206,7 @@ async fn test_bounce_buffers(
internal_memory: InternalBurstConfig::Enabled, internal_memory: InternalBurstConfig::Enabled,
external_memory: ExternalBurstConfig::Size32, external_memory: ExternalBurstConfig::Size32,
}, },
false,
); );
let _ = buf.send().await; let _ = buf.send().await;
error!("TEST BOUNCE BUFFERS SECTION DONE"); error!("TEST BOUNCE BUFFERS SECTION DONE");

View file

@ -1273,7 +1273,10 @@ where
// - prevent flash from being used whilst your program is running. (There's a PR to make // - prevent flash from being used whilst your program is running. (There's a PR to make
// this easy to do) // this easy to do)
// https://github.com/esp-rs/esp-hal/pull/3024 // 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 { .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

@ -13,7 +13,7 @@ use embassy_sync::{
channel::{Channel, TrySendError}, channel::{Channel, TrySendError},
signal::Signal, signal::Signal,
}; };
use embassy_time::Timer; use embassy_time::{Instant, Timer};
use esp_alloc::MemoryCapability; use esp_alloc::MemoryCapability;
use esp_hal::{ use esp_hal::{
Blocking, Blocking,
@ -38,7 +38,10 @@ use rmk::{
join_all, 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 /// 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. /// 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. // TODO: Consider having a separate burst config for the two transfers.
burst_config: BurstConfig, burst_config: BurstConfig,
// rx: DmaRxStreamBuf, cyclic: bool,
/// The size of each window. /// The size of each window.
window_size: usize, window_size: usize,
/// The number of windows. /// The number of windows.
@ -113,6 +116,7 @@ impl DmaBounce {
buffer_src: &'static mut [u8], buffer_src: &'static mut [u8],
window_size: usize, window_size: usize,
burst_config: BurstConfig, burst_config: BurstConfig,
cyclic: bool,
) -> Self { ) -> Self {
assert_eq!( assert_eq!(
buffer_src.len() % window_size, 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", "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() 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_dst = Box::leak(allocate_dma_buffer_in(window_size, Global));
let bounce_buffer_src = 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| { let src_descs = Self::linear_descriptors_for_buffer(window_size, burst_config, |desc| {
@ -130,7 +135,9 @@ 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 = Self::bounce_descriptors_for_buffer( let bounce_src_descs = if cyclic {
Self::bounce_descriptors_for_buffer_single(
windows_len,
unsafe { unsafe {
( (
&mut *(bounce_buffer_dst as *mut _), &mut *(bounce_buffer_dst as *mut _),
@ -138,15 +145,27 @@ impl DmaBounce {
) )
}, },
burst_config, 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 { Self {
channel, channel,
peripheral_src, peripheral_src,
peripheral_dst: Some(peripheral_dst), peripheral_dst: Some(peripheral_dst),
burst_config, burst_config,
cyclic,
window_size, window_size,
windows_len: buffer_src.len() / window_size, windows_len,
buffer_src, buffer_src,
bounce_buffer_dst, bounce_buffer_dst,
bounce_buffer_src, bounce_buffer_src,
@ -190,7 +209,7 @@ impl DmaBounce {
descriptors descriptors
} }
fn bounce_descriptors_for_buffer( fn bounce_descriptors_for_buffer_cyclic(
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] { ) -> &'static mut [DmaDescriptor] {
@ -202,8 +221,13 @@ impl DmaBounce {
let buffer_len = bounce_buffers.0.len(); let buffer_len = bounce_buffers.0.len();
let max_chunk_size = burst_config.max_compatible_chunk_size(); let max_chunk_size = burst_config.max_compatible_chunk_size();
let descriptors_len = dma::descriptor_count(buffer_len, max_chunk_size, false); let descriptors_len = dma::descriptor_count(
// TODO: This leaks memory. Ensure it's only called during setup. 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 = let descriptors_combined =
Box::leak(vec![DmaDescriptor::EMPTY; 2 * descriptors_len].into_boxed_slice()); Box::leak(vec![DmaDescriptor::EMPTY; 2 * descriptors_len].into_boxed_slice());
let descriptors_pair = descriptors_combined.split_at_mut(descriptors_len); let descriptors_pair = descriptors_combined.split_at_mut(descriptors_len);
@ -246,6 +270,58 @@ impl DmaBounce {
descriptors_combined 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( fn linear_descriptors_prepare(
descriptors: &mut [DmaDescriptor], descriptors: &mut [DmaDescriptor],
mut buffer: Option<&mut [u8]>, mut buffer: Option<&mut [u8]>,
@ -274,55 +350,10 @@ impl DmaBounce {
// Enable interrupts for the peripheral // Enable interrupts for the peripheral
interrupt::enable(interrupt, 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(),
// )
// .unwrap();
// Bind the handler // Bind the handler
unsafe { unsafe {
interrupt::bind_interrupt(interrupt, 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(),
// );
} }
// Enable interrupts in the peripheral. // Enable interrupts in the peripheral.
@ -330,74 +361,19 @@ impl DmaBounce {
.ch(channel_number) .ch(channel_number)
.out_int() .out_int()
.ena() .ena()
.modify(|_, w| { .modify(|_, w| w.out_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
// });
// 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()
);
}
} }
/// 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) { fn receive_window_blocking(&mut self) {
// Descriptors are initialized by `DmaTxBuf::new`. // Descriptors are initialized by `DmaTxBuf::new`.
let buffer_src_window = let buffer_src_window =
&mut self.buffer_src[self.window_index_next * self.window_size..][..self.window_size]; &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)`. // TODO: Precompute a descriptor list for each buffer, then use `None` instead of `Some(&mut *self.bounce_buffer_dst)`.
Self::linear_descriptors_prepare( Self::linear_descriptors_prepare(
self.bounce_dst_descs, self.bounce_dst_descs,
@ -425,15 +401,6 @@ impl DmaBounce {
transfer.wait().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); self.increase_window_counter(1);
} }
@ -452,42 +419,22 @@ impl DmaBounce {
Self::enable_interrupts(); Self::enable_interrupts();
// Receive the first window, so that the outbound transfer can read valid data. // 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 mut dma_tx_buffer = self.get_dma_tx_buffer();
let transfer = match self let mut transfer = self
.peripheral_dst .peripheral_dst
.take() .take()
.unwrap() .unwrap()
.send(true /* Send perpetually */, dma_tx_buffer) .send(self.cyclic /* Send perpetually */, dma_tx_buffer)
{ .unwrap_or_else(|(error, _, _)| {
Ok(transfer) => { panic!("failed to begin the transmission of the first frame: {error:?}");
// 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:?}");
}
};
let mut windows_skipped_total = 0; let mut windows_skipped_total = 0;
loop { loop {
// warn!("Iteration. Done = {}", transfer.is_done()); self.receive_window_blocking();
self.receive_window().await;
// let windows_sent = BOUNCE_BUFFER_SENT.receive().await;
let windows_skipped = WINDOWS_SKIPPED.wait().await; let windows_skipped = WINDOWS_SKIPPED.wait().await;
if windows_skipped > 0 { if windows_skipped > 0 {
@ -498,6 +445,38 @@ impl DmaBounce {
100.0 * windows_skipped_total as f32 / (self.frame_index_next + 1) as f32 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 { // loop {
@ -568,10 +547,6 @@ unsafe impl DmaTxBuffer for DmaTxBounceBuf {
} }
} }
static INT_CHANNEL: Channel<RawMutex, u32, 128> = Channel::new();
static FLAG: AtomicBool = AtomicBool::new(false);
// static WINDOWS_SENT: AtomicU32 = AtomicU32::new(0);
// static BOUNCE_BUFFER_SENT: Channel<RawMutex, usize, 1> = Channel::new();
static WINDOWS_SKIPPED: Signal<RawMutex, usize> = Signal::new(); static WINDOWS_SKIPPED: Signal<RawMutex, usize> = Signal::new();
#[handler(priority = Priority::Priority3)] #[handler(priority = Priority::Priority3)]
@ -588,45 +563,7 @@ fn dma_interrupt_handler() {
.map(|windows_skipped| windows_skipped + 1) .map(|windows_skipped| windows_skipped + 1)
.unwrap_or_default(); .unwrap_or_default();
WINDOWS_SKIPPED.signal(windows_skipped); 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( pub async fn run_lcd(