STM32 CubeMX Configuration Implementation Reference Here.
1. The Principle and Solution of Unfixed-length Data Receiving
In STM32, UART is the most common mode of communication - it receives one byte at a time, and we can use polling, but for some data sent in a fixed time, polling is sometimes not flexible enough. You can also use interruption, such as interrupting every byte once, to consume system resources. Especially in HAL libraries, there are many programs running from interrupt to callback function. Frequent interrupts may cause data overflow.
To avoid this problem, we use the specified length of data to receive, and then call the callback function, which allows us to receive large data, but this situation results in the requirement that each packet is fixed length.
In order to solve the above problems, the most commonly used method on the Internet is to use idle interrupt, that is, when the serial port is idle, trigger an interrupt and notify the kernel that the transportation is completed. The judgment of serial idle interruption is that when the serial port begins to receive data, there is no data transmission within the time of detecting 1 byte data, then the serial port is considered idle. Since our kernel does not accept serial data when it receives data at the serial port until it is idle, we also need to use DMA to assist us in transferring data to the designated place and notify the kernel to process it when the data transfer is completed.
2. Implementation process
- Open Serial Free Interrupt: Enable Serial Interrupt during Program Initialization
- Define Serial Free Interrupt Processing Function: Add Serial Space Interrupt Processing Function to Serial Interrupt
- Define the idle interrupt callback function of serial port: to mark the completion of data reception and calculate the length of data received
2.1 Open Serial Free Interrupt
First, when we initialize, we can make the serial port idle interrupt, so that when the serial port interrupt, MCU can call the serial interrupt function.
In the MX_USART1_UART_Init(void) function in the main.c file:
static void MX_USART1_UART_Init(void) { // The initialization part of serial port protocol is omitted. __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // Enable serial port idle interrupt }
When this function is added to the project, every time data is sent, the USART1_IRQHandler() interrupt service function is called. You can insert a print statement into the function to verify whether the idle interrupt is normal.
2.2 Configuration of DMA Receiver
Although we use CubeMx to configure DMA, of course, we only configure the mode of DMA to serial port to memory, but we need to further develop in the program, which memory the DMA will be transported to. We build an array to store the serial port data transported by DMA, and use the HAL_UART_Receive_DMA() function to configure the specific code, such as As follows:
... ... uint8_t receive_buff[255]; //Define the receive array ... ... void main(void) { .. ... __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); HAL_UART_Receive_DMA(&huart1, (uint8_t*)receive_buff, 255); //Set up the DMA transmission to move the data of serial port 1 to recvive_buff, 255 bytes at a time while(1) { .. .. } } ... ...
2.3 Add interrupt handler and callback functions
After CubeMx operates according to the above, the processing function HAL_UART_IRQHandler() of serial interrupt has been used in the generated project, but the processing of idle interrupt has not been found in the function. So we add an additional function: USER_UART_IRQHandler() (user encapsulation function). The complete code after adding is as follows:
stm32f0xx_it.c :
void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ USER_UART_IRQHandler(&huart1); //New function added to handle idle serial interrupts /* USER CODE END USART1_IRQn 1 */ }
Define USER_UART_IRQHandler():
usart.c:
void USER_UART_IRQHandler(UART_HandleTypeDef *huart) { if(USART1 == huart1.Instance) //Determine whether it is serial port 1 { if(RESET != __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) //Judging whether it is idle interruption { __HAL_UART_CLEAR_IDLEFLAG(&huart1); //Clear idle interrupt sign (otherwise it will continue to enter interrupt) printf("\r\nUART1 Idle IQR Detected\r\n"); USAR_UART_IDLECallback(huart); //Call interrupt handler } } }
So far, we have been able to respond to serial interrupts properly and call a new function: USAR_UART_IDLECallback(), which is a callback function specially designed to handle idle interrupts. It is defined as follows (written in the usart.c file as usual):
//Declare external variables extern uint8_t receive_buff[255]; void USAR_UART_IDLECallback(UART_HandleTypeDef *huart) { //Stop this DMA transmission HAL_UART_DMAStop(&huart1); //Calculate the length of the received data uint8_t data_length = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); //Test function: Print out the received data printf("Receive Data(length = %d): ",data_length); HAL_UART_Transmit(&huart1,receive_buff,data_length,0x200); printf("\r\n"); //Zero Receiving Buffer memset(receive_buff,0,data_length); data_length = 0; //Restart to start DMA transmission of 255 bytes of data at a time HAL_UART_Receive_DMA(&huart1, (uint8_t*)receive_buff, 255); }
About calculating data length, we can know the operation of HAL library function. Simply speaking, the _HAL_DMA_GET_COUNTER() function will return the data to be received, setting the length of data to be received, subtracting the data to be received, we will get the data that has been received.
So far, we use the method of DMA + serial idle interrupt to achieve the reception of variable length data.