Modul MCAL DMA
Podporované rodiny STM32: G4
Nepodporované: H5, H7, L4, U5
Ovladač MCAL (Dma_Lib)
Modul DMA poskytuje kompletní abstrakci na úrovni MCAL pro konfiguraci, řízení a monitorování periferií DMA na STM32. Podporuje plné řízení směrování DMA/DMAMUX, režimů přenosu, přerušení a konfigurace na úrovni kanálů.
🧩 Přehled architektury
Dma_Lib
├── Dma_Port.h
├── Dma_Types.h
├── Dma.c / Dma.h
└── Config/
Aplikační kód smí používat pouze Dma_Port.h.
🔄 Architektura DMA → DMAMUX → PERIPH
+----------------------+
| 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 |
+------------------+
🚀 Vlastnosti
- Podpora DMA1/DMA2
- Přenosy Memory⇄Peripheral / Memory⇄Memory
- Režimy Normal a Circular
- Velikosti přenosu 8/16/32 bitů
- Inkrementace adres periferie a paměti
- Úrovně priority
- Kompletní systém přerušení s uživatelskými callbacky
- Směrování požadavků pomocí DMAMUX
📦 Přehled veřejného API
Kompletní API je k dispozici v Dma_Port.h.
Zahrnuje:
- Inicializace / výchozí konfigurace
- Start/stop přenosu
- Aktivace kanálu
- Směr / režim / inkrementace
- Adresy periferie a paměti
- Počet dat
- Priorita
- Výběr požadavku periferie
- Povolení/zakázání přerušení
- Registrace callbacků
⚙️ Konfigurační struktura
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;
🧪 Příklad inicializace
Příklad 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);
Příklad 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);
🚀 Pokročilé příklady
🔵 Příklad A: Režim double buffer (přenos do RAM ping-pong)
Režim double buffer umožňuje DMA automaticky střídat Buffer A a Buffer B
bez zastavení přenosů. Je ideální pro:
- Zpracování zvuku
- Filtrování signálu
- Zachytávání dat v reálném čase
- Streaming bez kopírování (zero-copy)
🧠 Jak to funguje:
- DMA načítá data do
BufferA - Když se spustí přerušení po polovině přenosu → CPU zpracovává BufferA, zatímco DMA zapisuje do BufferB
- Když nastane událost dokončení přenosu → CPU zpracovává BufferB, zatímco DMA zapisuje do BufferA
Příklad:
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);
}
🧩 Případy použití:
| Případ použití | Proč double buffer pomáhá |
|---|---|
| Vstup audio kodeku | Nepřetržité zachytávání PCM |
| Streamování ze senzorů | Zajišťuje, že se neztratí žádné vzorky |
| Vzorkování zpětné vazby PWM | Deterministické získávání dat |
| DSP v reálném čase | Ping-pong buffery eliminují zpoždění způsobená kopírováním |
🟢 Příklad B: Cirkulární vzorkování ADC (nepřetržitý převod)
Cirkulární režim DMA automaticky restartuje přenos, jakmile dosáhne konce. Nejvhodnější pro nepřetržité vzorkování ADC.
Příklad:
#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);
}
🧩 Případy použití:
- Měření proudu u SMPS
- Smyčka FOC pro řízení motoru
- Vysokofrekvenční vzorkování ADC
- Logování ze senzorů bez zátěže CPU
🟣 Příklad C: Dvoukanálový režim SPI DMA TX/RX
SPI vyžaduje:
- Jeden kanál DMA pro TX
- Jeden kanál DMA pro RX
To umožňuje plně duplexní přenosy přes DMA.
Příklad:
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);
}
🧩 Případy použití:
- Obnovování SPI displeje
- Vysokorychlostní senzory (IMU, gyroskop)
- Čtení/zápis paměti flash
- Plně duplexní komunikace (např. ethernetový čip W5500)
📐 UML sekvenční diagram – průběh přenosu DMA
+---------+ +---------+ +------------+
| 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 |
|----------------------------------->|
| | |
🛠 Integrace s CMake
add_library(Dma_Lib
Dma_Port.h
Dma_Types.h
Dma.c
Dma.h
)
target_include_directories(Dma_Lib PUBLIC .)
Použití:
target_link_libraries(App PRIVATE Dma_Lib)
Licence
Tento projekt je licencován pod licencí Creative Commons Uveďte původ–Neužívejte komerčně 4.0 Mezinárodní (CC BY-NC 4.0).
Toto dílo můžete volně používat, upravovat a sdílet pro nekomerční účely za předpokladu, že uvedete odpovídající autorství.
Úplné podmínky najdete v LICENSE.md nebo navštivte creativecommons.org/licenses/by-nc/4.0.
Autoři
- Mr.Nobody — embedbits.com
Příspěvky jsou vítány! Otevřete prosím pull request.