In this tutorial, we’ll discuss the STM32 UART Interrupt DMA Polling methods using the HAL APIs. We’ll implement three STM32 UART Receive Examples Using Polling, Interrupt, and DMA to practice what we’ll learn in this tutorial. Without further ado, let’s get right into it!
Table of Contents
- STM32 UART Interrupt, DMA, Polling (Receive Modes)
- STM32 UART Receive (Rx) Examples Overview
- STM32 UART Receive (Rx) Polling Example
- STM32 UART Interrupt Example (Receive / Rx)
- STM32 UART DMA Example (Receive / Rx)
- Wrap Up
STM32 UART Interrupt, DMA, Polling (Receive Modes)
This tutorial is intended to be an example application for the DMA unit which we’ve discussed in the previous tutorial (DMA Tutorial). However, I’d like also to list down all the other possible ways in order to receive serial UART data with an STM32 microcontroller including the DMA method.
STM32 UART Polling
The polling method is essentially a blocking function being called from the main routine and it does block the CPU so it can’t proceed in the main task execution until a certain amount of UART data bytes are received. After receiving the required amount of data, the function ends and the CPU continues the main code execution.
Otherwise, and if the UART peripheral for some reason didn’t receive the expected amount of data, the function will keep blocking the CPU for a certain amount of time “time-out” after which it gets terminated and gives back control to the CPU to resume the main code.
This function for example
1 | HAL_UART_Receive (&huart1, UART1_rxBuffer, 12, 5000); |
Receives 12 bytes to the buffer. if it does that in 100uSec, it’s ok and the CPU will resume main code execution. If it doesn’t receive that amount of data, the CPU will be kept blocked waiting for 5sec until this function returns to the main context. Obviously, it’s not an efficient way to receive serial data despite the fact that it does actually work.
STM32 UART Interrupt
Using interrupt signals is a convenient way to achieve serial UART data reception. The CPU initializes the UART receive hardware so that it fires an interrupt signal whenever a new byte of data is received. And in the ISR code, we save the received data in a buffer for further processing.
This way of handling the UART data reception is a non-blocking way. As the CPU initiates the receiving process and it resumes executing the main code. Until an interrupt is received, then it freezes the main context and switches to the ISR handler to save the received byte to a buffer and switches back to the main context, and resumes the main code.
1 | HAL_UART_Receive_IT(&huart1, UART1_rxBuffer, 12); |
The above function initializes the UART receive process in interrupt mode (non-blocking) and upon completion, a callback function is called to notify the application and the processor knows that the received data buffer is now ready.
This method is a non-blocking one, yet very efficient. However, if you’re receiving a continuous stream of serial data, an insane number of interrupts per second will have to be handled by the CPU which is a huge load and a waste of time for it. In this situation, even the interrupt method will be inefficient and unnecessarily load the CPU and consume too much of the CPU time. In the worst scenario, there might be some data packets lost or dropped in the way. Now, it comes the time to use the DMA method.
STM32 UART DMA
Using the DMA unit in order to direct the received serial UART data from the UART peripheral directly to the memory is considered to be the most efficient way to do such a task. It requires no CPU intervention at all, you’ll have only to set it up and execute the main application code. The DMA will notify back the CPU upon reception completion and the received data buffer will be in the pre-programmed location.
The DMA can prioritize the channels, decide on the data width, and also the amount of data to be transferred up to 65536 bytes. Which is amazing in fact, and all you’ve got to do is to initialize it like this.
1 | HAL_UART_Receive_DMA (&huart1, UART1_rxBuffer, 12); |
In the following sections, we’ll create a simple STM32 UART receive examples using each of the 3 methods.
Make sure you’ve completed the STM32 UART Tutorial for more information about how it works in detail.
STM32 UART Receive (Rx) Examples Overview
In the next sections of this tutorial, we’ll implement the following 3 example projects:
- Application1: Setup an STM32 UART Receive Example With Polling
- Application2: Setup an STM32 UART Receive Example With Interrupt
- Application3: Setup an STM32 UART Receive Example With DMA
Each application will have to receive 12 bytes of data from the PC terminal and echo back the received data buffer. So in the testing, we’ll expect to see back whatever data we send through the terminal.
STM32 UART Receive (Rx) Polling Example
Step #1
Start a New CubeMX Project & Set up The RCC Clock
Step #2
Setup The UART1 Peripheral in Async Mode @ 9600bps
Step #3
Generate Code & Open CubeIDE or Any Other IDE You’re Using
STM32 UART Polling Example Code (Receive) – main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include "main.h" uint8_t UART1_rxBuffer[12] = {0}; UART_HandleTypeDef huart1; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); HAL_UART_Receive (&huart1, UART1_rxBuffer, 12, 5000); HAL_UART_Transmit(&huart1, UART1_rxBuffer, 12, 100); while (1) { } } |
STM32 UART Polling Example Testing (Receive)
STM32 UART Interrupt Example (Receive / Rx)
Step #1
Start New CubeMX Project & Setup The RCC Clock
Step #2
Setup The UART1 Peripheral in Async Mode @ 9600bps
Step #3
Enable The UART1 Global Interrupt From NVIC Controller Tab
Step #4
Generate Code & Open CubeIDE or Any Other IDE You’re Using
STM32 UART Interrupt Example Code (Receive) – main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include "main.h" uint8_t UART1_rxBuffer[12] = {0}; UART_HandleTypeDef huart1; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); HAL_UART_Receive_IT (&huart1, UART1_rxBuffer, 12); while (1) { } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { HAL_UART_Transmit(&huart1, UART1_rxBuffer, 12, 100); HAL_UART_Receive_IT(&huart1, UART1_rxBuffer, 12); } |
STM32 UART Interrupt Example Testing (Receive)
STM32 UART DMA Example (Receive / Rx)
Step #1
Start New CubeMX Project & Setup The RCC Clock
Step #2
Setup The UART1 Peripheral in Async Mode @ 9600bps
Step #3
Add A DMA Channel For UART RX From The DMA Tab
Step #4
Generate Code & Open CubeIDE or Any Other IDE You’re Using
STM32 UART DMA Example Code (Receive) – main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #include "main.h" uint8_t UART1_rxBuffer[12] = {0}; UART_HandleTypeDef huart1; DMA_HandleTypeDef hdma_usart1_rx; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_USART1_UART_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_USART1_UART_Init(); HAL_UART_Receive_DMA (&huart1, UART1_rxBuffer, 12); while (1) { } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { HAL_UART_Transmit(&huart1, UART1_rxBuffer, 12, 100); HAL_UART_Receive_DMA(&huart1, UART1_rxBuffer, 12); } |
STM32 UART DMA Example Testing (Receive)
Wrap Up
In conclusion, we’ve explored how to set up the STM32 UART Interrupt, DMA, and Polling-based Receiver applications. The STM32 UART Receiver examples we’ve created in this tutorial should help you get started with your STM32 UART-based projects.
You can build on top of the example provided in this tutorial and/or explore the other parts of this STM32 UART tutorial series linked below.
Thanks a lot for this article.
I usually use uart with interrupt on each byte, as the length of the data is unknown.
Sometimes it is only 1 byte, and other times it can be 50 bytes.
I know the end of the transmission ends with \n, as l usually design both sides..
How can l use DMA with unknown data length?
Thank again,
Lior
I’m really sorry for the late response. But what you’re talking about seems to be software defined thing which means we can use whatever hardware features available for us and implement this functionality.
In the near future, I’ll be publishing an article called “BCM Communication”. Which is going to be something like what you’re asking about. It’s a software communication module that sends char-based messages of any length. Each data packet will have the info of the data bytes count, header, footer, and checksum for error detection.
It’s a kind of basic software protocol that the user can include and use in his/her project.
As usual, it’s going to be configurable to different data rates and hardware layer. the messages can be sent/received over UART or SPI. More features can be added in the future. We’ll see how this piece of software will turn out to be.
Kind regards,
Khaled M.
Your article helped me a lot. Thanks for that ????
I had one doubt though, why you have used HAL_UART_Receive_IT()/_DMA() in the Interrupt Subroutine (ISR). Won’t the Transmit function be enough in the ISR, as we already have Receive function in the main().
I am referring to the DMA and Interrupt codes.
Got UART DMA working with CubeIDE 1.7.0 and Nucleo-H723ZG with difficulty! At first the code echoed back zeros. The CubeMX code generator issues the UART init before the DMA init and I noticed your code did the reverse, so I changed the order and it worked! Why? Is this an MX error?
Yeah, I believe it was generated in that order by Cube MX