WIP3: Glitchy bounce buffering

This commit is contained in:
Jakub Hlusička 2026-02-15 18:17:16 +01:00
parent b8e3987139
commit 7e1f0d1b68
2 changed files with 268 additions and 111 deletions

View file

@ -60,6 +60,7 @@ use esp_hal::timer::timg::TimerGroup;
use esp_hal::{Blocking, interrupt};
use esp_rtos::embassy::{Executor, InterruptExecutor};
use esp_storage::FlashStorage;
use i_slint_core::software_renderer::TargetPixel;
use indoc::writedoc;
use itertools::Itertools;
use log::{error, info, warn};
@ -83,7 +84,7 @@ use crate::matrix::IoeMatrix;
use crate::peripherals::st7701s::St7701s;
use crate::proxy::create_hid_report_interceptor;
use crate::ui::backend::{FramebufferPtr, SlintBackend};
use crate::ui::dpi::{DmaBounce, DmaTxBounceBuf, Framebuffer};
use crate::ui::dpi::{DmaBounce, DmaTxBounceBuf, Framebuffer, allocate_dma_buffer_in};
use crate::vial::{
CustomKeycodes, VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID, VIAL_KEYBOARD_NAME, VIAL_PRODUCT_ID,
VIAL_VENDOR_ID,
@ -133,20 +134,53 @@ static SIGNAL_LCD_SUBMIT: Signal<CriticalSectionRawMutex, ()> = Signal::new();
/// Used to signal that the MCU is ready to render the GUI.
static SIGNAL_UI_RENDER: Signal<CriticalSectionRawMutex, ()> = Signal::new();
#[embassy_executor::task]
async fn test_bounce_buffers_task(
channel: DMA_CH0<'static>,
peripheral: SPI2<'static>,
st7701s: St7701s<'static, Blocking>,
) {
test_bounce_buffers(channel, peripheral, st7701s).await;
}
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");
let windows_len = 2;
let window_size = 368 * core::mem::size_of::<u16>();
let buffer_src = Box::leak(vec![0_u8; windows_len * window_size].into_boxed_slice());
let mut counter: u8 = 0;
buffer_src.fill_with(|| {
counter = counter.wrapping_add(1);
counter
});
let windows_len = 960;
let window_size = 8 * 368 * core::mem::size_of::<u16>();
let buffer_src = Box::leak(allocate_dma_buffer_in(
windows_len * window_size,
&PSRAM_ALLOCATOR,
));
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))
.collect::<Vec<_>>();
for (index, pixel) in buffer_src.iter_mut().enumerate() {
let mut x = (index % 368) as i16 - 120;
let mut y = (index / 368) 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);
*pixel = colors[min as usize % colors.len()].clone();
continue;
}
*pixel = Rgb565Pixel::default();
}
let buffer_src = bytemuck::cast_slice_mut::<Rgb565Pixel, u8>(buffer_src);
// let mut counter: u8 = 0;
// buffer_src.fill_with(|| {
// counter = counter.wrapping_add(1);
// counter
// });
let mut buf = DmaBounce::new(
channel,
AnySpi::from(peripheral),
@ -421,9 +455,15 @@ async fn main(_spawner: Spawner) {
info!("ST7701S-based LCD display initialized!");
let lcd_task = test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2, st7701s);
interrupt_core_0_spawner.must_spawn(test_bounce_buffers_task(
peripherals.DMA_CH0,
peripherals.SPI2,
st7701s,
));
let _ = lcd_task.await;
// let lcd_task = test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2, st7701s);
// let _ = lcd_task.await;
return;
// RMK config

View file

@ -1,7 +1,7 @@
use core::{
alloc::Layout,
pin::Pin,
sync::atomic::{self, AtomicBool},
sync::atomic::{self, AtomicBool, AtomicU32, AtomicUsize},
};
use alloc::{
@ -9,7 +9,10 @@ use alloc::{
boxed::Box,
vec,
};
use embassy_sync::channel::Channel;
use embassy_sync::{
channel::{Channel, TrySendError},
signal::Signal,
};
use embassy_time::Timer;
use esp_alloc::MemoryCapability;
use esp_hal::{
@ -30,6 +33,10 @@ use esp_sync::RawMutex;
use i_slint_core::software_renderer::Rgb565Pixel;
use indoc::{formatdoc, indoc};
use log::{error, info, warn};
use rmk::{
futures::{FutureExt, pin_mut},
join_all,
};
use crate::{PSRAM_ALLOCATOR, SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, peripherals::st7701s::St7701s};
@ -93,6 +100,9 @@ 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],
// The index of the next window about to be received into the destination bounce buffer.
window_index_next: usize,
frame_index_next: usize,
}
impl DmaBounce {
@ -143,6 +153,8 @@ impl DmaBounce {
src_descs,
bounce_dst_descs,
bounce_src_descs,
window_index_next: 0,
frame_index_next: 0,
}
}
@ -256,17 +268,32 @@ impl DmaBounce {
}
fn enable_interrupts() {
// TODO: Get from self.channel
let channel_number = 2;
let interrupt = esp_hal::peripherals::Interrupt::DMA_OUT_CH2;
// Enable interrupts for the peripheral
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(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(),
@ -275,14 +302,23 @@ impl DmaBounce {
// Bind the handler
unsafe {
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(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(),
@ -290,26 +326,25 @@ impl DmaBounce {
}
// Enable interrupts in the peripheral.
let channel_number = 0; // TODO: Get from self.channel
DMA::regs()
.ch(channel_number)
.out_int()
.ena()
.modify(|_, w| {
w.out_total_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.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);
@ -320,16 +355,17 @@ impl DmaBounce {
}
fn print_regs() {
let channel_number = 0; // TODO: Get from self.channel
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:
// 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}
@ -339,20 +375,85 @@ impl DmaBounce {
suc_eof: {in_suc_eof}
done: {in_done}
"},
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(),
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.
/// Finally, swaps the bounce buffers.
async fn receive_window(&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), |_| {});
// 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();
},
);
error!("int_raw_msg: 0x{:08x?}", INT_CHANNEL.try_receive());
{
// 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 = unsafe { &mut *(self.bounce_dst_descs as *mut _) };
let src_descs = unsafe { &mut *(self.src_descs as *mut _) };
let mut mem2mem = Mem2Mem::new(self.channel.reborrow(), self.peripheral_src.reborrow())
.with_descriptors(bounce_dst_descs, src_descs, self.burst_config)
.unwrap();
let transfer = mem2mem
.start_transfer(&mut self.bounce_buffer_dst, buffer_src_window)
.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);
}
fn increase_window_counter(&mut self, windows: usize) {
if windows % 2 == 1 {
core::mem::swap(&mut self.bounce_buffer_dst, &mut self.bounce_buffer_src);
}
self.window_index_next = 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;
}
pub async fn send(&mut self) // -> DpiTransfer<'static, DmaTxBounceBuf, Blocking>
{
Self::enable_interrupts();
// Receive the first window, so that the outbound transfer can read valid data.
self.receive_window().await;
let mut dma_tx_buffer = self.get_dma_tx_buffer();
let transfer = match self
.peripheral_dst
@ -381,58 +482,47 @@ impl DmaBounce {
}
};
for window_index in 0..self.windows_len {
// Descriptors are initialized by `DmaTxBuf::new`.
let buffer_src_window =
&mut self.buffer_src[window_index * self.window_size..][..self.window_size];
Self::linear_descriptors_prepare(self.src_descs, Some(buffer_src_window), |_| {});
// 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 = unsafe { &mut *(self.bounce_dst_descs as *mut _) };
let src_descs = unsafe { &mut *(self.src_descs as *mut _) };
let mut mem2mem =
Mem2Mem::new(self.channel.reborrow(), self.peripheral_src.reborrow())
.with_descriptors(bounce_dst_descs, src_descs, self.burst_config)
.unwrap();
let transfer = mem2mem
.start_transfer(&mut self.bounce_buffer_dst, buffer_src_window)
.unwrap();
Self::print_regs();
transfer.wait().unwrap();
}
Self::print_regs();
// 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);
}
let mut windows_skipped_total = 0;
loop {
warn!("Still sending data to DPI? {}", !transfer.is_done());
Timer::after_secs(10).await;
// warn!("Iteration. Done = {}", transfer.is_done());
self.receive_window().await;
// let windows_sent = BOUNCE_BUFFER_SENT.receive().await;
let windows_skipped = WINDOWS_SKIPPED.wait().await;
if windows_skipped > 0 {
self.increase_window_counter(windows_skipped);
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
);
}
}
// loop {
// // BOUNCE_BUFFER_SENT.receive().await;
// warn!("Iteration. Done = {}", transfer.is_done());
// let receive_window = self.receive_window().fuse();
// pin_mut!(receive_window);
// // let mut send_buffer = BOUNCE_BUFFER_SENT.wait().fuse();
// let mut send_buffer = BOUNCE_BUFFER_SENT.receive().fuse();
// let window_received_first = rmk::futures::select_biased! {
// () = receive_window => Ok(()),
// windows_sent = send_buffer => Err(windows_sent),
// };
// match window_received_first {
// Ok(()) => {
// send_buffer.await;
// }
// Err(windows_sent) => {
// error!("Sent {windows_sent} windows before a window could be received.");
// receive_window.await;
// }
// }
// }
// transfer
}
@ -480,14 +570,41 @@ 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)]
#[ram] // Improves performance.
fn dma_interrupt_handler() {
FLAG.store(true, atomic::Ordering::SeqCst);
let int_raw = DMA::regs().ch(0).out_int().raw().read();
let interrupt = DMA::regs().ch(2).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));
INT_CHANNEL.try_send(int_raw.bits()).unwrap();
let windows_skipped = WINDOWS_SKIPPED
.try_take()
.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 };