STM32串口利用空闲中断接收数据以解决HAL库效率不高的问题
阅读原文时间:2021年04月20日阅读:1

文章目录

导读

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);
}

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

结束语

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

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器