DMA in the IDE

Blinking an LED with STM32 DMA M2M Mode

The following program is for the Adafruit STM32F405 Feather board. It is a simple demonstration of using DMA to blink the onboard LED. DMA seemed very mysterious and complicated to me. That is, until I completed this excellent uDemy online course. Please note, I am not affiliated with this course or the uDemy website in any manner.

This, and the following four posts (DMA UART Receive, DMA UART Transmit, DMA ADC, and DMA Register-Level Programming) will present the exercises from the above course, as I have adapted them to the Arduino IDE and the STM32F405 Feather board. Many details inside the programs needed to be changed to adapt them to both the IDE and STM32F405 feather. The original course and the exercises are specific to the STM32F446RE Nucleo Board.

However, you really should take the course to get all of the theory and details I will be skipping over. The original source code for the exercises from the online course are located here.

STM32 DMA Overview

Direct Memory Access (DMA) is a component of the microcontroller that can be used in conjunction with the main microprocessor in order to offload memory transfer operations. This significantly reduces the CPU load. The DMA controller can perform memory to memory data transfers as well as peripheral to memory transfers and/or vice versa. DMA with a CPU can accelerate its throughput by orders of magnitude. Data can be quickly moved by DMA without any CPU action. This keeps CPU resources free for other operations.

The STM32F405 MCU has two DMA controllers.

Each DMA transfer consists of three operations:

  1. Loading of data from the peripheral data register or a location in memory addressed through a peripheral/memory address register.
  2. Storage of the data loaded to the peripheral data register or a location in memory.
  3. Post-decrementing of the DMA counter, which contains the number of transactions that still need to be performed.

DMA Memory-To-Memory

The DMA can also work without a request from a peripheral. This mode is called Memory to Memory mode.

DMA Interrupts

An interrupt can be produced on a Half-transfer, Transfer complete, or Transfer error for each DMA channel. Separate interrupt enable bits are available for flexibility.

The easiest method to setup the registers for a DMA operation is by using the CubeMX software and then transferring the required portions of that code into the Arduino IDE. The HAL APIs which configure the DMA units and programmatically set the buffer lengths, DMA source, destination, and required details are fully usable inside the IDE.

The only issue is to properly integrate the DMA code with the existing STM32 Arduino software. One must remember that many of the MCU peripheral items are used behind the scenes (if you will) by the Arduino code.

The Code

// Use DMA M2M Mode to blink LED.
//
// STM32F405 Feather, toggle ODR for GPIOC, Pin 1 on AHB1 bus.
//
// To enable setting callback, change the following option: 
//   #define USE_HAL_UART_REGISTER_CALLBACKS 1U
// In the following file:
//   stm32f4xx_hal_conf_default.h
// File is located here: 
//   C:\Users\USER\AppData\Local\Arduino15\packages\STM32\hardware\stm32\1.9.0\system\STM32F4xx
// The callback is not used in this example.

DMA_HandleTypeDef hDMA2_Stream0;

// DMA IRQ handler
extern "C" void DMA2_Stream0_IRQHandler(void) { HAL_DMA_IRQHandler(&hDMA2_Stream0); }

// DMA transfer complete callback.
extern "C" void dmaXferCompleteCallback(DMA_HandleTypeDef *pHandle) { UNUSED(pHandle); }

static void dmaInit(void) {
  __HAL_RCC_DMA2_CLK_ENABLE();

  hDMA2_Stream0.Instance = DMA2_Stream0;
  hDMA2_Stream0.Init.Channel = DMA_CHANNEL_0;
  hDMA2_Stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;
  hDMA2_Stream0.Init.PeriphInc = DMA_PINC_DISABLE;
  hDMA2_Stream0.Init.MemInc = DMA_MINC_DISABLE;
  hDMA2_Stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  hDMA2_Stream0.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  hDMA2_Stream0.Init.Mode = DMA_NORMAL;
  hDMA2_Stream0.Init.Priority = DMA_PRIORITY_LOW;
  hDMA2_Stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
  hDMA2_Stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
  hDMA2_Stream0.Init.MemBurst = DMA_MBURST_SINGLE;
  hDMA2_Stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;
  if (HAL_DMA_Init(&hDMA2_Stream0) != HAL_OK)
    while(1);

  HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}

void setup() {
  pinMode(PC1, OUTPUT);
  dmaInit();
}

void loop() {
  //HAL_DMA_RegisterCallback(&hDMA2_Stream0, HAL_DMA_XFER_CPLT_CB_ID, &dmaXferCompleteCallback);

  while (1)   {
    uint8_t ledPin[2] = { 0b00000010, 0x00 }; // GPIOC, pin 1.
    
    HAL_DMA_Start_IT(&hDMA2_Stream0, (uint32_t)&ledPin[0], (uint32_t)&GPIOC->ODR, 1);
    delay(1000);
    HAL_DMA_Start_IT(&hDMA2_Stream0, (uint32_t)&ledPin[1], (uint32_t)&GPIOC->ODR, 1);
    delay(1000);
  }
}

About Jim Eli

µC experimenter
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s