刚装了VS2019Preview,VS2017系列应该还有最后一章就结束了,找个时间结束掉它。
昨晚弄了下STM32的串口通信,发现UART在接收PC串口调试助手发送的数据的时候,会时不时卡死,不能接收新的数据。之前公司有人做这方面的项目的时候也是这个情况,当时发现UART处于ORE(overrun error)状态,归结为波特率太高,降低波特率算妥协了。结果自己弄Nucleo的开发板也出现这个情况,我想STM官方开发板应该不至于只能跑低速通信。没办法查查吧。
首先把ORE的检测关掉。这个东西吧,有啥意义呢?Overrun检测是好的,可以告诉系统目前通信超负荷然后进行调整。但是目前99%以上的开发者都不会管这个东西,另外他们也没有这么极限数据率通信的需求。如果要检测ORE,你的系统中一定要有UART的Error handler 函数,进行ORE出现时的状态寄存器清理和系统调整。否则就会出现系统被卡死再也无法通信的情况。
这个ORE的检测是CubeMX默认打开的,在UART的配置里面,如下(我这是5.0CubeMX):
代码里面是这两句(我的CubeMX和MDK都是最新版,可能老版本不一样,如果没有就写ErrorHandler进行错误位复位):
huart2.AdvancedInit.OverrunDisable = UART_ADVFEATURE_OVERRUN_DISABLE;
huart2.AdvancedInit.DMADisableonRxError = UART_ADVFEATURE_DMA_DISABLEONRXERROR;
---------------------------------------------------------2019/6/25 更新--------------------------------------------------------------------
试了下STM32F103RC并没有这个OverrunDisable开关,之前还认为是CubeMX更新后带来的新特性。
如果没有的话那就需要自己写ErrorCallback函数了,如下:
/**
* @brief UART error callback.
* @param huart UART handle.
* @retval None
*/
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
if(huart->ErrorCode&HAL_UART_ERROR_ORE)
{
__HAL_UART_CLEAR_OREFLAG(huart);
}
/* NOTE : This function should not be modified, when the callback is needed,
the HAL_UART_ErrorCallback can be implemented in the user file.
*/
}
这是一个weak函数,把这个函数实现在自己的c文件里就可以覆盖原来的那个。初步测试了下,在Overrun以后能够继续工作,但是发生ORE时肯定会丢数据。所以还是要从根本上去解决ORE出现的原因。
-------------------------------------------------------------------------------------------------------------------------------------------------
这样就关掉了ORE的检测,现在会出现接收不全数据的情况,但是UART不会卡死,会继续接收新的数据。
然而上面的操作实际上没有解决真正的问题,因为并非由于Overrun导致的ORE。换句话来说,目前的数据率和读写操作不可能Overrun。我的main函数代码如下:
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
uint16_t rxSize=UART_RXBUF_SIZE;
if(USER_UART_Receive(&huart2,uartRxBuf,&rxSize,10)==HAL_OK)
{
HAL_UART_Transmit(&huart2,uartRxBuf,rxSize,10);
}
}
收到什么返回什么,经典测试代码。Overrun指UART处理不了当前的数据率。115200BPS,一收一发显然不满足。所以还是怀疑是库函数有问题。
今天晚上回家找了下网上的解决方案,都和我的情况不一样(HAL果然坑)。于是自己开始一点点调试HAL的UART读写代码,基本上认定问题出现在HAL_UART_Receive这个函数。
BUG大概就是每次进行读操作时会有个超时,这个超时的作用是如果UART在这个时间内没有收到期望的数据量那么函数就返回TIMEOUT。上面我的代码里设置的超时时间是10ms,看似足够了,但是实际上存在一种错误状态。该状态如下图所示:
看完上图应该明白为什么这么低的数据率会ORE了。找到问题解决起来也很简单,改HAL的HAL_UART_Receive代码如下:
//这是我重写的UART接收函数,除了修复超时导致的丢数据问题以外,增加了
//超时后返回当前读取到的数据量,这样可以读不定长帧。目标接收20Byte,
//实际发送了10Byte,那么pSize为10,也就是实际接收到的数据量。
HAL_StatusTypeDef USER_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t *pSize, uint32_t Timeout)
{
uint8_t *pdata8bits;
uint16_t *pdata16bits;
uint16_t uhMask;
uint32_t tickstart;
//这里是保证原来的代码结构
uint16_t Size=*pSize;
//记录第一个数据的到来时间
uint8_t firstData=0;
/* Check that a Rx process is not already ongoing */
if (huart->RxState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* In case of 9bits/No Parity transfer, pData buffer provided as input parameter
should be aligned on a u16 frontier, as data to be received from RDR will be
handled through a u16 cast. */
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
{
if ((((uint32_t)pData) & 1) != 0)
{
return HAL_ERROR;
}
}
/* Process Locked */
__HAL_LOCK(huart);
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
/* Init tickstart for timeout managment*/
tickstart = HAL_GetTick();
huart->RxXferSize = Size;
huart->RxXferCount = Size;
/* Computation of UART mask to apply to RDR register */
UART_MASK_COMPUTATION(huart);
uhMask = huart->Mask;
/* In case of 9bits/No Parity transfer, pRxData needs to be handled as a uint16_t pointer */
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
{
pdata8bits = NULL;
pdata16bits = (uint16_t *) pData;
}
else
{
pdata8bits = pData;
pdata16bits = NULL;
}
/* as long as data have to be received */
while (huart->RxXferCount > 0U)
{
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
{
//这里是超时退出,我在这里把当前接收到的数据量写入pSize返回
//目前只支持数据量低于期望Size的不定长数据,超过你的接收数量还是可能出现ORE错误
*pSize=*pSize-huart->RxXferCount;
return HAL_TIMEOUT;
}
//如果是定长帧这个超时时间就是通过波特率计算出的整个帧的超时时间,比如上图中的20byte的通信时间为1.73ms,那么设置一个2ms就差不多了
//这里记录下第一个byte到来的时间,从这个时间开始计时,这个超时时间一定要和你的接收数据量相匹配
#if FIXEDSIZE
if(firstData==0)
{
tickstart = HAL_GetTick();
firstData=1;
}
#else
//如果是不定长接收,这个超时时间改为单byte的超时时间,如115200下一个byte通信时间大概为0.086ms
//同时代码改为每个byte都更新超时计时开始时间。理论上可以一次性接收你能开空间大小的帧长。
//比较推荐这种判断,对系统整体的阻塞较小。超时时间和数据接收量无关。
tickstart = HAL_GetTick();
#endif
if (pdata8bits == NULL)
{
*pdata16bits = (uint16_t)(huart->Instance->RDR & uhMask);
pdata16bits++;
}
else
{
*pdata8bits = (uint8_t)(huart->Instance->RDR & (uint8_t)uhMask);
pdata8bits++;
}
huart->RxXferCount--;
}
/* At end of Rx process, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(huart);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
好,代码改完了测试效果如下:
首先是接收20Byte,实际上位机发送20Byte,100ms的间隔连续发送:
然后是不定长测试,接收20Byte,实际上位机发送11Byte,100ms的间隔连续发送:
如果用中断IT接收好像可以避免这种情况,但要处理如果出现收到的帧长和期望长度不一致的情况。最简单就是把ORE关了,否则长度不正确也会导致UART卡死。