DMA MCAL Module
Supported STM32 families: F4 Β· G4
Not supported: H5, H7, L4, U5
MCAL Driver (Dma_Lib)
The DMA module provides a complete MCAL-level abstraction for configuring, controlling, and monitoring STM32 DMA peripherals. It supports full control of DMA/DMAMUX routing, transfer modes, interrupts, and channel-level configuration.
π§© Architecture Overviewβ
Dma_Lib
βββ Dma_Port.h
βββ Dma_Types.h
βββ Dma.c / Dma.h
βββ Config/
Only Dma_Port.h shall be used by application code.
π DMA β DMAMUX β PERIPH Architectureβ
+----------------------+
| Peripheral (e.g.) |
| USART, SPI, ADC... |
+----------+-----------+
|
| DMA Request (ID)
v
+--------+---------+
| DMAMUX |
|------------------|
| Request Routing |
| Channel Mapping |
+--------+---------+
|
| Routed DMA Request
v
+--------+---------+
| DMA |
|------------------|
| Channel Control |
| Transfer Config |
| Interrupt Logic |
+------------------+
π Featuresβ
- DMA1/DMA2 support
- MemoryβPeripheral / MemoryβMemory transfers
- Normal & Circular modes
- 8/16/32-bit transfer sizes
- Peripheral & memory address increments
- Priority levels
- Full interrupt system with user callbacks
- DMAMUX request routing
π¦ Public API Summary
Full API available in Dma_Port.h.
Includes:
- Initialization / default configuration
- Transfer start/stop
- Channel activation
- Direction / mode / increments
- Peripheral & memory addresses
- Data count
- Priority
- Peripheral request selection
- Interrupt enabling/disabling
- Callback registration
βοΈ Configuration Structure
typedef struct
{
dma_PeriphId_t DmaPeriphId;
dma_ChannelId_t DmaChannel;
dma_Direction_t Direction;
dma_TransferMode_t TransferMode;
dma_PeriphAddr_t PeriphAddress;
dma_MemoryAddr_t MemoryAddress;
dma_PeriphAddrInc_t PeriphAddrIncrement;
dma_MemoryAddrInc_t MemoryAddrIncrement;
dma_PeriphTransferSize_t PeriphTransferSize;
dma_MemoryTransferSize_t MemoryTransferSize;
dma_DataCount_t DataCount;
dma_PeriphReqId_t PeripheralReqId;
dma_Priority_t Priority;
dma_IsrCallback TransferCompleteCallback;
dma_IsrCallback HalfTransferCallback;
dma_IsrCallback TransferErrorCallback;
} dma_ConfigStruct_t;
π§ͺ Example Initialization
Example 1 β USART1 TX DMAβ
dma_ConfigStruct_t cfg;
Dma_Get_DefaultConfig(&cfg);
cfg.DmaPeriphId = DMA_PERIPH_1;
cfg.DmaChannel = DMA_CHANNEL_4;
cfg.Direction = DMA_DIR_MEMORY_TO_PERIPH;
cfg.TransferMode = DMA_TRANSFER_MODE_NORMAL;
cfg.PeriphAddress = (uint32_t)&USART1->TDR;
cfg.MemoryAddress = (uint32_t)txBuffer;
cfg.PeriphAddrIncrement = DMA_PERIPH_ADDR_STATIC;
cfg.MemoryAddrIncrement = DMA_MEMORY_ADDR_INCREMENT;
cfg.PeriphTransferSize = DMA_PERIPH_TRANSFER_SIZE_BYTE;
cfg.MemoryTransferSize = DMA_MEMORY_TRANSFER_SIZE_BYTE;
cfg.DataCount = TX_LEN;
cfg.PeripheralReqId = DMA_REQ_USART1_TX;
cfg.Priority = DMA_PRIORITY_HIGH;
cfg.TransferCompleteCallback = TxDone;
cfg.TransferErrorCallback = TxError;
Dma_Init(&cfg);
Example 2 β Memory to Memoryβ
dma_ConfigStruct_t cfg;
Dma_Get_DefaultConfig(&cfg);
cfg.DmaPeriphId = DMA_PERIPH_1;
cfg.DmaChannel = DMA_CHANNEL_1;
cfg.Direction = DMA_DIR_MEMORY_TO_MEMORY;
cfg.TransferMode = DMA_TRANSFER_MODE_NORMAL;
cfg.PeriphAddress = (uint32_t)src;
cfg.MemoryAddress = (uint32_t)dst;
cfg.DataCount = 256;
cfg.PeripheralReqId = DMA_REQ_MEM2MEM;
Dma_Init(&cfg);
π Advanced Examples
π΅ Example A: Double Buffer Mode (Ping-Pong RAM Transfer)β
Double-buffer mode allows DMA to alternate between Buffer A and Buffer B
automatically without stopping transfers. This is ideal for:
- Audio processing
- Signal filtering
- Real-time data capture
- Zero-copy streaming
π§ How it works:β
- DMA loads data to
BufferA - When half-transfer interrupt fires β CPU processes BufferA while DMA writes BufferB
- When transfer-complete event fires β CPU processes BufferB while DMA writes BufferA
Example:β
uint16_t BufferA[128];
uint16_t BufferB[128];
void SetupDmaDoubleBuffer(void)
{
dma_ConfigStruct_t cfg;
Dma_Get_DefaultConfig(&cfg);
cfg.DmaPeriphId = DMA_PERIPH_1;
cfg.DmaChannel = DMA_CHANNEL_2;
cfg.Direction = DMA_DIR_PERIPH_TO_MEMORY;
cfg.TransferMode = DMA_TRANSFER_MODE_CIRCULAR;
cfg.PeriphAddress = (uint32_t)&ADC1->DR;
cfg.MemoryAddress = (uint32_t)BufferA;
cfg.DataCount = 128;
cfg.PeripheralReqId = DMA_REQ_ADC1;
cfg.Priority = DMA_PRIORITY_HIGH;
cfg.HalfTransferCallback = ProcessBufferA; // Called for Buffer A
cfg.TransferCompleteCallback = ProcessBufferB; // Called for Buffer B
Dma_Init(&cfg);
// Switch secondary buffer
LL_DMA_SetMemory2Address(DMA1, LL_DMA_CHANNEL_2, (uint32_t)BufferB);
}
π§© Use cases:β
| Use case | Why double-buffer helps |
|---|---|
| Audio codec input | Continuous PCM capture |
| Sensor streaming | Ensures no samples are lost |
| PWM feedback sampling | Deterministic acquisition |
| Real-time DSP | Ping-pong buffers eliminate copy delays |
π’ Example B: Circular ADC Sampling (Continuous Conversion)β
Circular DMA mode restarts the transfer automatically once it reaches the end. Best for continuous ADC sampling.
Example:β
#define ADC_BUF_SIZE 256
uint16_t AdcBuffer[ADC_BUF_SIZE];
void SetupCircularAdcDma(void)
{
dma_ConfigStruct_t cfg;
Dma_Get_DefaultConfig(&cfg);
cfg.DmaPeriphId = DMA_PERIPH_1;
cfg.DmaChannel = DMA_CHANNEL_1;
cfg.Direction = DMA_DIR_PERIPH_TO_MEMORY;
cfg.TransferMode = DMA_TRANSFER_MODE_CIRCULAR;
cfg.PeriphAddress = (uint32_t)&ADC1->DR;
cfg.MemoryAddress = (uint32_t)AdcBuffer;
cfg.DataCount = ADC_BUF_SIZE;
cfg.PeripheralReqId = DMA_REQ_ADC1;
cfg.HalfTransferCallback = OnAdcHalfFilled;
cfg.TransferCompleteCallback = OnAdcFilled;
Dma_Init(&cfg);
}
π§© Use cases:β
- Current measurement on SMPS
- Motor control FOC loop
- High-frequency ADC sampling
- Logging sensors without CPU load
π£ Example C: SPI DMA TX/RX Dual-Channel Modeβ
SPI requires:
- One DMA channel for TX
- One DMA channel for RX
This allows full-duplex DMA transfers.
Example:β
uint8_t txData[64];
uint8_t rxData[64];
void SetupSpiDma(void)
{
dma_ConfigStruct_t txCfg, rxCfg;
// TX channel
Dma_Get_DefaultConfig(&txCfg);
txCfg.DmaPeriphId = DMA_PERIPH_1;
txCfg.DmaChannel = DMA_CHANNEL_3;
txCfg.Direction = DMA_DIR_MEMORY_TO_PERIPH;
txCfg.PeriphAddress = (uint32_t)&SPI1->DR;
txCfg.MemoryAddress = (uint32_t)txData;
txCfg.DataCount = sizeof(txData);
txCfg.PeripheralReqId = DMA_REQ_SPI1_TX;
txCfg.TransferCompleteCallback = OnSpiTxDone;
// RX channel
Dma_Get_DefaultConfig(&rxCfg);
rxCfg.DmaPeriphId = DMA_PERIPH_1;
rxCfg.DmaChannel = DMA_CHANNEL_2;
rxCfg.Direction = DMA_DIR_PERIPH_TO_MEMORY;
rxCfg.PeriphAddress = (uint32_t)&SPI1->DR;
rxCfg.MemoryAddress = (uint32_t)rxData;
rxCfg.DataCount = sizeof(rxData);
rxCfg.PeripheralReqId = DMA_REQ_SPI1_RX;
rxCfg.TransferCompleteCallback = OnSpiRxDone;
Dma_Init(&txCfg);
Dma_Init(&rxCfg);
}
π§© Use cases:β
- SPI screen refresh
- High-speed sensors (IMU, gyroscope)
- Flash memory read/write
- Full-duplex communication (e.g., W5500 Ethernet chip)
π UML Sequence β DMA Transfer Flow
+---------+ +---------+ +------------+
| CPU | | DMAMUX | | DMA |
+---------+ +---------+ +------------+
| | |
| Configure DMA | |
|---------------->| |
| | Map Req β Ch |
| |----------------->|
| | |
| Enable Channel | |
|----------------------------------->|
| | |
| Transfer Starts |
|<-----------------------------------|
| | |
| Half Transfer Interrupt (HT) |
|<-----------------------------------|
| Process first half buffer |
|----------------------------------->|
| | |
| Transfer Complete Interrupt (TC) |
|<-----------------------------------|
| Process second buffer |
|----------------------------------->|
| | |
π CMake Integration
add_library(Dma_Lib
Dma_Port.h
Dma_Types.h
Dma.c
Dma.h
)
target_include_directories(Dma_Lib PUBLIC .)
Usage:
target_link_libraries(App PRIVATE Dma_Lib)
Licenseβ
This project is licensed under the Creative Commons AttributionβNonCommercial 4.0 International (CC BY-NC 4.0).
You are free to use, modify, and share this work for non-commercial purposes, provided appropriate credit is given.
See LICENSE.md for full terms or visit creativecommons.org/licenses/by-nc/4.0.
Authorsβ
- Mr.Nobody β embedbits.com
Contributions are welcome! Please open a pull request.