Home STM32 STM32 HAL STM32 UART DMA & Interrupt Tutorial | HAL_UART_Transmit_DMA Example

How to use Interrupt & DMA to Transmit Data

Welcome to this STM32 UART tutorial using DMA and interrupts. In this guide, you’ll learn how to use HAL_UART_Transmit_DMA and interrupt-driven UART communication in STM32. We’ll explore the advantages of using background transmission methods over blocking mode, and demonstrate practical use cases for STM32 UART DMA, UART2, and interrupt-based methods. This is ideal for anyone working with uart stm32 communication on STM32F4 or STM32CubeMX.

How to use Interrupt & DMA to Transmit Data

This is the second tutorial in our series on the UART peripheral in STM32 microcontrollers. In this tutorial, we’ll focus on using interrupts and DMA to transmit data over UART. In the previous tutorial, we discussed how to configure UART and send data using the blocking mode. Here, we’ll explain why blocking mode is not ideal for sending large amounts of data, and how using interrupt or DMA methods can make the process more efficient.

You should also take a look at the following:

VIDEO TUTORIAL

You can check the video to see the complete explanation and working of this project.

Check out the Video Below

Introducing the DMA Peripheral in STM32

DMA (Direct Memory Access) in STM32 allows data to be transferred between memory and peripherals—like UART—without involving the CPU. When using DMA with the UART peripheral, data transmission becomes faster and more efficient, especially during large or continuous data transfers. This helps keep the CPU free to perform other tasks, improving overall system performance.

Important Features of UART DMA in STM32:

  • Non-blocking Transmission – Data is sent in the background without halting CPU operations.
  • Faster Data Transfer – Ideal for sending or receiving large data buffers over UART.
  • Low CPU Usage – Offloads data handling from the CPU, allowing better multitasking.
  • Support for Circular Mode – Enables continuous transmission or reception, perfect for real-time applications like logging or streaming.

Sending Large data in blocking mode

Let’s assume a case where we want to send 10 kilobytes of data continuously via the UART. We will also blink a LED periodically indicating a process that needs to run at a fixed interval. Below is the code to send the data in the blocking mode.

uint8_t TxData[10240];
int main()
{
  ...
  /* Fill array with some data */
  for (uint32_t i=0; i<10240; i++)
{
	  TxData[i] = i&(0xff);
  }
  while (1)
  {
    HAL_UART_Transmit(&huart2, TxData, 10240, HAL_MAX_DELAY);
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
    HAL_Delay(500);
  }
}

Here we defined a 10 KB array. Then filled the array with some data in the main function. The data is sent to the UART in the while loop and the timeout is set to HAL_MAX_DELAY. This basically means the function will never timeout and it can take as much time as it wants to send the data. After the data is sent, the LED will toggle and the loop repeats every 500ms.

As I mentioned the LED blinking can be assumed as a process which runs periodically. The data transmission should not affect the rate of this process.

But this does not takes pace as expected. Below is the gif showing the LED blinking rate.

LED blinking on STM32

You can see the LED is taking more than 500 ms to blink. This is because the UART transfer is taking significant amount of time and it blocks the CPU while transmitting the data. This affects other processes as they have to wait for the CPU.

This issue generally arise when transferring large amount of data. Small data is transferred in an instant, so we don’t need to worry about that.

To solve this issue, the data needs to be transmitted in the background while the CPU can process other tasks. This is why we use interrupt or DMA in the first place.

STM32 UART DMA & Interrupt vs Blocking Transmission

In this section, we compare STM32 UART blocking, interrupt, and DMA-based transmission methods. For large data transfers or real-time tasks, using DMA or interrupt-based transmission can free the CPU and keep timing accurate for parallel tasks.

Using UART Interrupt to transmit Data

Interrupt can be used to send the data to the UART in the background. While the transmission is taking place, the CPU can process other tasks as well. Note that even the data transmission takes place in the background, the data is still transmitted by the CPU, so it still puts the load on the CPU.

Cubemx Configuration

Below is the cubeMX configuration for the interrupt.

STM32 UART Interrupt Configuration

I am keeping the same configuration as used in the previous tutorial. Here we will just enable the UART interrupt in the NVIC tab.

The Code

The data sent using interrupt is transferred in the background. Once the data is sent completely, an interrupt will trigger and the transfer complete callback is called.

int isSent = 1;
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	isSent = 1;
	countinterrupt++;
}

Inside the callback we will increment the interrupt counter. This keeps track of how many times the callback was called. We also set the variable isSent indicating that the data has been transferred. This is to make sure that CPU sends the data again only after the previous data has been transferred.

To do so, we will guard the Transmit function with the condition as shown below.

int main()
{
  ...
  ...
  while (1)
  {
    if (isSent == 1)
    {
	  HAL_UART_Transmit_IT(&huart2, TxData, 10240);
	  isSent = 0;
    }
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
    HAL_Delay(500);
    countloop++;
  }
}

The HAL_UART_Transmit_IT is used to transfer the data in the interrupt mode. The function only runs if the previous data has been transmitted. We toggle the LED at the normal rate of 500ms and also increment the loop counter. This keeps track of how many times the while loop runs.

Result

Below is the image showing the loop counters after running the code for few seconds.

loop counter values

As you can see the interrupt counter is half of that of the loop counter. Actually the loop is running every 500ms and the interrupt is only triggered once the data is sent completely, which is taking around 1 second.

Gif Result

Below is the gif showing the LED blinking.

STM32 LED blinking

The LED blinks every 500ms. This means the CPU is not blocked for the transmission and it can process the rest of the tasks while the data is being transmitted in the background.

Conclusion

Interrupt-based transmission is suitable for sending large amounts of data while allowing the CPU to continue executing other tasks. However, since the CPU is still responsible for handling the data transfer through interrupt service routines, it adds to the overall processing load. Therefore, using interrupts is appropriate when data is transmitted occasionally. For continuous or high-frequency data transmission, it is more efficient to use DMA, as it offloads the transfer process from the CPU entirely, ensuring better system performance.

Using UART DMA to Transmit Data

The Direct memory access (DMA) is used to provide high-speed data transfers between peripherals and memory and between memory and memory. Data can be quickly moved by the DMA without any CPU action. This keeps CPU resources free for other operations.

CubeMx Configuration

Below is the cubeMX configuration for the DMA mode.

STM32 UART DMA Configuration
  • The DMA Request is set to USART_TX as we want to use DMA to send the data.
  • The DMA Stream is selected automatically by the cubeMX. It is basically the DMA channel that we are going to use for the data transmission.
  • The direction is Memory to Peripheral as we are sending the data from the MCU memory to the UART peripheral.
  • The Data width is set to Byte as we are sending one byte data to the UART.
  • After sending 1 byte, the memory address will increment so it can send the next data byte.

The DMA can work in 2 modes, Normal and Circular.

  • In Normal mode, the DMA sends the data once and then stops automatically. This is just like how we used the interrupt.
  • In circular mode, The data flow is continuous. The source address, the destination address and the number of data to be transferred are automatically reloaded after the transfer completes. This allows DMA to continuously transmit the same data until we issue the stop command manually.

Sending data in normal DMA mode

In Normal mode, the DMA stops after sending the data completely. An interrupt will trigger and the transfer complete callback is called. We will send data the same way we did in case of the interrupt.

int isSent = 1;
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	isSent = 1;
	countinterrupt++;
}

int main()
{
  ...
  ...
  while (1)
  {
    if (isSent == 1)
    {
	  HAL_UART_Transmit_DMA(&huart2, TxData, 10240);
	  isSent = 0;
    }
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
    HAL_Delay(500);
    countloop++;
  }
}

The HAL_UART_Transmit_DMA is used to transfer the data in the DMA mode. The function only runs if the previous data has been transmitted. We toggle the LED at the normal rate of 500ms and also increment the loop counter. This keeps track of how many times the while loop runs.

Below is the image showing the loop counters after running the code for few seconds.

loop counter

As you can see the interrupt counter is half of that of the loop counter. Actually the loop is running every 500ms and the interrupt is only triggered once the data is sent completely, which is taking around 1 second.

Below is the gif showing the LED blinking.

LED blinking on STM32

The LED blinks every 500ms. This means the CPU is not blocked for the transmission and it can process the rest of the tasks while the data is being transmitted in the background. Since DMA is being used to transfer the data, the CPU load is also reduced.

Sending data in circular DMA mode

We first need to select the Circular mode in the cubeMX configuration as shown below.

STM32 UART DMA Circular Mode Configuration

In circular mode the Data is transmitted continuously. Once all the data has been sent, the DMA reloads the same data again and sends it. We can also update the data in the runtime and use the DMA more efficiently.

Actually the 2 interrupts gets triggered while transmitting the data via the DMA. The Half Transmit Complete callback is called when the DMA finished transferring half the data and the Transmit complete callback is called when the DMA transferred all the data. For example, if we are transferring 10KB data, the HT Callback will be called when DMA finished transferring 5KB and the TC Callback will be called when DMA finished transferring 10KB.

When the HT callback is called, the DMA is still transferring the 2nd half of the buffer. In that time, we can load a new data to the first half of the TX buffer.

int indx = 49;  // char '1'

void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)
{
	  for (uint32_t i=0; i<5120; i++)
	  {
		  TxData[i] = indx;
	  }
	  indx++;
}

Similarly, when the TC callback is called, the DMA is start transferring the 1st half of the buffer. In that time, we can load a new data to the 2nd half of the TX buffer.

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	  for (uint32_t i=5120; i<10240; i++)
	  {
		  TxData[i] = indx;
	  }
	  indx++;

	  if (indx>=60)
	  {
		  HAL_UART_DMAStop(&huart2);
	  }
	isSent = 1;
	countinterrupt++;
}

This way we are loading a new data to the DMA in parallel to the previous data transfer.

This loop keeps running till we issue the stop command. Here we will set a condition and stop the DMA when the condition is satisfied.

We still need to call the DMA once in the main function, so that everything can start from there.

int main ()
{
  /* Fill array with some data */
  for (uint32_t i=0; i<10240; i++)
  {
	  TxData[i] = i&(0xff);
  }

  HAL_UART_Transmit_DMA(&huart2, TxData, 10240);



  while (1)
  {
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
    HAL_Delay(500);
    countloop++;
  }
}

Conclusion

Use DMA in Normal Mode for occasional or fixed-size data transfers, where data is sent only once or at regular intervals.
Use DMA in Circular Mode for continuous or repeated transmissions, such as real-time streaming or background data logging.

Choosing the right mode ensures efficient UART communication and better CPU performance in STM32 applications.

This tutorial explained how to implement UART2 DMA and interrupt communication using STM32 HAL. We also explored how HAL_UART_Transmit_DMA improves performance in data-heavy or time-critical applications. If you’re looking for stm32 uart dma transmit or stm32 uart interrupt example references, this guide should be your go-to resource.

PROJECT DOWNLOAD

Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

Project FAQs

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments