Async DMA transfer

This commit is contained in:
Jakub Hlusička 2026-02-18 05:03:05 +01:00
parent 04f4070634
commit ebf8205f2d
4 changed files with 73 additions and 23 deletions

View file

@ -38,3 +38,14 @@ ACID_COMPOSE_LOCALE = "cs_CZ.UTF-8"
# This can be substituted with a `-Zbuild-std="core,alloc"` cargo flag. # This can be substituted with a `-Zbuild-std="core,alloc"` cargo flag.
[unstable] [unstable]
build-std = ["alloc", "core"] build-std = ["alloc", "core"]
[patch.crates-io]
esp-backtrace = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
esp-hal = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
esp-storage = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
esp-alloc = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
esp-println = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
esp-radio = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
esp-rtos = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
esp-bootloader-esp-idf = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
esp-sync = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }

View file

@ -289,9 +289,7 @@ async fn main(_spawner: Spawner) {
let timg0 = TimerGroup::new(peripherals.TIMG0); let timg0 = TimerGroup::new(peripherals.TIMG0);
let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
esp_rtos::start( esp_rtos::start(timg0.timer0, software_interrupt.software_interrupt0);
timg0.timer0, /*, software_interrupt.software_interrupt0 */
);
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);
@ -592,7 +590,7 @@ async fn main(_spawner: Spawner) {
let second_core_stack = SECOND_CORE_STACK.init(Stack::new()); let second_core_stack = SECOND_CORE_STACK.init(Stack::new());
esp_rtos::start_second_core( esp_rtos::start_second_core(
peripherals.CPU_CTRL, peripherals.CPU_CTRL,
software_interrupt.software_interrupt0, // software_interrupt.software_interrupt0,
software_interrupt.software_interrupt1, software_interrupt.software_interrupt1,
second_core_stack, second_core_stack,
move || { move || {

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(8)) // From Adafruit .with_frequency(Rate::from_mhz(7)) // 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,19 +13,19 @@ use embassy_sync::{
channel::{Channel, TrySendError}, channel::{Channel, TrySendError},
signal::Signal, signal::Signal,
}; };
use embassy_time::{Instant, Timer}; use embassy_time::{Duration, Instant, Timer, WithTimeout};
use esp_alloc::MemoryCapability; use esp_alloc::MemoryCapability;
use esp_hal::{ use esp_hal::{
Blocking, Blocking,
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, Mem2Mem, ExternalBurstConfig, Mem2Mem, SimpleMem2MemTransfer,
}, },
dma_descriptors, handler, dma_descriptors, handler,
interrupt::{self, Priority}, interrupt::{self, Priority},
lcd_cam::lcd::dpi::{Dpi, DpiTransfer}, lcd_cam::lcd::dpi::{Dpi, DpiTransfer},
peripherals::{DMA, DMA_CH0, Peripherals, SPI2}, peripherals::{DMA, DMA_CH0, Interrupt, Peripherals, SPI2},
ram, ram,
spi::master::AnySpi, spi::master::AnySpi,
}; };
@ -74,6 +74,11 @@ pub unsafe fn cache_invalidate_addr(addr: u32, size: u32) {
} }
} }
// const DMA_CHANNEL_INBOUND: usize = 0;
// const INTERRUPT_INBOUND: Interrupt = Interrupt::DMA_IN_CH0;
const DMA_CHANNEL_OUTBOUND: usize = 2;
const INTERRUPT_OUTBOUND: Interrupt = Interrupt::DMA_OUT_CH2;
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.
@ -344,21 +349,28 @@ impl DmaBounce {
} }
fn enable_interrupts() { 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 // Enable interrupts for the peripheral
interrupt::enable(interrupt, dma_interrupt_handler.priority()).unwrap(); // interrupt::enable(INTERRUPT_INBOUND, dma_inbound_interrupt_handler.priority()).unwrap();
interrupt::enable(
INTERRUPT_OUTBOUND,
dma_outbound_interrupt_handler.priority(),
)
.unwrap();
// Bind the handler // Bind the handler
unsafe { unsafe {
interrupt::bind_interrupt(interrupt, dma_interrupt_handler.handler()); // interrupt::bind_interrupt(INTERRUPT_INBOUND, dma_inbound_interrupt_handler.handler());
interrupt::bind_interrupt(INTERRUPT_OUTBOUND, dma_outbound_interrupt_handler.handler());
} }
// Enable interrupts in the peripheral. // Enable interrupts in the peripheral.
// DMA::regs()
// .ch(DMA_CHANNEL_INBOUND)
// .in_int()
// .ena()
// .modify(|_, w| w.in_done().bit(true));
DMA::regs() DMA::regs()
.ch(channel_number) .ch(DMA_CHANNEL_OUTBOUND)
.out_int() .out_int()
.ena() .ena()
.modify(|_, w| w.out_eof().bit(true)); .modify(|_, w| w.out_eof().bit(true));
@ -366,7 +378,7 @@ impl DmaBounce {
/// 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.
fn receive_window_blocking(&mut self) { async fn receive_window(&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];
@ -392,13 +404,15 @@ impl DmaBounce {
let bounce_dst_descs = unsafe { &mut *(self.bounce_dst_descs as *mut _) }; let bounce_dst_descs = unsafe { &mut *(self.bounce_dst_descs as *mut _) };
let src_descs = unsafe { &mut *(self.src_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()) let mut mem2mem = Mem2Mem::new(self.channel.reborrow(), self.peripheral_src.reborrow())
.into_async()
.with_descriptors(bounce_dst_descs, src_descs, self.burst_config) .with_descriptors(bounce_dst_descs, src_descs, self.burst_config)
.unwrap(); .unwrap();
let transfer = mem2mem let transfer = mem2mem
.start_transfer(&mut self.bounce_buffer_dst, buffer_src_window) .start_transfer(&mut self.bounce_buffer_dst, buffer_src_window)
.unwrap(); .unwrap();
transfer.wait().unwrap(); // INBOUND_TRANSFER_FINISHED.wait().await;
transfer.wait_async().await.unwrap();
} }
self.increase_window_counter(1); self.increase_window_counter(1);
@ -409,7 +423,7 @@ impl DmaBounce {
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 = self.window_index_next + windows; self.window_index_next += windows;
self.frame_index_next += self.window_index_next / self.windows_len; self.frame_index_next += self.window_index_next / self.windows_len;
self.window_index_next = self.window_index_next % self.windows_len; self.window_index_next = self.window_index_next % self.windows_len;
} }
@ -419,7 +433,7 @@ 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_blocking(); self.receive_window().await;
let mut dma_tx_buffer = self.get_dma_tx_buffer(); let mut dma_tx_buffer = self.get_dma_tx_buffer();
let mut transfer = self let mut transfer = self
@ -434,8 +448,15 @@ impl DmaBounce {
let mut windows_skipped_total = 0; let mut windows_skipped_total = 0;
loop { loop {
self.receive_window_blocking(); self.receive_window().await;
let windows_skipped = WINDOWS_SKIPPED.wait().await; let windows_skipped = WINDOWS_SKIPPED
.wait()
// .with_timeout(Duration::from_secs(1))
.await;
// .unwrap_or_else(|_| {
// error!("Timed out while waiting for skipped windows");
// 0
// });
if windows_skipped > 0 { if windows_skipped > 0 {
self.increase_window_counter(windows_skipped); self.increase_window_counter(windows_skipped);
@ -548,11 +569,12 @@ unsafe impl DmaTxBuffer for DmaTxBounceBuf {
} }
static WINDOWS_SKIPPED: Signal<RawMutex, usize> = Signal::new(); static WINDOWS_SKIPPED: Signal<RawMutex, usize> = Signal::new();
// static INBOUND_TRANSFER_FINISHED: Signal<RawMutex, ()> = Signal::new();
#[handler(priority = Priority::Priority3)] #[handler(priority = Priority::Priority3)]
#[ram] // Improves performance. #[ram] // Improves performance.
fn dma_interrupt_handler() { fn dma_outbound_interrupt_handler() {
let interrupt = DMA::regs().ch(2).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_processed = interrupt.st().read().out_eof().bit_is_set();
if bounce_buffer_processed { if bounce_buffer_processed {
// Clear the bit by writing 1 to the clear bits. // Clear the bit by writing 1 to the clear bits.
@ -566,6 +588,25 @@ fn dma_interrupt_handler() {
} }
} }
// #[handler(priority = Priority::Priority3)]
// #[ram] // Improves performance.
// fn dma_inbound_interrupt_handler() {
// warn!("Inbound");
// let interrupt = DMA::regs().ch(DMA_CHANNEL_INBOUND).in_int();
// let bounce_buffer_processed = interrupt.st().read().in_done().bit_is_set();
// if bounce_buffer_processed {
// // Clear the bit by writing 1 to the clear bits.
// interrupt.clr().write(|w| w.in_done().bit(true));
// assert!(
// !INBOUND_TRANSFER_FINISHED.signaled(),
// "inbound transfer already signalled"
// );
// INBOUND_TRANSFER_FINISHED.signal(());
// }
// }
pub async fn run_lcd( pub async fn run_lcd(
mut st7701s: St7701s<'static, Blocking>, mut st7701s: St7701s<'static, Blocking>,
framebuffer: &'static mut Framebuffer, framebuffer: &'static mut Framebuffer,