STM32之HAL库串⼝USART丢数据及ORE卡死的解决⽅案刚装了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 unud 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 ur 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 ca 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*/牛吃草公式
抒情散文600字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 ca 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;
}
el
{
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;
}
#el
//如果是不定长接收,这个超时时间改为单byte的超时时间,如115200下⼀个byte通信时间⼤概为0.086ms //同时代码改为每个byte都更新超时计时开始时间。理论上可以⼀次性接收你能开空间⼤⼩的帧长。
//⽐较推荐这种判断,对系统整体的阻塞较⼩。超时时间和数据接收量⽆关。
tickstart = HAL_GetTick();
#endif
if (pdata8bits == NULL)
{
*pdata16bits = (uint16_t)(huart->Instance->RDR & uhMask);
pdata16bits++;
}
el
{
*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;
}
el
{
return HAL_BUSY;
}
}
好,代码改完了测试效果如下:
⾸先是接收20Byte,实际上位机发送20Byte,100ms的间隔连续发送:
然后是不定长测试,接收20Byte,实际上位机发送11Byte,100ms的间隔连续发送:
如果⽤中断IT接收好像可以避免这种情况,但要处理如果出现收到的帧长和期望长度不⼀致的情况。最简单就是把ORE关了,否则长度不正确也会导致UART卡死。