Solve the problem that the STM32 serial port cannot receive data at the same time when uploading data at high speed

1. Problem description

In a previous project, the STM32F7 serial port 3 was used for data transmission, and the robot data was uploaded regularly. The upper computer (ground station) received the data and displayed it on the interface. At the same time, the upper computer could send data to the robot to achieve the purpose of parameter adjustment and calibration. . The data upload is normal, the data distribution has always been a bit problematic, and it doesn't work at all times. If you change the parameters for a while, you can change it for a while and you can't change it, which is very fascinating. Because the link for downloading data is the host computer serial port sending-the lower computer serial port receiving-the lower computer modifies the parameters-the lower computer uploads new parameters-the upper computer receives-the upper computer displays the new parameters, I am always confused I thought that there was a problem with the host computer (LabWindows multithreading). Recently, I have time to summarize, and finally located the problem is that the STM32 serial port will lock up and cannot receive data when it is uploaded at a high speed .

Let me start with the conclusion: the reason for the inability to receive is the use of HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)this function, which causes the serial port to be blocked. Sometimes the HAL library function of STM32 is really mine.

2. Solution

(1) Use byte transmission instead of frame transmission

After debugging, it is found that there is HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)a problem with this function. Let's take a look at what is done inside this function.

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
    
    
	uint16_t *tmp;
	uint32_t tickstart = 0U;

	/* Check that a Tx process is not already ongoing */
	if (huart->gState == HAL_UART_STATE_READY)
	{
    
    
		if ((pData == NULL) || (Size == 0U))
		{
    
    
			return HAL_ERROR;
		}

		/* Process Locked */
		__HAL_LOCK(huart);

		huart->ErrorCode = HAL_UART_ERROR_NONE;
		huart->gState = HAL_UART_STATE_BUSY_TX;

		/* Init tickstart for timeout managment*/
		tickstart = HAL_GetTick();

		huart->TxXferSize = Size;
		huart->TxXferCount = Size;
		while (huart->TxXferCount > 0U)
		{
    
    
			huart->TxXferCount--;
			if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
			{
    
    
				return HAL_TIMEOUT;
			}
			if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
			{
    
    
				tmp = (uint16_t *)pData;
				huart->Instance->TDR = (*tmp & (uint16_t)0x01FFU);
				pData += 2;
			}
			else
			{
    
    
				huart->Instance->TDR = (*pData++ & (uint8_t)0xFFU);
			}
		}
		if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
		{
    
    
			return HAL_TIMEOUT;
		}

		/* At end of Tx process, restore huart->gState to Ready */
		huart->gState = HAL_UART_STATE_READY;

		/* Process Unlocked */
		__HAL_UNLOCK(huart);

		return HAL_OK;
	}
	else
	{
    
    
		return HAL_BUSY;
	}
}

There are two lines of code __HAL_LOCK(huart)and the __HAL_UNLOCK(huart)comments say that the process is locked and the process is unlocked. This thing will be killed when you see it. Can the serial port be locked and the receiving interrupt can be entered? ? It's hard to say, I feel there is a problem. HAL_UART_TransmitIt is to send a string of data at a time, once the amount of data sent is large, it will inevitably lead to normal reception. Okay, let's change the method, instead of sending it as a frame of data, sending it as a single byte.

Look at the previous standard library that has a single byte sending function:

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{
    
    
  /* Check the parameters */
  assert_param(IS_USART_ALL_PERIPH(USARTx));
  assert_param(IS_USART_DATA(Data)); 
    
  /* Transmit Data */
  USARTx->DR = (Data & (uint16_t)0x01FF);
}

But the HAL library is gone, no more. . . It doesn't matter, write one yourself:

int Uart1SendChar(u8 ch)
{
    
    
    while((USART1->ISR&0X40)==0);//循环发送,直到发送完毕   
	USART1->TDR=(u8)ch; 
    return ch;	
}

This is very simple, that is, always fill in the data in the sending data register TDR, and it will be sent out.

Now change the sending data to use the above function to send. For example, it turned out to be using

HAL_UART_Transmit(&UART1_Handler, send_buf, 25, 1000);

Corresponding to

for (i=0;i<25;i++)
{
    
    
	Uart1SendChar(send_buf[i]);
}

Test again, bingo, the reception is normal.

(2) Use DMA to send

The second method is to use DMA, direct storage access, it has the advantage that data transmission does not go through the CPU, which saves CPU resources and improves calculation speed. There are many tutorials on serial port DMA, and one of the very annoying problems is the processing of interrupt functions. The HAL library really blinded me. Although the function of sending and receiving variable-length data was finally realized, the library didn't understand it and felt very sad. Later, I plan to write an article on the interrupt mechanism of serial communication/serial DMA communication to thoroughly dry the HAL library code, so I won't go into details here.

Next, we will use the DMA of serial port 1 to send data and DMA to receive variable-length data, and then use the normal method of serial port 2 to print out the data received by serial port 1 for observation.

Create a new project, open serial port 1, serial port 2, and serial port 3 in Cubemx, open their DMA at the same time, and check interrupt.

Insert picture description here
Insert picture description hereThe configuration of serial ports 1, 2, and 3 are the same. Just pay attention to selecting the corresponding Sream.

The code generated by default already has the driver code for the serial port and DMA. HAL_UART_Transmit_DMAJust call it directly .

Since the serial port idle interrupt is used to receive variable-length data, the serial port idle interrupt must be enabled and the default serial port initialization function must be modified.

static void MX_USART1_UART_Init(void)
{
    
    

	/* USER CODE BEGIN USART1_Init 0 */

	/* USER CODE END USART1_Init 0 */

	/* USER CODE BEGIN USART1_Init 1 */

	/* USER CODE END USART1_Init 1 */
	huart1.Instance = USART1;
	huart1.Init.BaudRate = 115200;
	huart1.Init.WordLength = UART_WORDLENGTH_8B;
	huart1.Init.StopBits = UART_STOPBITS_1;
	huart1.Init.Parity = UART_PARITY_NONE;
	huart1.Init.Mode = UART_MODE_TX_RX;
	huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
	huart1.Init.OverSampling = UART_OVERSAMPLING_16;
	huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
	huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
	if (HAL_UART_Init(&huart1) != HAL_OK)
	{
    
    
		Error_Handler();
	}
	/* USER CODE BEGIN USART1_Init 2 */
	__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);		   //使能idle中断
	HAL_UART_Receive_DMA(&huart1, rx_buffer, BUFFER_SIZE); //打开DMA接收,数据存入rx_buffer数组中。
	/* USER CODE END USART1_Init 2 */
}

The last two lines of code are added, which is to open the IDLE interrupt and start the serial port DMA transfer.

Then add the interrupt function, first add the external data declaration in the interrupt file (stm32F7xxx.it.c) generated by cubemx,

extern volatile uint8_t rx_len;
extern volatile uint8_t recv_end_flag;
extern uint8_t rx_buffer[100];
extern char BUFFER_SIZE;

The extern used, because it is defined in main.c, add the definition to the head of main.c:

volatile uint8_t rx_len;
volatile uint8_t recv_end_flag;
uint8_t rx_buffer[100];
char BUFFER_SIZE;

The interrupt function is written directly in USART1_IRQHandler(void)it, and the callback function is the hell. . .

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 */

	uint32_t tmp_flag = 0;
	uint32_t temp;
	tmp_flag = __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE);    //获取IDLE标志位
	if ((tmp_flag != RESET))      //idle标志被置位
	{
    
    
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);
	    //清除标志位
		temp = huart1.Instance->ISR; 			//清除状态寄存器SR(F0的HAL库USART_TypeDef结构体中名字为ISR:USART Interrupt and status register),读取SR可以清楚该寄存器
		temp = huart1.Instance->RDR; 			//读取数据寄存器中的数据,读取DR(F0中为RDR:USART Receive Data register)
		HAL_UART_DMAStop(&huart1);
		temp = hdma_usart1_rx.Instance->NDTR;   //获取DMA中未传输的数据个数,NDTR寄存器分析见下面
		rx_len = BUFFER_SIZE - temp;       		//总计数减去未传输的数据个数,得到已经接收的数据个数
		recv_end_flag = 1;                 		//接受完成标志位置1
	}
	/* USER CODE END USART1_IRQn 1 */
}

The interrupt function first obtains the IDLE idle interrupt flag bit, then clears the flag bit, stops the serial port DMA transmission, obtains the number of received data, sets the receive completion flag, and places the received data processing in the main function.

Add the sending buffers of serial port 1 and serial port 2 in main.c:

uint8_t i = 0;
uint8_t count = 0;
uint8_t send_buf[25] = {
    
    0};
uint8_t send[25] = {
    
    0};
send_buf[0] = 0xAA;
send_buf[1] = 0xAA;
for (i = 2; i < 25; i++)
	send_buf[i] = 0x50;

Then modify the while (1) loop

while (1)
{
    
    
	/* USER CODE END WHILE */
	HAL_UART_Transmit_DMA(&huart1, send_buf, 25); //开启DMA传输
												  //	HAL_UART_Transmit(&huart1,send_buf,25,1000);//开启DMA传输
	if (recv_end_flag == 1)
	{
    
    
		//printf("rx_len=%d\r\n", rx_len);					//打印接收长度
		count++;
		send[0] = count; 
		HAL_UART_Transmit(&huart2, send, 1, 200);			//接收数据打印出来,标记接收数据的数量
		HAL_UART_Transmit(&huart2, rx_buffer, rx_len, 200); //接收数据打印出来
		HAL_UART_Transmit(&huart2, &send[1], 2, 200); //接收数据打印出来
		for (uint8_t i = 0; i < rx_len; i++)
		{
    
    
			rx_buffer[i] = 0; //清接收缓存
		}
		rx_len = 0;											   //清除计数
		recv_end_flag = 0;									   //清除接收结束标志位
		HAL_UART_Receive_DMA(&huart1, rx_buffer, BUFFER_SIZE); //重新打开DMA接收
	}

	HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
	HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_1);
	HAL_Delay(20);
	/* USER CODE BEGIN 3 */
}

In the while loop, the data is uploaded regularly through the serial port 1 DMA, and the data received by the serial port 1 is printed out through the serial port 2. Open the two serial port debugging assistants on the computer, connect the two serial ports respectively, and check the data sent back from the serial port 1 and the serial port 2. As shown in the figure, on the left is the data sent back from serial port 1. Click Send to send the data to the board. The data on the right is the data sent back by serial port 2. Click Send on the left and the data sent on the left will be displayed on the right.

I noticed that every time there is a frame of right data and a wrong frame of data, well, I'm fascinated again, fascinated. . . Debugging.

Insert picture description here

Guess you like

Origin blog.csdn.net/qq_30267617/article/details/115276566