stm32 利用DMA+串口空闲中断接受任意长数据

因为想申请 CSDN 博客认证需要一定的粉丝量,而我写了五年博客才 700 多粉丝,本文开启关注才可阅读全文,很抱歉影响您的阅读体验

在进行stm32开发时,有时会遇到这种情况:需要在设备间进行数据传输,由于stm32串口RDR和TDR寄存器都是8位有效的,我们往往需要定义传输协议(如一帧数据中,包含包含帧头、帧ID、数据帧、校验帧等若干8位数据)。我们希望可以一次收到一帧数据,并进行解码操作。利DMA+串口空闲中断可以有效完成上述任务。

  • 基于stm32f4
  • DMA接受
  • 串口空闲中断

一、DMA

1、简介

  • DMA(直接存储器访问)是一种数据传输方法,利用DMA控制器,将数据直接从一个地址空间复制到另一个地址空间。
  • DMA在硬件ROM和IO设备间开辟直接传输数据的通道,不需要CPU主控芯片控制,也不需要类似中断处理那种保留现场&恢复现场的操作。这大大减小了CPU的负担。

2、使用场景

DMA用在只需要传输数据,不需要处理数据的地方,有三种传输方式:

  • 外设→存储器(例:从串口RDR寄存器写入某数据buf)
  • 存储器→外设(例:从某数据buf写入串口TDR寄存器)
  • 存储器→存储器(例:复制某特别大的数据buf)

3、主要特性

在中文参考手册9.2节详细说明了DMA特性
在这里插入图片描述
在这里插入图片描述

4、DMA控制器结构

  • stm32f4最多有:2个DMA控制器,各8个数据流,每个数据流有8个通道(或请求),每个通道有一个仲裁器,用于处理请求的优先级。如下图所示
    在这里插入图片描述
  • 来自各个外设的DMA请求连接到8个通道(请求)上。DMA_SxCR寄存器中CHSEL[2:0]位域,控制某时刻是哪个外设连接到此数据流上。如下图所示:
    在这里插入图片描述

5、DMA请求映射

  • 注意:如果要开启某外设的DMA传输,其库函数通常在该外设相应的.c文件中,在配置外设时注意开启。
  • DMA通道/请求外设不是任意连接的,不同的型号的芯片需要查询相应的映射表。如下:
    在这里插入图片描述
    在这里插入图片描述

6、指针递增

  • 在传输数据时,可以配置指向传输双方数据的指针是否自动向后递增。
  • 通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。

通常如下图配置:

方向指针情况
外设 → 存储器外设指针不变,存储buf指针递增
存储器 → 外设存储buf指针不变, 外设指针递增
存储器 → 存储器都递增

7、循环模式

  • 循环模式可用于处理循环缓冲区连续数据流(例如 ADC 扫描模式)。可以使用 DMA_SxCR寄存器中的 CIRC 位使能。文章最前提出的例子也适用此设置
  • 当激活循环模式时,要传输的数据项的数目在数据流配置阶段自动用设置的初始值进行加载,并继续响应 DMA 请求。

8、其他

关于DMA还有双缓冲区模式、突发传输等等其他设置,一般用不到,具体查询《stm32中文参考手册》

9、示例代码

文章开头情景的示例代码如下(DMA配置部分):

//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
//chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
//par:外设地址
//mar:存储器地址
//ndtr:数据传输量  
void DMA_Config(DMA_Stream_TypeDef *DMA_Streamx,uint32_t chx,uint32_t par,uint32_t mar,uint32_t dir,u16 ndtr)
{ 
 
	DMA_InitTypeDef  DMA_InitStructure;
	
	if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
	{
	  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 
		
	}else 
	{
	  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能 
	}
  DMA_DeInit(DMA_Streamx);
	
	while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待DMA可配置 
	
  /* 配置 DMA Stream */
  DMA_InitStructure.DMA_Channel 					= chx;  							//通道选择
  DMA_InitStructure.DMA_PeripheralBaseAddr 			= par;								//DMA外设地址
  DMA_InitStructure.DMA_Memory0BaseAddr 			= mar;								//DMA 存储器0地址
  DMA_InitStructure.DMA_DIR 					    = dir;								//direction of transmit.
  DMA_InitStructure.DMA_BufferSize 				    = ndtr;								//数据传输量 
  DMA_InitStructure.DMA_PeripheralInc				= DMA_PeripheralInc_Disable;		//外设非增量模式
  DMA_InitStructure.DMA_MemoryInc 					= DMA_MemoryInc_Enable;				//存储器增量模式
  DMA_InitStructure.DMA_PeripheralDataSize 		    = DMA_PeripheralDataSize_Byte;		//外设数据长度:8位
  DMA_InitStructure.DMA_MemoryDataSize 				= DMA_MemoryDataSize_Byte;			//存储器数据长度:8位
  DMA_InitStructure.DMA_Mode 						= DMA_Mode_Normal;					// 使用普通模式 
  DMA_InitStructure.DMA_Priority 					= DMA_Priority_High;				//中等优先级
  DMA_InitStructure.DMA_FIFOMode 					= DMA_FIFOMode_Disable;         
  DMA_InitStructure.DMA_FIFOThreshold 			    = DMA_FIFOThreshold_Full;
  DMA_InitStructure.DMA_MemoryBurst 				= DMA_MemoryBurst_Single;			//存储器突发单次传输
  DMA_InitStructure.DMA_PeripheralBurst 		    = DMA_PeripheralBurst_Single;		//外设突发单次传输
  DMA_Init(DMA_Streamx, &DMA_InitStructure);
  DMA_Cmd(DMA_Streamx,ENABLE);
} 

//开启一次DMA传输
void DMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
 
	DMA_Cmd(DMA_Streamx, DISABLE);                      //先关闭DMA,才能设置它
	
	while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}	//等待传输结束
		
	DMA_SetCurrDataCounter(DMA_Streamx,ndtr);          //设置传输数据长度 
 
	DMA_Cmd(DMA_Streamx, ENABLE);                      //开启DMA
}	  

二、串口空闲中断

先看一下串口中断表
在这里插入图片描述

1、常用的串口接收中断

  • 简单的串口接受一般使用串口接受中断,对应事件标志为RXEN
  • 一旦发生中断,即可使USART_ReceiveData(USART_TypeDef* USARTx)函数接受最新收到的一位数据
  • 通过对 USART_DR 寄存器执行读入操作将RXNE位清零。也可以通过向该位写入零来清零。
  • 这种接受方式,需要自行编程实现数据帧识别,且无法使用DMA,速度较慢。

示例代码如下

void My_USART1_Init(void)
{
	GPIO_InitTypeDef GPIO_Initstructure;
	USART_InitTypeDef USART_Initstructure;
	NVIC_InitTypeDef NVIC_Initstrcuture;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE );
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA ,ENABLE );
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
	
    GPIO_Initstructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_Initstructure.GPIO_OType = GPIO_OType_PP;
	GPIO_Initstructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Initstructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_Init(GPIOA,&GPIO_Initstructure);
	GPIO_Initstructure.GPIO_Pin = GPIO_Pin_10;//IO³õʼ»¯RX
	GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_Initstructure.GPIO_OType = GPIO_OType_PP;
	GPIO_Initstructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Initstructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_Init(GPIOA,&GPIO_Initstructure);
	
	USART_Initstructure.USART_BaudRate = 9600;
	USART_Initstructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_Initstructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Initstructure.USART_Parity = USART_Parity_No;
	USART_Initstructure.USART_StopBits = USART_StopBits_1;
	USART_Initstructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1,&USART_Initstructure);
	
	USART_Cmd(USART1,ENABLE);
	
	USART_ITConfig (USART1,USART_IT_RXNE ,ENABLE);//开启串口接受中断
	
	NVIC_Initstrcuture.NVIC_IRQChannel = USART1_IRQn;
    NVIC_Initstrcuture.NVIC_IRQChannelCmd = ENABLE ;
	NVIC_Initstrcuture.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_Initstrcuture.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_Initstrcuture);
	
}

void USART1_IRQHandler(void)
{
	u8 res=0;
	if(USART_GetITStatus(USART1,USART_IT_RXNE))//串口非空标志位为1,收到数据
	{
		res = USART_ReceiveData(USART1);//读取最新一个收到的数据
		USART_SendData(USART1,res );//发送数据
	}
}

2、串口空闲中断

  • 串口空闲中断,对应事件标志为IDLE
  • 检测到空闲线路时,该位由硬件置 1。如果 USART_CR1 寄存器中 IDLEIE = 1,则会生成中断
  • 该位由软件序列清零(读入 USART_SR 寄存器,然后读入 USART_DR 寄存器

利用串口空闲中断,可以用如下流程实现DMA控制的任意长数据接收:

  • 0、开启串口DMA接收
  • 1、串口收到数据,DMA不断传输数据到存储buf
  • 2、一帧数据发送完毕,串口暂时空闲,触发串口空闲中断
  • 3、在中断服务函数中,可以计算刚才收到了多少个字节的数据
  • 4、解码存储buf,清除标志位,开始下一帧接收

示例代码如下:

void USART1_Init(uint32_t bound)//DMA2_Stream2
{
	GPIO_InitTypeDef GPIO_Initstructure;
	USART_InitTypeDef USART_Initstructure;
	NVIC_InitTypeDef NVIC_Initstrcuture;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE );
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA ,ENABLE );
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
	
    GPIO_Initstructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_Initstructure.GPIO_OType = GPIO_OType_PP;
	GPIO_Initstructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Initstructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_Init(GPIOA,&GPIO_Initstructure);
	GPIO_Initstructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_Initstructure.GPIO_OType = GPIO_OType_PP;
	GPIO_Initstructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Initstructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_Init(GPIOA,&GPIO_Initstructure);
	
	USART_Initstructure.USART_BaudRate = bound;
	USART_Initstructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_Initstructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Initstructure.USART_Parity = USART_Parity_No;
	USART_Initstructure.USART_StopBits = USART_StopBits_1;
	USART_Initstructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1,&USART_Initstructure);
	
	NVIC_Initstrcuture.NVIC_IRQChannel = USART1_IRQn;
	NVIC_Initstrcuture.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_Initstrcuture.NVIC_IRQChannelSubPriority =2;		
	NVIC_Initstrcuture.NVIC_IRQChannelCmd = ENABLE;		
	NVIC_Init(&NVIC_Initstrcuture);	
	
//	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启接收中断
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启空闲中断
	USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);//开启DMA接收
	USART_Cmd(USART1, ENABLE);
	
	//initialize the DMA channel.
	DMA_Config(DMA2_Stream2,DMA_Channel_4, 
						 (uint32_t)&(USART1->DR),     //串口DR寄存器
						 (uint32_t)USART1_Rx_Buffer,//自定义的接收数据buf
						 DMA_DIR_PeripheralToMemory,//外设到存储器方向
						 USART1_RX_BUFFER_SIZE/2);//长度

}

void USART1_IRQHandler(void) 
{
	uint8_t rc_tmp;
	uint16_t rc_len;
	uint16_t i;
	if(USART_GetITStatus(USART1,USART_IT_IDLE)!=RESET)
	{
	  rc_tmp=USART1->SR;
      rc_tmp=USART1->DR;//软件序列清除IDLE标志位
      DMA_Cmd(DMA2_Stream2, DISABLE);关闭DMA,准备重新配置
      DMA_ClearITPendingBit(DMA2_Stream2, DMA_IT_TCIF2);	// Clear Transfer Complete flag
      DMA_ClearITPendingBit(DMA2_Stream2, DMA_IT_TEIF2);	// Clear Transfer error flag	
      rc_len = USART1_RX_BUFFER_SIZE - DMA_GetCurrDataCounter(DMA2_Stream2);//计算接收数据长度
      
	  for(i=0;i<rc_len;i++)//输出每一字节的数据观察
	  {
		 printf("%d ",USART1_Rx_Buffer[i]);
		 usart1.rx_buf[i]=USART1_Rx_Buffer[i];
	  }
	  printf("\n");
      
      Data_Decode(USART1_Rx_Buffer);//解码收到的数据
	}
	DMA_Enable(DMA2_Stream2,USART1_RX_BUFFER_SIZE);//开启下一次DMA接收

}

上述代码经stm32f407平台测试通过

三、纠正

感谢qq_20246035在评论区提出问题。之前一、9部分的实例代码中,DMA_Config函数定义时,数据传输方向直接写死到配置中了,而二、2部分的示例代码中,调用DMA_Config时把数据传输方向作为参数了,运行应该会报错示例和声明不匹配。
两种改正方法:1.调用时不要写传输方向参数 ; 2.修改DMA_Config。目前已按方法二改正

再次感谢读者帮我找到错误,谢谢!
如果大家发现还有什么问题,欢迎在评论区告诉我

  • 95
    点赞
  • 498
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 63
    评论
在使用HAL库进行UART总线空闲中断时,需要进行以下步骤: 1. 在原有的串口1中断服务函数中添加代码,判断是否是空闲中断,并进行相应的处理。例如,可以使用`__HAL_UART_GET_FLAG`函数判断是否是空闲中断,使用`__HAL_UART_CLEAR_IDLEFLAG`函数清除空闲中断标志,使用`HAL_UART_DMAStop`函数停止DMA传输,使用`HAL_UART_Receive_DMA`函数重新打开DMA接收。\[1\] 2. 重定义串口发送完成回调函数,在这里清空标志位。可以使用`HAL_UART_TxCpltCallback`函数,在函数中判断是否是USART1实例,然后释放总线。\[2\] 3. 使用空闲中断进行串口接收。一般情况下,我们使用RXNE中断,在接收到一个字节数据时进入中断并将其放入缓存。但是当数据量很大时,频繁进入中断会影响单片机的时效性。此时可以使用IDLE空闲中断,在接收到一段数据后,在一定的时间内检测到没有数据到来,就认为串口总线空闲,并产生一个空闲中断。\[3\] 以上是使用HAL库进行UART总线空闲中断的步骤。 #### 引用[.reference_title] - *1* *2* [STM32-UART 空闲中断+DMA发送接收](https://blog.csdn.net/my_id_kt/article/details/122412078)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [STM32 hal库串口空闲中断最新用法](https://blog.csdn.net/qq_47159466/article/details/124542339)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 63
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云端FFF

所有博文免费阅读,求打赏鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值