STM32
直播中

张秀兰

7年用户 1255经验值
私信 关注
[问答]

怎样去使用串口空闲中断避免数据丢失呢

STM32的串口中断标记中有哪几种?
基于CubeMx生成的HAL库代码该如何去实现呢?

怎样去使用串口空闲中断避免数据丢失呢?

回帖(1)

李桂香

2021-12-9 14:26:41
STM32用CubeMx生成的HAL库效率很低,利用HAL自带的串口中断在接收大量数据时很容易出现数据丢失。一般来说,串口所接受的数据长度可能是不固定的。然而在一串数据的连续接收中,串口接收中断内实现的程序应尽可能短小,避免因打断接收过程而产生数据丢失。本文介绍自己写一个轻量的串口接收中断,使用空闲中断实现效率的大幅提升,并且避免数据丢失

  原理

  STM32 的串口中断标记中有
  


  • UART_IT_RXNE:串口非空标记,在接收缓存寄存器中有数据时置位;
  • UART_IT_IDLE:串口空闲标记,在一串数据接收完成后置位;

  当每接收到一个字符时都会置位UART_IT_RXNE,此时我们将字符存到自定义的数组Buffer中,然后将UART_IT_RXNE清零。当一串字符接收结束后串口进入空闲状态,UART_IT_IDLE则会置位,此时我们将接收到的所有数据进行处理,并将UART_IT_IDLE清零。
  
   发送端每次都发送一串数据,发送这一串数据的各字符之间没有空闲时间,而每串数据结束到接收到下一串数据是有一定空闲时间的。因此,接收端只有在接收完一串数据之后才能检测到空闲,从而触发空闲中断。该方法直接利用数据串之间的空闲来识别数据串的尾部,而非使用如’n’等符号表示数据结尾,也将提高处理的效率。
  
  实现过程

  这里是基于CubeMx生成的HAL库代码而改写。
  在串口初始化中使能上述中断标记位

__HAL_UART_ENABLE_IT(uartHandle,UART_IT_IDLE);
__HAL_UART_ENABLE_IT(uartHandle,UART_IT_RXNE);


具体代码:


void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct;
  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */


  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();
  
    /**USART1 GPIO Configuration   
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);


    /* USART1 interrupt Init */
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspInit 1 */
           __HAL_UART_ENABLE_IT(uartHandle,UART_IT_IDLE);
           __HAL_UART_ENABLE_IT(uartHandle,UART_IT_RXNE);
          
     /* USER CODE END USART1_MspInit 1 */
  }
}


改写串口中断函数
在stm32f4xx_it.c里的中断函数USART1_IRQHandler()原本是调用HAL库的HAL_UART_IRQHandler() 函数,这里把这个调用注释掉。


写检查UART_FLAG_RXNE 并存储接收到的字符的代码:


if(__HAL_UART_GET_FLAG(&hirda3, UART_FLAG_RXNE) != RESET)
{       
  uint8_t ch=(uint16_t) READ_REG(huart1.Instance->DR);
  *(pBuff++)=ch;
  ctn++;
  __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
}


写检查UART_FLAG_IDLE 并触发回调函数的代码:


if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
{       
  UART1_IDLECallBack(UART1_RxBuff,ctn);
  ctn=0;
  pBuff=UART1_RxBuff;
  __HAL_UART_CLEAR_IDLEFLAG(&huart1);
}


完整代码如下:


extern void UART1_IDLECallBack(uint8_t *buff,uint8_t size);
uint8_t UART1_RxBuff[50];
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
        static uint8_t* pBuff=UART1_RxBuff;
        static uint8_t ctn=0;
       
    if(__HAL_UART_GET_FLAG(&hirda3, UART_FLAG_RXNE) != RESET)
    {       
          uint8_t ch=(uint16_t) READ_REG(huart1.Instance->DR);
          *(pBuff++)=ch;
          ctn++;
          __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
    }
    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
    {       
      UART1_IDLECallBack(UART1_RxBuff,ctn);
      ctn=0;
      pBuff=UART1_RxBuff;
      __HAL_UART_CLEAR_IDLEFLAG(&huart1);
    }
  /* USER CODE END USART1_IRQn 0 */
  //HAL_IRDA_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */


  /* USER CODE END USART1_IRQn 1 */
}


写回调函数
最后只要在main.c中重定义UART1_IDLECallBack()函数即可。


void IRDA3_IDLECallBack(uint8_t *buff,uint8_t size)
{
        HAL_UART_Transmit(&huart1, buff, size, 0xFFFF);
}


上述回调函数内容根据实际情况编写,这里提供的例程是听过串口将受到的一整串数据全部返回。
  
  结束语

  简化单个字符接收后的处理过程,即只将字符存入缓存数组,只当整串数据接受完才调用回调函数执行对该串数据的处理。该方法大大提高了串口接收的效率,避免了数据丢失。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分