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__)

而宏体又是一个宏定义,转到底层代码就可以看到先读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)

所以在用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 */
}

当串口产生中断时,会进入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)

空闲中断回调函数

__weak void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)

使用方法:

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

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

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

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);//继续接收数据
	}
}

参考: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

物联沃分享整理
物联沃-IOTWORD物联网 » STM32使用串口空闲中断(IDLE)和DMA实现数据流接收

发表评论