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

2023-05-16

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

目录

  • 一、DMA
    • 1、简介
    • 2、使用场景
    • 3、主要特性
    • 4、DMA控制器结构
    • 5、DMA请求映射
    • 6、指针递增
    • 7、循环模式
    • 8、其他
    • 9、示例代码
  • 二、串口空闲中断
    • 1、常用的串口接收中断
    • 2、串口空闲中断
  • 三、纠正

在进行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。目前已按方法二改正

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

stm32 利用DMA+串口空闲中断接受任意长数据 的相关文章

  • 137-基于stm32单片机智能保温杯控制装置Proteus仿真+源程序

    资料编号 137 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 DS18B20传感器 电机 制作一个基于stm32单片机智能保温杯控制装置Proteus仿真 2 通过DS18b20传感器检测当前保温杯水的温度 并且
  • HAL库STM32常用外设教程(二)—— GPIO输入\输出

    HAL库STM32常用外设教程 二 GPIO输入 输出 文章目录 HAL库STM32常用外设教程 二 GPIO输入 输出 前言 一 GPIO功能概述 二 GPIO的HAl库驱动 三 GPIO使用示例 1 示例功能 四 代码讲解 五 总结
  • STM32F4 通过软复位跳转到引导加载程序,无需 BOOT0 和 BOOT1 引脚

    我问这个问题是因为可以在这里找到类似问题的答案 通过应用程序跳转到 STM32 中的引导加载程序 即从用户闪存在引导模式下使用引导 0 和引导 1 引脚 用户 JF002 JF002回答 当我想跳转到引导加载程序时 我在其中一个备份寄存器中
  • [MM32硬件]搭建灵动微MM32G0001A6T的简易开发环境

    作为学习单片机的经典 自然是通过GPIO点亮LED 或者是响应按钮的外部中断例程 这我们看看SOP8封装的芯片MM32G0001A6T得引脚 除了VDD和GND固定外 我们可以使用PA14 PA1 PA13 PA15 PA2 PA3这六个G
  • VS Code 有没有办法导入 Makefile 项目?

    正如标题所说 我可以从现有的 Makefile 自动填充 c cpp properties json 吗 Edit 对于其他尝试导入 makefile 的人 我找到了一组脚本 它们完全可以实现我想要实现的目标 即通过 VS Code 管理
  • 串口通讯第一次发送数据多了一字节

    先初始化IO再初始化串口 导致第一次发送时 多出一个字节数据 优化方案 先初始化串口再初始化IO 即可正常通讯
  • 1.69寸SPI接口240*280TFT液晶显示模块使用中碰到的问题

    1 69寸SPI接口240 280TFT液晶显示模块使用中碰到的问题说明并记录一下 在网上买了1 69寸液晶显示模块 使用spi接口 分辨率240 280 给的参考程序是GPIO模拟的SPI接口 打算先移植到FreeRtos测试 再慢慢使用
  • for循环延时时间计算

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 pandas是什么 二 使用步骤 1 引入库 2 读入数据 总结 前言 之前做led点亮的实验 好像是被delay函数影响了 因为delay参数设置的不对
  • 特殊寄存器

    特殊寄存器 文章目录 前言 一 背景 二 2 1 2 2 总结 前言 前期疑问 STM32特殊寄存器到底是什么 特殊寄存器怎么查看和调试代码 本文目标 记录和理解特殊寄存器 一 背景 最近在看ucosIII文章是 里面提到特殊寄存器 这就进
  • Cortex-M3与M4权威指南

    处理器类型 所有的ARM Cortex M 处理器是32位的精简指令集处理器 它们有 32位寄存器 32位内部数据路径 32位总线接口 除了32位数据 Cortex M处理器也可以有效地处理器8位和16位数据以及支持许多涉及64位数据的操作
  • 从哪里开始学习 Linux DMA/设备驱动/内存分配

    我正在移植 调试设备驱动程序 由另一个内核模块使用 并面临死胡同 因为 dma sync single for device 因内核错误而失败 我不知道这个函数应该做什么 而且谷歌搜索也没有什么帮助 所以我可能需要了解更多关于这个东西的知识
  • 通过JTAG恢复STM32 MCU磨掉的标记

    我有一块可能带有 STM32 MCU 的板 我想为该板制作定制固件 因为库存板有很多问题 不幸的是 电路板制造商很友善地磨掉了所有标记 有没有办法通过 jtag 获取设备 系列 ID 并将其交叉引用到型号 我能找到的一切都是关于获取芯片的唯
  • 如何从cdev获取设备

    我正在编写一个内核模块 它将分配一些一致的内存并返回相应的虚拟和物理地址 我正在将模块注册为cdev 分配空间dma alloc coherent 我想使用 mmap 它dma common mmap dma common mmap 需要一
  • 嵌入式 C++11 代码 — 我需要 volatile 吗?

    采用 Cortex M3 MCU STM32F1 的嵌入式设备 它具有嵌入式闪存 64K MCU固件可以在运行时重新编程闪存扇区 这是由闪存控制器 FMC 寄存器完成的 所以它不像a b那么简单 FMC 获取缓冲区指针并将数据刻录到某个闪存
  • STM32内部时钟

    我对 STM32F7 设备 意法半导体的 Cortex M7 微控制器 上的时钟系统感到困惑 参考手册没有充分阐明这些时钟之间的差异 SYSCLK HCLK FCLK 参考手册中阅读章节 gt RCC 为 Cortex 系统定时器 SysT
  • PWM DMA 到整个 GPIO

    我有一个 STM32F4 我想对一个已与掩码进行 或 运算的 GPIO 端口进行 PWM 处理 所以 也许我们想要 PWM0b00100010一段时间为 200khz 但随后 10khz 后 我们现在想要 PWM0b00010001 然后
  • 在 Contiki 程序中使用 malloc

    考虑以下 Contiki 程序 include
  • 移动数组中的元素

    我需要一点帮助 我想将数组中的元素向上移动一个元素 以便新位置 1 包含位置 1 中的旧值 new 2 包含 old 1 依此类推 旧的最后一个值被丢弃 第一个位置的新值是我每秒给出的新值 我使用大小为 10 的数组 uint32 t TE
  • stm32l0: 执行MI命令失败。使用 vFlashErase 数据包擦除闪存时出错

    我正在使用 Nucleo STM32L031 和 AC6 STM32 工作台 eclipse 我编写应用程序并进入调试模式 一切正常 直到我在应用程序中添加另一个功能 我注意到当我删除 评论 新函数 软件可以再次进入调试模式 但是当我添加
  • 读取STM32 MCU SPI数据寄存器的值

    有很多类似的问题 但似乎没有一个问题完全相同 我正在将 STML4 MCU 连接到 6 轴传感器 LSM6DS3 我已经成功地在 I2C 中实现了所有内容 但想要 SPI 的额外速度 和 DMA 如果我能让这些第一步工作起来的话 因此 第一

随机推荐