问题:
HAL库在大数据量频繁收发时出现串口接收失效。
分析:
HAL库对串口中断进行了封装,留给用户的接口只有一个回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //接收中断回调函数
平时在中断函数中写的东西,现在统统写在这个回调函数中。
在使用库函数或寄存器时,我们通常要自己清除中断标志位,且接收中断的使能和失能可以由用户直接操作寄存器控制,而这些都被HAL库进行了封装,呈现在用户面前的只有两个函数:
HAL_UART_Receive(&huart2,(uint8_t*)Data_Buf, Data_Length,Timeout)//阻塞式接收,在指定时间内阻塞等待数据到来
HAL_UART_Receive_IT(&huart2,(uint8_t*)Data_Buf,Data_Length);//中断式接收
第一个函数使用频率较低,一般情况下,我们想要复现库函数或寄存器编程时的那种单字节接收中断的方式来处理数据,就可以使用第二个函数,将Data_Length设为1,这样接收到一字节数据就会进入一次回调函数。如下:
HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1);//开启串口2接收中断
HAL库之所以把这些封装起来,就是为了大数据量的吞吐(因为可以可以指定数据长度,且清除标志位等操作HAL库都自己做了)。
现在拿它当单字节的接收中断用反而很低效,因为每一字节都要做很多判断再进入回调函数。
并且HAL_UART_Receive_IT这个函数每次使用时都在函数内部对串口中断进行使能。
因为HAL库在串口中断中加入了这样一个机制:在规定字节接收完成后,对中断进行失能。
为了保证能连续接收,我们通常要在回调函数结束时,重新调用该函数使能接收中断用以下一字节的接收,如下:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART2)
{
UART1_Rx_Buf[UART1_Rx_cnt] = UART2_temp;
UART1_Rx_cnt++;
HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1);//重新使能接收中断
}
}
我们进入HAL_UART_Receive_IT()观察一下函数原型
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
/* Check that a Rx process is not already ongoing */
if (huart->RxState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(huart);
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
/* Process Unlocked */
__HAL_UNLOCK(huart);
/* Enable the UART Parity Error Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_PE);
/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_ENABLE_IT(huart, UART_IT_ERR);
/* Enable the UART Data Register not empty Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
可以看到该函数使能中断时,是有可能失败的,如果使能失败,返回值为 HAL_BUSY
这里失败的具体原因我自己研究了一会儿,水平还是不太够,网上有大佬给出了答案(链接在文末)。
大概的问题是:
串口的发送和接收出现冲突,HAL_UART_Transmit是阻塞式调用,阻塞中发生中断后就会出问题。
我模拟大数据量的收发试验了一下。
if(HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1) == HAL_BUSY);//开启串口2接收中断
{
printf("Uart2 is BUSY!\r\n");
}
如果使能串口失败,就提示Uart2 is BUSY!,串口助手界面结果如下:
在数据量过大时,就出现了使能串口中断失败的情况,之后串口接收便失效了,程序其他部分运行正常,串口发送也正常。
解决办法
检测到HAL_UART_Receive_IT()返回HAL_BUSY时就重新使能,注意不要迅速使能,在中断里千万不要这么写:
while(HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1) == HAL_BUSY);
亲测上面的代码要是卡在中断中的话会让程序崩溃,最好是这样:
if(HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1) == HAL_BUSY)//开启串口2接收中断
{
UART_ERR = 1;
}
然后在循环(或另一个任务中)检测UART_ERR,重新使能:
if(UART_ERR)
{
UART_ERR = 0;
printf("\r\nUart2 is BUSY!-------------------->\r\n");
while(HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1) == HAL_BUSY);//开启串口2接收中断
}
这种方法只能充当一个看门狗的角色,不能从根本上解决HAL库的串口接收问题。
如果在平时的应用中只是偶尔出现这样的问题,数据也有重发的机制的话,可以用这种方法解决。
我觉得这也不能算是HAL库的BUG,因为这里使能失败是可以读到的,并不是未知的错误,只能说是这样的机制没考虑到普通的小型应用设计的方便性吧。
下面帖子里详细说明了HAL库的这个问题,可以通过修改HAL库的相关部分代码解决该问题:
https://blog.csdn.net/suxingtian/article/details/86526746
4.29更新
建议不要用HAL库的串口接收函数了,直接把原来生成的串口中断函数删掉,自己写一个就好,。
HAL库的初始化配置不用更改,只需在初始化时加一句开启串口中断:
__HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE); //使能接收中断
之后就可以像使用标准库/寄存器那样的写法去处理HAL库的串口中断了:
void USART3_IRQHandler(void) //接收中断
{
uint8_t UART3_RX_TEMP = 0;
if(huart3.Instance->SR & UART_FLAG_RXNE)//接收到数据
{
UART3_RX_TEMP = USART3->DR; //读出数据(读DR寄存器会自动清除标志位)
/***user code begin***/
/***user code end***/
}
}
处理串口数据的两种常用方式:
串口单字节中断:小数据量时适合处理帧协议,
DMA+空闲中断+缓冲区:适用于各种情况。