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>,
) -> DpiTransfer<'static, DmaTxBounceBuf, Blocking> {
error!("TEST BOUNCE BUFFERS SECTION ENTERED");
let windows_len = 960;
let window_size = 8 * 368 * core::mem::size_of::<u16>();
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::<u16>();
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::<u8, Rgb565Pixel>(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::<Vec<_>>();
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");

View file

@ -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

View file

@ -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<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();
#[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(