Efficient UART Reception Programming Example with DMA and IDLE Based on STM32

Efficient UART Reception Programming Example with DMA and IDLE Based on STM32

The goal of this article: A FreeRTOS programming example based on STM32_H5

According to the description in this article, experiments should be able to be conducted on the corresponding hardware, and similar applications can be derived.

Prerequisites: Basic knowledge of C language, and a development environment with compilation and integration, such as: Keil uVision5

Used peripherals: USART1, USART2, GPIO, SysTick

HAL library version: STM32H5xx HAL Driver version number 1.1.0

STMCubeMX version: 6.10.0

Keil uVision5 version: V5.38.0.0

Experiment Objective

Document project learning, learn UART programming in the project, experience efficient serial reception, and design an experiment to implement serial reception.

Circuit Diagram for Application Scenario

In my application scenario, the content of the circuit diagram is as follows:

Efficient UART Reception Programming Example with DMA and IDLE Based on STM32
Insert image description here

I will connect the J4 interface, thus designing an experiment with one serial port for sending and another for receiving.

Three Programming Methods for UART

Based on the UART hardware structure, there are 3 programming methods:

① Polling method:

To send data, first write the data into the TDR register, then check if TDR is empty before returning. Alternatively, you can check if TDR is empty first, then write. To read data, first check if RDR is not empty, then read RDR to get the data.

② Interrupt method:

Using the interrupt method is more efficient and can avoid data loss during reception. To send data, enable the “TXE” interrupt (transmit register empty interrupt). In the TXE interrupt handler, take one data from the program’s send buffer and write it into TDR. When the TXE interrupt occurs again, take the next data from the program’s send buffer and write it into TDR.

For receiving data, enable the “RXNE” interrupt (receive register not empty) from the start. This way, when the UART receives one data, it will trigger an interrupt, and in the interrupt program, read RDR to get the data and store it in the program’s receive buffer. When the program wants to read serial data, it can directly read from the receive buffer. The “send buffer” and “receive buffer” involved here are particularly suitable for using a “circular buffer”.

③ DMA method:

When using the interrupt method, interrupts will occur during data transmission and reception, requiring the CPU to execute the interrupt handler. There is another method: DMA (Direct Memory Access), which can transfer data directly between two devices without CPU involvement.

Efficient UART Reception Programming Example with DMA and IDLE Based on STM32
Insert image description here

After setting up DMA (source, destination, address increment/decrement direction, data length for each read, number of reads), DMA will automatically transfer data between SRAM and UART:

① When sending: DMA gets data from SRAM and writes it into the UART’s TDR register

② When receiving: DMA gets data from the UART’s RDR register and writes it to SRAM

③ After the specified data transfer is complete, a DMA interrupt is triggered; during the data transfer process, there are no interrupts, and the CPU does not need to handle anything.

The HAL library APIs used are as follows:

// Polling method: // Send: HAL_UART_Transmit // Receive: HAL_UART_Receive // Interrupt method: // Send: HAL_UART_Transmit_IT HAL_UART_TxCpltCallback // Receive: HAL_UART_Receive_IT HAL_UART_RxCpltCallback // DMA method: // Send: HAL_UART_Transmit_DMA HAL_UART_TxHalfCpltCallback HAL_UART_TxCpltCallback // Receive: HAL_UART_Receive_DMA HAL_UART_RxHalfCpltCallback HAL_UART_RxCpltCallback // Error HAL_UART_ErrorCallback HAL_UART_ErrorCallback

IDLE

IDLE is defined as: no data is received on the bus within the time of one byte. When does the UART IDLE interrupt occur? The RxD pin is initially idle, so does the IDLE interrupt keep occurring? No. When we enable the IDLE interrupt, it does not occur immediately; rather, it occurs after at least one data is received and it is found that no new data has been received within the time of one byte. When we use DMA to receive data, it indeed improves CPU efficiency, but we cannot predict how much data will be received, and we want to process the received data as soon as possible. What should we do? For example, if I want to read 100 bytes of data, but after receiving 60 bytes, the other party stops sending data, what should we do? How do we determine that the data transmission has stopped? We can use the IDLE interrupt. In this case, there are 3 conditions for DMA transfer to end:

① The specified amount of data has been received, for example, 100 bytes of data, and HAL_UART_RxCpltCallback is called

② The bus is idle: HAL_UARTEx_RxEventCallback is called

③ An error occurred: HAL_UART_ErrorCallback is called

The functions that use the IDLE state for reception are:

// Polling method: // Receive: HAL_UARTEx_ReceiveToIdle // Callback function: // Determine if reception is complete based on the returned parameter RxLen, or if it returned due to idleness // Interrupt method: // Receive: HAL_UARTEx_ReceiveToIdle_IT // Callback function: Complete: HAL_UART_RxCpltCallback Aborted due to idleness: HAL_UARTEx_RxEventCallback // DMA method: // Receive: HAL_UARTEx_ReceiveToIdle_DMA // Callback function: Half transfer: HAL_UART_RxHalfCpltCallback Complete: HAL_UART_RxCpltCallback Aborted due to idleness: HAL_UARTEx_RxEventCallback // Error HAL_UART_ErrorCallback

Program Design

① Use the DMA + IDLE interrupt method to receive data, which will store the data in a temporary buffer;

② In the callback function: write the data from the temporary buffer into the queue, then re-enable DMA

③ APP reads the queue: if there is no data in the queue, it blocks.

Efficient UART Reception Programming Example with DMA and IDLE Based on STM32
Insert image description here

Serial Port Configuration

Open the configuration tool to configure the serial port

Efficient UART Reception Programming Example with DMA and IDLE Based on STM32
Insert image description here
Efficient UART Reception Programming Example with DMA and IDLE Based on STM32
Insert image description here

Configure Interrupts

Efficient UART Reception Programming Example with DMA and IDLE Based on STM32
Insert image description here

Configure DMA

Efficient UART Reception Programming Example with DMA and IDLE Based on STM32
Insert image description here
Efficient UART Reception Programming Example with DMA and IDLE Based on STM32
Insert image description here

Code Snippet

After the low-level configuration as described above, we write some code as follows:

  xTaskCreate(      CH1_UART2_TxTaskFunction, // Function pointer, task function      "ch1_uart2_tx_task",     // Task name      200,                      // Stack size, in words, 200 means 800 bytes      NULL,                     // Parameters passed to the task function      osPriorityNormal,         // Priority      NULL);                    // Task handle, used later to operate this task  xTaskCreate(      CH2_UART4_RxTaskFunction, // Function pointer, task function      "ch2_uart4_rx_task",      // Task name      200,                      // Stack size, in words, 200 means 800 bytes      NULL,                     // Parameters passed to the task function      osPriorityNormal,         // Priority      NULL);                    // Task handle, used later to operate this task

The code snippets for the two created tasks are as follows:

static void CH1_UART2_TxTaskFunction( void *pvParameters ) {    uint8_t c = 0;        while (1)    {      // send data      HAL_UART_Transmit_DMA (&huart2, &c, 1);      Wait_UART2_TxComplete(100);      vTaskDelay(500);      c++;    }} static void CH2_UART4_RxTaskFunction( void *pvParameters ) {    uint8_t c = 0;    int cnt = 0;    char buf[100];    HAL_StatusTypeDef err;    UART4_Rx_Start();    while (1)    {      // receive data      err = UART4_GetData(&c);      if(err == 0)      {        sprintf(buf, "Recv Data : 0x%02x, Cnt : %d", c, cnt++);        Draw_String(0, 0, buf, 0x0000ff00, 0);      }      else      {        HAL_UART_DMAStop(&huart4);      }    }}

The code snippets for starting sending and receiving are as follows:

int UART4_GetData(uint8_t *pData){    xQueueReceive(g_xUART4_RX_Queue, pData, portMAX_DELAY);    return 0;} void UART4_Rx_Start(void){    g_xUART4_RX_Queue = xQueueCreate(200, 1);    HAL_UARTEx_ReceiveToIdle_DMA(&huart4, g_uart4_rx_buf, 100);}

Compile, flash, and run, and you can see the LED on the development board flashing continuously, and data coming in on the LCD, indicating the experiment design was successful.

Efficient UART Reception Programming Example with DMA and IDLE Based on STM32
Insert image description here

The project experiment was successful, and I will continue to document experiments in the project. Thank you for your attention.

Test Project Used in This Article

https://download.csdn.net/download/weixin_44317448/89195002

Leave a Comment