embassy-rs/embassy · critical
DMA: error on DMA@ channel
Error message
DMA: error on DMA@{:08x} channel {} What it means
The DMA (direct memory access) interrupt handler for classic STM32 DMA controllers panics when the transfer-error flag (TEIF) is set for the channel being serviced. A transfer error means the hardware could not complete the memory/peripheral access (bad address, bus fault, unsupported alignment/size), so the driver stops with a panic naming the DMA instance base address and channel number.
Solutions
- Check the buffer address/alignment: DMA targets must be in DMA-accessible RAM (not flash for writes, not CCMRAM on many chips) and aligned to the access size.
- Ensure the source/destination pointer and data width configuration match (e.g. u16 slice with 16-bit transfer).
- Verify the peripheral is enabled/clocked and the request multiplexer (DMAMUX) routes the correct request to the channel.
- Keep the buffer alive for the whole transfer (e.g. await the future or use a 'static/owned buffer).
Example fix
// before let buf = [0u8; 16]; // stack, may be invalid after return uart.write_dma(&buf).await; // after static BUF: [u8; 16] = [0u8; 16]; // DMA-accessible, alive during transfer uart.write_dma(&BUF).await;
Defensive patterns
Strategy: validation
Validate before calling
// Before starting a DMA transfer: let addr = buf.as_ptr() as u32; assert!(addr % core::mem::size_of::<T>() == 0, "DMA buffer misaligned"); // and ensure buf lives in DMA-accessible RAM (not flash/CCM for writes).
Type guard
fn dma_accessible<T>(buf: &[T]) -> bool {
let addr = buf.as_ptr() as u32;
// SRAM range example; adjust per chip
(0x2000_0000..0x2005_0000).contains(&addr)
} Try / catch
// This is a hard panic in an IRQ handler; it cannot be caught. // Guard instead: validate address and lifetime before write_dma/read_dma. assert!(dma_accessible(&buf), "buffer not DMA accessible");
Prevention
- Use static or long-lived buffers (linked into DMA-capable RAM) for DMA transfers.
- Keep buffers aligned to the transfer word size.
- Never move, free, or reuse a buffer while a transfer is in flight; await the transfer future.
- Check the chip reference manual for which RAM regions each DMA controller can address.
When it happens
Trigger: A DMA transfer completes with isr.teif(info.num % 4) set when on_irq runs — e.g. invalid source/destination address, misaligned buffer, or peripheral FIFO error during an active DMA channel transfer.
Common situations: Passing a slice in flash/CCM RAM not accessible by DMA; buffer not aligned to the transfer size; writing to a peripheral register with wrong word size; stack-allocated buffer moved/freed while transfer in flight; wrong DMA channel/mux routing configuration.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- DMA data error
- DMA: error on DMA_0 channel
- DMA: error on BDMA@ channel
- DMA: error on MDMA@ channel
- DMA: error on DMA_0 channel
AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10).
Data as JSON: /api/errors/bde9e98477bd604b.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-stm32/src/dma/dma_bdma.rs:26
use super::ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer};
use super::word::{Word, WordSize};
use super::{Channel, Dir, Increment, Request, STATE, info};
use crate::_generated::DmaChannel;
use crate::interrupt::typelevel::Interrupt;
use crate::rcc::WakeGuard;
use crate::{interrupt, pac};
pub(crate) unsafe fn on_irq(channel: DmaChannel) {
let info = info(channel);
let state = &STATE[channel as usize];
match info.dma {
#[cfg(dma)]
DmaInfo::Dma(r) => {
let cr = r.st(info.num).cr();
let isr = r.isr(info.num / 4).read();
if isr.teif(info.num % 4) {
panic!("DMA: error on DMA@{:08x} channel {}", r.as_ptr() as u32, info.num);
}
let mut activity = false;
if isr.htif(info.num % 4) && cr.read().htie() {
// Acknowledge half transfer complete interrupt
r.ifcr(info.num / 4).write(|w| w.set_htif(info.num % 4, true));
activity = true;
}
if isr.tcif(info.num % 4) && cr.read().tcie() {
// Acknowledge transfer complete interrupt
r.ifcr(info.num / 4).write(|w| w.set_tcif(info.num % 4, true));
state.complete_count.fetch_add(1, Ordering::Release);
activity = true;
}
if !activity {
return;
}
state.waker.wake();View on GitHub (pinned to 463a07b963)