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.

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:
- STM32 UART Configuration and Transmission in Blocking Mode
- STM32 UART Receive Data in Blocking Mode
- STM32 UART Receive using DMA
- STM32 UART Receive using Idle Line
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.
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.
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.
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.
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.
- 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.
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.
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.
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
Yes, you can use both DMA and interrupt-based UART transmission in the same project, but typically not on the same UART instance at the same time. For example, you can use DMA for transmitting data over UART2 and use interrupt mode for UART1. This allows more flexibility based on the data size and timing needs of each communication channel.
When using DMA or interrupt modes, it's important to handle UART errors such as framing errors, overrun errors, or noise errors. You can enable the appropriate error interrupts in STM32CubeMX and implement the HAL_UART_ErrorCallback()
function in your code to manage these issues gracefully.
Yes, STM32 HAL provides functions like HAL_UART_DMAPause()
and HAL_UART_DMAResume()
that let you pause and resume UART DMA transmission. This can be useful when you need to temporarily halt data flow during critical operations or system reconfiguration.
If you modify the data buffer while DMA is still transmitting, you may encounter corrupted data unless you manage buffer access carefully. To avoid this, use techniques like double buffering or update only the half of the buffer that has already been transmitted (as shown in the circular mode example using half-transfer and full-transfer callbacks).
Absolutely. UART DMA receive mode is commonly used for high-speed or continuous data reception. You can combine it with a circular buffer or implement a ring buffer to handle incoming data efficiently without data loss.
Similar Posts ...
Support Us by Disabling Adblock
We rely on ad revenue to keep Controllerstech free and regularly updated. If you enjoy the content and find it helpful, please consider whitelisting our website in your ad blocker.
We promise to keep ads minimal and non-intrusive.
Thank you for your support! 💙