HAL库串口中断RX中的Overrun Error问题

HAL库串口中断RX中的Overrun Error解决方案

1. ORE触发条件

首先简单研究一下什么时候会出现overrun的问题,配置正常的HAL串口中断接收如下

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
     
    /* judge interrupt source */
    if(huart ->Instance == USART2)
    {
     
        //send data to huart1 from huart2
        HAL_UART_Transmit(&huart1, (uint8_t*)recv_buf, 3,0xFFFF);
        //enable RX IT
        HAL_UART_Receive_IT(huart, (uint8_t*)recv_buf, 3);
    }
}

串口收发情况

可以看出,分三种情况:

  • 发送数据=接收缓存,正常
  • 发送数据<接收缓存,BUF未满,要等待下一次数据到来填满缓存才会触发中断
  • 发送数据>接收缓存,BUF已满,多余部分被截断,和下一次数据一起触发中断

其中,触发两次接收缓存溢出的时候,就会进入Overrun Error。

比如说BUF长度为3,连续发两次长度为4的数据,就会进入ORE。或者说第一次发送长度为2的数据,然后接着发送两次长度为3的数据,也是会触发ORE的,后续的两次长度为3的数据因为有之前一条长度为2的铺垫,都是视作BUF溢出的数据。但是如果发送一次长度为4的数据之后,后续发长度一条长度为2的数据,就会进入正常状态,是有弹性的。弹性限度为BUF长度的两倍,也就是说发送一次长度为6的数据,会立即进入ORE。

ORE状态下,串口进入ORE溢出中断,无法继续接收数据,但是可以发送数据,相当于接收数据不会触发中断了。

2.ORE解决方案

对于ORE溢出常用解决方案有下:

  • 关闭ORE中断
  • 采取双重缓存

无论哪一种方法都要理解HAL_UART_RxCpltCallback的实现原理:

  1. HAL_UART_IRQHandler(UART_HandleTypeDef *huart)(中断处理函数)
  2. UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数)
  3. HAL_UART_RxCpltCallback(huart)(中断回调函数)

从上到下,层层封装和调用。

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
     
  uint32_t isrflags   = READ_REG(huart->Instance->SR);
  uint32_t cr1its     = READ_REG(huart->Instance->CR1);
  uint32_t cr3its     = READ_REG(huart->Instance->CR3);
  uint32_t errorflags = 0x00U;
  uint32_t dmarequest = 0x00U;

  /* If no error occurs */
  errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
  //close ORE check
  //errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_NE));
  if (errorflags == RESET)
  {
     
    /* UART in mode Receiver -------------------------------------------------*/
    if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
    {
     
      UART_Receive_IT(huart);
      return;
    }
  }

​想要关闭ORE中断,将errorflags中的USART_SR_ORE 这一位去掉即可,相当于对ORE错误不予检查,经过实际的测试是可行的,实际的表现就是出现ORE溢出之后,串口只接受固定BUF长度的数据,对于溢出的数据直接丢弃,但是还需要考虑ORE溢出之后,那个时刻中BUF肯定是有数据,但是由于没有清除BUF,后续的数据直接进来会导致错位,因此想要真正实现完美的溢出数据丢弃处理且不影响后续的数据,是要在程序中加入清除BUF的指令的。但是这些方法都会影响HAL库的底层代码,包括去掉USART_SR_ORE 这一位,这也是STM32IDE的一点的不好的地方,底层文件是没有用户代码入口的,修改IOC文件之后,一切都会回到最初的起点。

其次就是双重缓存,具体的就是将BUF长度设置为1,这样子每一次收到一个字节的话都会调用回调函数,为什么长度长度大于1的时候会发生ORE,而1的时候可以接受很长的数据而不ORE呢,我猜是因为1的时候将某些位置位了,间接关闭了ORE,然后用户自己定义BUF区,一个一个去读取写入,这个方法还可以实现不定长的接收,设置数据结束帧位就可以了。缺点也很明显,当数据很长的时候,中断要进入很多次,有点占资源了。

用户回调函数HAL_UART_RxCpltCallback只有接收到设定的BUF的长度才会调用一次,而不是有字节就调用,实现的方法在UART_Receive_IT函数中:

    if (--huart->RxXferCount == 0U)
    {
     
      /* Disable the UART Data Register not empty Interrupt */
      __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);

      /* Disable the UART Parity Error Interrupt */
      __HAL_UART_DISABLE_IT(huart, UART_IT_PE);

      /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
      __HAL_UART_DISABLE_IT(huart, UART_IT_ERR);

      /* Rx process is completed, restore huart->RxState to Ready */
      huart->RxState = HAL_UART_STATE_READY;

#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
      /*Call registered Rx complete callback*/
      huart->RxCpltCallback(huart);
#else
      /*Call legacy weak Rx complete callback*/
      HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */

if (–huart->RxXferCount == 0U)这一句,每次字节来都会调用UART_Receive_IT,然后RxXferCount减1,这个变量就是一开始设置的BUF接收长度,只有等这个到零了,才会调用HAL_UART_RxCpltCallback(huart)

最终,因为在项目中串口是需要来高频率接收固定长度的测量数据的,但是在之前要用串口发送上电指令,这个指令会返回一个和测量数据长度不同的ASK信号,处理方法就是在上电之后,再开启串口接收,这样子就可以设置BUF长度为固定长度,同时稍微降低测量的频率,让串口接收有足够的时间,避免ORE的产生。

你可能感兴趣的:(stm32,串口通信,c语言)