Перейти до основного вмісту

Модуль DMA MCAL

Підтримувані родини STM32: G4
Не підтримуються: H5, H7, L4, U5

Драйвер MCAL (Dma_Lib)

Модуль DMA надає повну абстракцію рівня MCAL для налаштування, керування та моніторингу периферії DMA в STM32. Він підтримує повне керування маршрутизацією DMA/DMAMUX, режимами передачі, перериваннями та конфігурацією на рівні каналів.


🧩 Огляд архітектури​

Dma_Lib
├── Dma_Port.h
├── Dma_Types.h
├── Dma.c / Dma.h
└── Config/

Код застосунку має використовувати лише Dma_Port.h.


🔄 Архітектура 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 |
+------------------+

🚀 Можливості​

  • Підтримка DMA1/DMA2
  • Передачі пам'ять⇄периферія / пам'ять⇄пам'ять
  • Нормальний і циклічний (circular) режими
  • Розміри передачі 8/16/32 біти
  • Інкремент адрес периферії та пам'яті
  • Рівні пріоритету
  • Повна система переривань із користувацькими callback-ами
  • Маршрутизація запитів DMAMUX

📦 Підсумок публічного API

Повний API доступний у Dma_Port.h.

Включає:

  • Ініціалізацію / конфігурацію за замовчуванням
  • Запуск/зупинку передачі
  • Активацію каналу
  • Напрямок / режим / інкременти
  • Адреси периферії та пам'яті
  • Кількість даних
  • Пріоритет
  • Вибір запиту периферії
  • Вмикання/вимикання переривань
  • Реєстрацію callback-ів

⚙️ Структура конфігурації

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;

🧪 Приклад ініціалізації

Приклад 1 — DMA для USART1 TX​

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);

Приклад 2 — з пам'яті в пам'ять​

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);

🚀 Розширені приклади

🔵 Приклад A: Режим подвійного буфера (ping-pong передача в RAM)​

Режим подвійного буфера дає змогу DMA автоматично чергувати Buffer A і Buffer B
без зупинки передач. Це ідеально підходить для:

  • Обробки аудіо
  • Фільтрації сигналів
  • Захоплення даних у реальному часі
  • Потокової передачі без копіювання (zero-copy)

🧠 Як це працює:​

  • DMA завантажує дані в BufferA
  • Коли спрацьовує переривання половини передачі → CPU обробляє BufferA, поки DMA записує в BufferB
  • Коли спрацьовує подія завершення передачі → CPU обробляє BufferB, поки DMA записує в BufferA

Приклад:​

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);
}

🧩 Сценарії використання:​

СценарійЧим допомагає подвійний буфер
Вхід аудіокодекаБезперервне захоплення PCM
Потокова передача даних сенсораГарантує, що жоден відлік не буде втрачено
Вибірка зворотного зв'язку PWMДетерміноване збирання даних
DSP у реальному часіPing-pong буфери усувають затримки на копіювання

🟢 Приклад B: Циклічна вибірка ADC (безперервне перетворення)​

Циклічний режим DMA автоматично перезапускає передачу, коли вона досягає кінця. Найкраще підходить для безперервної вибірки ADC.

Приклад:​

#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);
}

🧩 Сценарії використання:​

  • Вимірювання струму в SMPS
  • Цикл FOC для керування двигуном
  • Високочастотна вибірка ADC
  • Логування даних сенсорів без навантаження на CPU

🟣 Приклад C: Двоканальний режим DMA TX/RX для SPI​

SPI потребує:

  • Одного каналу DMA для TX
  • Одного каналу DMA для RX

Це дозволяє виконувати повнодуплексні передачі DMA.

Приклад:​

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);
}

🧩 Сценарії використання:​

  • Оновлення SPI-екрана
  • Високошвидкісні сенсори (IMU, гіроскоп)
  • Читання/запис flash-пам'яті
  • Повнодуплексний зв'язок (наприклад, мікросхема Ethernet W5500)

📐 UML-діаграма послідовності — потік передачі 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 |
|----------------------------------->|
| | |

🛠 Інтеграція з CMake

add_library(Dma_Lib
Dma_Port.h
Dma_Types.h
Dma.c
Dma.h
)

target_include_directories(Dma_Lib PUBLIC .)

Використання:

target_link_libraries(App PRIVATE Dma_Lib)

Ліцензія​

Цей проєкт ліцензовано за Creative Commons Attribution–NonCommercial 4.0 International (CC BY-NC 4.0).

Ви вільні використовувати, змінювати та поширювати цю роботу в некомерційних цілях за умови належного зазначення авторства.

Повні умови дивіться у LICENSE.md або на creativecommons.org/licenses/by-nc/4.0.


Автори​

Внески вітаються! Будь ласка, відкрийте pull request.