Getting started with DMA

This article explains what DMA is and how to use it through examples.

1. What is DMA?

DMA stands for Direct Memory Access controller.
DMA is a bus master and system peripheral providing high-speed data transfers between peripherals and memory, as well as memory-to-memory.
Data can be quickly moved by DMA without any CPU action, keeping CPU resources free for other operations.

This article uses the STM32L476 device as an example. The STM32L476 device embeds 2 DMAs: DMA1 and DMA2.

Each channel is dedicated to managing memory access requests from one or more peripherals. The two DMA controllers have 14 channels in total. Each channel is dedicated to managing memory access requests from one or more peripherals. Each channel has an arbiter to handle priority between DMA requests.

DMA features DMA1 DMA2
Number of regular channels 7 7

1.1. Objectives

  • Learning how to setup DMA transfer in STM32CubeIDE.
  • Creating simple DMA memory-to-memory transfer from RAM to RAM and transfer with interrupt.

1.2. DMA memory-to-memory example overview

  • Using STM32CubeIDE and generating code with DMA.
  • Learning how to setup DMA using HAL.
  • Verifying the correct functionality by comparing transferred buffers.

2. Creating the project in STM32CubeIDE

The example below uses the NUCLEO-L476RG board[1] .

  • File > New > STM32 Project in main panel.

create STM32CubeIDE project.png

  • Select the NUCLEO-L476RG board using the Board Selector, as shown in the figure below:

Select NUCLEO-L476RG board.png

  • If not downloaded previously, the download of the STM32CubeL4 Cube library starts automatically. The download may take some time.
  • Save the project.

Setup menu GPIO.png

  • Pin configuration is not required for DMA.

stm32l476RG.png

3. Memory-to-memory mode

DMA channels may operate without being triggered by a request from a peripheral. This mode is called memory-to-memory mode, and is initiated by software.
It allows transfer from one address location to another without a hardware request. Once the channel is configured and enabled, the transfer starts immediately.
DMA Memory to memory.png

Warning white.png Warning
Memory-to-memory mode must not be used in circular mode.

3.1. DMA process workflow summary

  • At the beginning of the main program, HAL_DMA_Init() is called to reset all peripherals, initialize flash interface and systick.
  • HAL_DMA_Start() starts the DMA transfer after the configuration of source and destination addresses, as well as the length of data to be transferred.
  • HAL_DMA_PollForTransfer() polls for the end of current transfer. In this case a fixed timeout can be configured by user depending on his application.

DMA process.png

Once the transfer is completed, it is recommended to handle the return values to make sure the program worked as expected.

  • HAL_OK: DMA transfer was successfully finished and data was transferred to destination without error.
  • HAL_ERROR: Error occured during DMA transfer. You can use HAL_DMA_GetError for details.
  • HAL_BUSY: DMA transfer in progress, user can only abort the transfer

3.2. DMA M2M Configuration

In STM32CubeMX, click Clock Configuration. Your clock settings should look like this:
DMA clock tree.png


Now, click Pinout & Configuration > DMA > DMA1 Button Add.

DMA final config DMA1.png

Configure DMA as follows:

  • MEMTOMEM DMA request : DMA1 Channel 1
  • Normal mode
  • Increment source and destination addresses
  • Byte data width

DMA final DMA config 2.png

  • Check system DMA in System view :

DMA System view DMA Config.png

  • Generate the code by pressing Ctrl + S:
  • Open main.c using the Project Explorer: myproject / Src / main.c.
  • Create two buffers: the firtst for source data, and the second as destination buffer.
Info white.png Information
Insert your code between the tags /* USER CODE BEGIN 0 */ and /* USER CODE END 0 */
/* USER CODE BEGIN 0 */
uint8_t Buffer_Src[]={0,1,2,3,4,5,6,7,8,9};
uint8_t Buffer_Dest[10];
/* USER CODE END 0 */
Info white.png Information
Insert your code between the tags /* USER CODE BEGIN 2 */ and /* USER CODE END 2 */
/* USER CODE BEGIN 2 */
HAL_DMA_Start(&hdma_memtomem_dma1_channel1, (uint32_t) (Buffer_Src), (uint32_t) (Buffer_Dest), 10);
while(HAL_DMA_PollForTransfer(&hdma_memtomem_dma1_channel1, HAL_DMA_FULL_TRANSFER, 100) != HAL_OK)
{
  __NOP();
}
/* USER CODE END 2 */

3.3. Compile and flash

  • Click Debug button to run step by step Debug.png
  • Click Resume to continue execution Resume button.png
  • Click Suspend to stop execution Suspend button.png

Add Buffer_Src and Buffer_Dest to Expressions, on the right side of STM32CubeIDE, to monitor their value. Source data has been transferred to destination buffer.

DMA nucleo dma buffers.png

This method requires polling for transfer status. The method to achieve memory-to-memory transfer with interrupts is described below.

4. Memory-to-memory transfer with Interrupt

  • To configure DMA with Interrupt, follow the steps detailed in the Memory-to-memory mode section.
  • Enable DMA1 Channel 1 Global Interrupt In System Core > NVIC as shown in the figure below:

DMA interrupt.png

  • Now generate the code by pressing : Ctrl + S

4.1. HAL Library DMA with IT flow

  • DMA intialization is generated in main.c.
  • HAL_DMA_Start_IT: Start DMA buffer transfer
  • DMA1_Channel1_IRQHandler is generated in stm32f4xx_it.c: it indicates whether the DMA process is half/complete or an error was detected.
  • HAL_DMA_IRQHandler is defined in stm32f4xx_hal_dma.c: Process interrupt information.
  • DMA_XferCpltCallback: Data correctly transferred complete callback function.
  • DMA_XferErrorCallback: Error was detected Error callback function.

DMA workflow with IT.png

4.2. DMA M2M with IT Configuration

We will be using the same code as in DMA M2M Configuration.

Info white.png Information
Insert your code between the tags /* USER CODE BEGIN 0 */ and /* USER CODE END 0 */
/* USER CODE BEGIN 0 */
void XferCpltCallback(DMA_HandleTypeDef *hdma);
uint8_t Buffer_Src[]={0,1,2,3,4,5,6,7,8,9};
uint8_t Buffer_Dest[10];
/* USER CODE BEGIN 0 */
Info white.png Information
Insert your code between the tags /* USER CODE BEGIN 4 */ and /* USER CODE END 4 */
/* USER CODE BEGIN 4 */
void XferCpltCallback(DMA_HandleTypeDef *hdma)
{
  __NOP(); //Line reached only if transfer was successful. Toggle a breakpoint here
}
/* USER CODE END 4 */

Before we start the DMA with interrupt, we need to set the callback into DMA structure.
Then, it is possible to use the HAL_DMA_Start_IT to begin DMA transfer.

Info white.png Information
Insert your code between the tags /* USER CODE BEGIN 2 */ and /* USER CODE END 2 */
/* USER CODE BEGIN 2 */
hdma_memtomem_dma1_Channel1.XferCpltCallback=&XferCpltCallback;
HAL_DMA_Start_IT(&hdma_memtomem_dma1_Channel1,(uint32_t)Buffer_Src,(uint32_t)Buffer_Dest,10);
/* USER CODE END 2 */

4.3. Compile and flash

  • Click Debug to run step by step Debug.png
  • Click Resume to continue execution Resume button.png

If you toggled a breakpoint in your callback function, execution should stop automatically.
Add Buffer_Src and Buffer_Dest to Expressions, on the right side of STM32CubeIDE, to monitor their value. Source data has been transferred to destination buffer.

DMA nucleo dma buffers.png

5. References