• STM32使用串口空闲中断(IDLE)和 DMA接收一串数据流


    STM32使用串口空闲中断(IDLE)和 DMA接收不定长数据

    方法一、使用宏定义判断IDLE标志位

    空闲的定义是总线上在一个字节的时间内没有再接收到数据,USART_IT_IDLE空闲中断是检测到有数据被接收后,总线上在一个字节的时间内没有再接收到数据的时候发生的。

    串口空闲中断(UART_IT_IDLE):STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一但接收的数据断流,没有接收到数据,即产生IDLE中断。IDLE位不会再次被置高直到RXNE位被置起(即又检测到一次空闲总线)。RXNE接收中断可以不用开启,减少进中断的次数。

    IDLE置1之后它不会自动清0,也不会因为状态位是1而一直产生中断,它只有0跳变到1时才会产生,也可以理解为上升沿触发。所以,为确保下次空闲中断正常进行,需要在中断服务函数发送任意数据来清除标志位。

    在这里插入图片描述

    清除IDLE标志位是通过先读USART_SR,再读USART_DR寄存器来完成的,在HAL库中,提供了一个用于清除IDLE标志位的宏定义,该宏定义在stm32f1xx_hal_uart.h头文件中,__ HAL_UART_CLEAR_IDLEFLAG(__ HANDLE__ )是宏名,__ HAL_UART_CLEAR_PEFLAG(__ HANDLE__)是宏体

    /** @brief  Clears the UART IDLE pending flag.
      * @param  __HANDLE__ specifies the UART Handle.
      *         UART Handle selects the USARTx or UARTy peripheral
      *         (USART,UART availability and x,y values depending on device).
      * @retval None
      */
    #define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    而宏体又是一个宏定义,转到底层代码就可以看到先读SR再读DR寄存器的操作

    /** @brief  Clears the UART PE pending flag.
      * @param  __HANDLE__ specifies the UART Handle.
      *         UART Handle selects the USARTx or UARTy peripheral
      *         (USART,UART availability and x,y values depending on device).
      * @retval None
      */
    #define __HAL_UART_CLEAR_PEFLAG(__HANDLE__)     \
      do{                                           \
        __IO uint32_t tmpreg = 0x00U;               \
        tmpreg = (__HANDLE__)->Instance->SR;        \
        tmpreg = (__HANDLE__)->Instance->DR;        \
        UNUSED(tmpreg);                             \
      } while(0U)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    所以在用HAL库编写串口空闲中断相关代码时,可以用该宏定义来清除IDLE标志位

    示例:用宏定义判断IDLE标志位是否置位

    /**
      * @brief This function handles USART1 global interrupt.
      */
    void USART1_IRQHandler(void)
    {
      /* USER CODE BEGIN USART1_IRQn 0 */
    	//检测串口空闲中断
    	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != 0x00u)	//判断IDLE标志位是否被置位
    	{
    		__HAL_UART_CLEAR_IDLEFLAG(&huart1);		//清除IDLE标志位
    		HAL_UART_IdleCallback(&huart1);			//调用自己编写的空闲中断回调函数
    	}
    	
      /* USER CODE END USART1_IRQn 0 */
      HAL_UART_IRQHandler(&huart1);
      /* USER CODE BEGIN USART1_IRQn 1 */
    	
      /* USER CODE END USART1_IRQn 1 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    当串口产生中断时,会进入USART1_IRQHandler这个函数,判断IDLE标志位是否被置位,是就先清除标志位,再调用自己写的空闲中断回调函数

    USART_IT_IDLE和USART_IT_RXNE区别

    当接收到1个字节,会产生USART_IT_RXNE中断

    当接收到一帧数据,就会产生USART_IT_IDLE中断

    DMA

    直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。 两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。

    在程序中就可以使用DMA搬运外设通过串口发送的数据,放到接收缓存中,在串口空闲中断的回调函数中先关闭DMA,再处理数据,待数据处理完后,再开启DMA搬运

    处理数据之前关闭DMA是防止在处理过程中DMA又将新数据搬运到接收缓存,覆盖掉原来的数据

    方法二、使用HAL库提供的库函数

    使能DMA接收,并于接收完后进入空闲中断函数

    函数作用:在DMA模式下接收一定数量的数据,直到接收到预期数量的数据或发生空闲事件。

    参数Size:接收数据的长度,一般大于不定长数据长度,避免遗漏数据

    HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
    
    • 1

    空闲中断回调函数

    __weak void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
    
    • 1

    使用方法:

    先在主函数中调用HAL_UARTEx_ReceiveToIdle_DMA函数初始化

    /*
    先调用HAL_UARTEx_ReceiveToIdle_DMA函数(比如说在main中调用),产生空闲中断后进入中断服务函数,中断服务函数会调用回调函数。
    */
    int main(void)
    {
    	...
    	HAL_UARTEx_ReceiveToIdle_DMA(&huart3,DATA_BUFF,BUFF_SIZE);
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    再在空闲中断回调函数中处理数据

    void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
    {
    	if(huart->Instance == USART3)
    	{
    	//这里的DMA为Normal模式
    	//HAL_UART_DMAStop(huart)//如果DMA为Circular模式,回调函数加上stop函数
    		USER_FNC();//用户自定义函数
    		HAL_UARTEx_ReceiveToIdle_DMA(&huart3,DATA_BUFF,BUFF_SIZE);//继续接收数据
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    参考:http://t.csdn.cn/DJmHQ

    注意

    看教程时别人使用的是HAL库是STM32Cube_FW_F1_V1.8.0,是1.8.0版本的,没有启动DMA和空闲中断的函数,也没有空闲中断回调函数,所以教程使用的是宏定义判断IDLE标志位的方法;而现在最新的HAL库是1.8.4版本,可在CubeMX中查看,在使用过程中发现是有DMA和空闲中断启动函数HAL_UARTEx_ReceiveToIdle_DMA的,也有空闲中断回调函数HAL_UARTEx_RxEventCallback

    不知道从1.8.1到1.8.3哪个版本开始就有了,不过现在最新的有这些函数,也可以尝试着使用一下

    在这里插入图片描述

    STM32Cube_FW_F1_V1.8.0

    在这里插入图片描述

    STM32Cube_FW_F1_V1.8.4

    在这里插入图片描述

  • 相关阅读:
    Windows系统中苹果ipa上传到App Store Connect
    软件测试行业到底有没有前景和出路?2022辞职后涨薪5K+,凭什么?
    前端CSS零碎知识点
    视频汇聚管理平台EasyCVR程序报错“create jwtSecret del server class:0xf98b6040”的原因排查与解决
    深入理解JavaScript堆栈、事件循环、执行上下文和作用域以及闭包
    【linux基础】 4.1 简单shell编程语法
    Go基础——指针、结构体
    Android系统_MSM8953_android10_集成openssh
    苏生不惑出品:2024 批量下载知乎回答,文章和想法,导出 excel 和 pdf
    [SQL]视图和权限
  • 原文地址:https://blog.csdn.net/weixin_46251230/article/details/126729795