Fundamentals 23 min read

Why Every Embedded Engineer Should Master DMA (Direct Memory Access)

This article explains the purpose, operation modes, configuration parameters, and register-level setup of DMA in STM32 microcontrollers, illustrating how DMA offloads data transfers from the CPU, improves performance, and provides concrete code examples for UART and memory‑to‑memory transfers.

Linux Tech Enthusiast
Linux Tech Enthusiast
Linux Tech Enthusiast
Why Every Embedded Engineer Should Master DMA (Direct Memory Access)

Definition and purpose

Direct Memory Access (DMA) copies data between peripherals and memory (or between memory regions) without CPU intervention, allowing the CPU to focus on computation and control tasks.

Transfer modes

Peripheral → memory

Memory → peripheral

Memory → memory

Peripheral → peripheral

All modes fundamentally copy data from a source address to a destination address.

Core transfer parameters

Four parameters are required for each DMA transfer: source address, destination address, transfer size (number of data items), and transfer mode (number of repetitions or circular operation).

STM32 DMA resources

High‑density STM32 devices provide two DMA controllers:

DMA1 – 7 channels

DMA2 – 5 channels

Each channel can be mapped to specific peripherals (TIM, ADC, SPI, I²C, USART, DAC, SDIO, etc.).

Arbitration and priority

Each channel has a programmable priority (very high, high, medium, low). When priorities are equal, the lower‑numbered channel wins. In large‑capacity parts DMA1 generally has higher priority than DMA2.

Data‑flow operation

Peripheral asserts a DMA request.

DMA controller acknowledges and starts the transfer.

DMA reads data from the peripheral (e.g., ADC) into its channel buffer.

DMA writes the data to SRAM via the AHB bus, completing the transfer without CPU involvement.

Transfer completion

A DMA transfer consists of three operations: read from the source address, write to the destination address, and decrement the DMA_CNDTRx register until it reaches zero.

Operation modes

Normal mode (DMA_Mode_Normal) – the transfer occurs once and stops.

Circular mode (DMA_Mode_Circular) – the transfer count is automatically reloaded, enabling continuous transfers.

Memory‑to‑memory transfers

Only DMA2 supports memory‑to‑memory mode. The MEM2MEM bit must be set, and circular mode cannot be used simultaneously.

Interrupts

Each channel can generate interrupts for half‑transfer, transfer‑complete, and error events. Status flags are read from DMA_ISR and cleared by writing zeros to the corresponding bits of DMA_IFCR.

Key registers

DMA_CPARx

– peripheral address. DMA_CMARx – memory address. DMA_CNDTRx – remaining data count (0‑65535). DMA_CCRx – controls direction, increment mode, data width, priority, circular mode, and enable.

Configuration procedure

Write the peripheral address to DMA_CPARx.

Write the memory address to DMA_CMARx.

Write the transfer count to DMA_CNDTRx.

Set channel priority in DMA_CCRx[PL].

Configure direction, circular mode, increment modes, data widths, and interrupt enables in DMA_CCRx.

Enable the channel by setting the ENABLE bit in DMA_CCRx.

Standard Peripheral Library functions (STM32 SPL)

DMA_DeInit(DMA_ChannelX)

– reset channel registers to default. DMA_Init(DMA_ChannelX, &DMA_InitStructure) – configure address, direction, size, increment, data width, mode, priority, and memory‑to‑memory flag. DMA_Cmd(DMA_ChannelX, ENABLE) – enable the channel. DMA_ITConfig(DMA_ChannelX, DMA_IT_TC, ENABLE) – enable transfer‑complete interrupt. DMA_SetCurrDataCounter(DMA_ChannelX, N) – set the transfer count. DMA_GetCurrDataCounter(DMA_ChannelX) – read the remaining count.

UART DMA example (STM32F1)

void dma_init(void) {
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base; // UART data register
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuff; // buffer address
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // memory → peripheral
    DMA_InitStructure.DMA_BufferSize = 500;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel4, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel4, ENABLE);
    DMA_SetCurrDataCounter(DMA1_Channel4, DMA1_MEM_LEN);
    DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);
}

void DMA1_Channel4_IRQHandler(void) {
    if (DMA_GetFlagStatus(DMA1_FLAG_TC4) == SET) {
        DMA_ClearFlag(DMA1_FLAG_TC4);
    }
}

int main(void) {
    uart_init(115200);
    for (uint16_t i = 0; i < 500; i++) {
        SendBuff[i] = 0xAF;
    }
    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
    while (1);
}

This code configures DMA1 Channel 4 to transfer a 500‑byte buffer to the USART1 transmitter without CPU‑driven byte‑by‑byte copying.

Data‑flow comparison (ADC example)

Without DMA : the CPU reads ADC data from the peripheral register via the AHB bus, then writes it to SRAM, consuming CPU cycles for each sample.

With DMA :

ADC asserts a DMA request.

DMA controller acknowledges the request.

DMA reads the ADC sample from the peripheral and stores it in its channel buffer.

DMA writes the sample to SRAM via the AHB bus. The CPU is not involved.

Arbitration details

Software sets channel priority in DMA_CCRx (four levels). If two requests have equal software priority, the hardware grants the request to the lower‑numbered channel.

In large‑capacity and connectivity devices, DMA1 has higher overall priority than DMA2.

DMA data streams (STM32F4/M4)

Each data stream provides a unidirectional link between a source and a destination. Streams support:

Normal transactions (memory ↔ peripheral, memory ↔ memory).

Double‑buffer mode (two memory pointers for ping‑pong buffering).

Transfer size is programmable up to 65535 items and is decremented after each transaction.

Transfer channel configuration

Set peripheral address in DMA_CPARx.

Set memory address in DMA_CMARx.

Set data count in DMA_CNDTRx.

Set priority in DMA_CCRx[PL].

Configure direction, circular mode, increment modes, data widths, and interrupt enables in DMA_CCRx.

Enable the channel by setting the ENABLE bit in DMA_CCRx.

During operation, the half‑transfer flag (HTIF) is set after half the data is moved; the transfer‑complete flag (TCIF) is set when the count reaches zero. Corresponding interrupt enable bits (HTIE, TCIE) generate IRQs.

Pointer increment mode

The PINC and MINC bits in DMA_SxCR control whether peripheral and memory addresses are incremented after each transfer. Increment step equals the configured data width (8, 16, or 32 bits).

Memory‑to‑memory mode

When the MEM2MEM bit in DMA_CCRx is set, the DMA channel starts a transfer without a peripheral request. The transfer ends when DMA_CNDTRx reaches zero. This mode is only available on DMA2; DMA1 does not support it. Memory‑to‑memory cannot be combined with circular mode.

DMA interrupt handling

Each channel can generate three interrupt sources: half‑transfer, transfer‑complete, and transfer error. The status register DMA_ISR holds the flags; writing zero to the corresponding bits of DMA_IFCR clears them. In many devices DMA2 Channel 4 and Channel 5 share an interrupt vector; other channels have dedicated vectors.

DMA register overview

DMA_ISR – read‑only status flags (HTIFx, TCIFx, TEIFx).

DMA_IFCR – write‑only flag‑clear register.

DMA_CCRx – control register (direction, priority, increment, data size, mode, enable).

DMA_CNDTRx – current transfer count (decrements automatically).

DMA_CPARx – peripheral address (e.g., 0x40013804 for USART1 data register).

DMA_CMARx – memory address (e.g., address of SendBuff array).

Library‑function configuration flow

Enable the DMA clock: RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); Initialize the channel with DMA_Init() (set addresses, direction, size, widths, mode, priority, MEM2MEM flag).

Enable the peripheral’s DMA request (e.g., USART_DMACmd()).

Enable the DMA channel: DMA_Cmd().

Optionally enable interrupts with DMA_ITConfig() and monitor status via DMA_GetFlagStatus() or DMA_GetCurrDataCounter().

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DMAMicrocontrollerDirect Memory AccessSTM32UARTPeripheral ProgrammingRegister Configuration
Linux Tech Enthusiast
Written by

Linux Tech Enthusiast

Focused on sharing practical Linux technology content, covering Linux fundamentals, applications, tools, as well as databases, operating systems, network security, and other technical knowledge.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.