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.
[unstable]
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 software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
esp_rtos::start(
timg0.timer0, /*, software_interrupt.software_interrupt0 */
);
esp_rtos::start(timg0.timer0, software_interrupt.software_interrupt0);
static EXECUTOR_CORE_0: StaticCell<InterruptExecutor<2>> = StaticCell::new();
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());
esp_rtos::start_second_core(
peripherals.CPU_CTRL,
software_interrupt.software_interrupt0,
// software_interrupt.software_interrupt0,
software_interrupt.software_interrupt1,
second_core_stack,
move || {

View file

@ -1276,7 +1276,7 @@ where
//
// 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_frequency(Rate::from_mhz(7)) // From Adafruit
.with_clock_mode(ClockMode {
polarity: Polarity::IdleLow, // From Adafruit
phase: Phase::ShiftHigh, // From Adafruit

View file

@ -13,19 +13,19 @@ use embassy_sync::{
channel::{Channel, TrySendError},
signal::Signal,
};
use embassy_time::{Instant, Timer};
use embassy_time::{Duration, Instant, Timer, WithTimeout};
use esp_alloc::MemoryCapability;
use esp_hal::{
Blocking,
dma::{
self, AnyGdmaChannel, BufView, BurstConfig, DmaChannel, DmaChannelConvert, DmaDescriptor,
DmaDescriptorFlags, DmaEligible, DmaRxStreamBuf, DmaTxBuf, DmaTxBuffer, DmaTxInterrupt,
ExternalBurstConfig, Mem2Mem,
ExternalBurstConfig, Mem2Mem, SimpleMem2MemTransfer,
},
dma_descriptors, handler,
interrupt::{self, Priority},
lcd_cam::lcd::dpi::{Dpi, DpiTransfer},
peripherals::{DMA, DMA_CH0, Peripherals, SPI2},
peripherals::{DMA, DMA_CH0, Interrupt, Peripherals, SPI2},
ram,
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 {
// TODO: Make these generic.
// They currently cannot be generic, because they lacks a `reborrow` method.
@ -344,21 +349,28 @@ 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(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
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.
// DMA::regs()
// .ch(DMA_CHANNEL_INBOUND)
// .in_int()
// .ena()
// .modify(|_, w| w.in_done().bit(true));
DMA::regs()
.ch(channel_number)
.ch(DMA_CHANNEL_OUTBOUND)
.out_int()
.ena()
.modify(|_, w| w.out_eof().bit(true));
@ -366,7 +378,7 @@ impl DmaBounce {
/// Receive a window of bytes into the current dst bounce buffer.
/// Finally, swaps the bounce buffers.
fn receive_window_blocking(&mut self) {
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];
@ -392,13 +404,15 @@ impl DmaBounce {
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())
.into_async()
.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();
// INBOUND_TRANSFER_FINISHED.wait().await;
transfer.wait_async().await.unwrap();
}
self.increase_window_counter(1);
@ -409,7 +423,7 @@ impl DmaBounce {
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.window_index_next = self.window_index_next % self.windows_len;
}
@ -419,7 +433,7 @@ impl DmaBounce {
Self::enable_interrupts();
// 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 transfer = self
@ -434,8 +448,15 @@ impl DmaBounce {
let mut windows_skipped_total = 0;
loop {
self.receive_window_blocking();
let windows_skipped = WINDOWS_SKIPPED.wait().await;
self.receive_window().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 {
self.increase_window_counter(windows_skipped);
@ -548,11 +569,12 @@ unsafe impl DmaTxBuffer for DmaTxBounceBuf {
}
static WINDOWS_SKIPPED: Signal<RawMutex, usize> = Signal::new();
// static INBOUND_TRANSFER_FINISHED: Signal<RawMutex, ()> = Signal::new();
#[handler(priority = Priority::Priority3)]
#[ram] // Improves performance.
fn dma_interrupt_handler() {
let interrupt = DMA::regs().ch(2).out_int();
fn dma_outbound_interrupt_handler() {
let interrupt = DMA::regs().ch(DMA_CHANNEL_OUTBOUND).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.
@ -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(
mut st7701s: St7701s<'static, Blocking>,
framebuffer: &'static mut Framebuffer,