STM32学习和实践笔记(37):DMA实验

news2025/1/13 15:34:07

1.DMA简介

  DMA,全称是Direct Memory Access,中文意思为直接存储器访问。DMA可用于实现外设与存储器之间或者存储器与存储器之间数据传输的高效性。

之所以高效,是因为DMA传输数据移动过程无需CPU直接操作,这样节省的 CPU 资源就可供其它操作使用。同时,因为没有经过CPU进行中间操作,所以效率更高!

从硬件层面来理解,DMA就好像是RAMI/O设备间数据传输的通路,外设与存储器之间或者存储器与存储器之间可以直接在这条通路上进行数据传输。

这里说的外设一般指外设的数据寄存器,比如 ADC、 SPI、 I2C、 DCMI等外设的数据寄存器,存储器一般是指片内SRAM、外部存储器、片内 Flash 等。

   STM32F1 最多有 2 个 DMA 控制器, DMA1 有 7 个通道, DMA2 有 5个通道(DMA2 仅存在STM系列的大容量产品中),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个 DMA 请求的优先权。(很明显,不通CPU来管理,那就需要另外有一个控制器来控制,这就是DMA控制器和仲裁器的作用)

2.DMA结构框图

  DMA 控制器在整个芯片中,它独立于内核,是一个单独的外设,结构比较简单,从编程的角度来看,我们只需掌握结构框图中的三部分内容即可。如图:

1)标号1:DMA请求

  如果外设要想通过 DMA 的方式来传输数据,必须先给 DMA 控制器发送 DMA 请求, DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。

2)标号2:DAM通道

DMA含有DMA1和DMA2两个控制器,其中DMA1含有7个通道,DMA2含有5个通道,不同的 DMA 控制器的通道对应着不同的外设请求,如下图所示:

每个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

DMA1:

(例如:外设ADC1只能通过DMA1的通道1与存储器互相传输数据。)

DMA2:

3)标号3:仲裁器

  当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器管理。

仲裁器管理 DMA 通道请求分为两个阶段。

第一阶段属于软件阶段,可以在DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。

第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的软件优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级

3.DMA数据具体传递方式

1外设到存储器

  当我们使用从外设到存储器传输时,以 ADC 采集为例DMA 外设寄存器的地址对应的就是 ADC 数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据)的地址。方向我们设置外设为源地址。简单来说,就是从ADC数据寄存器地址往存储器对应的地址存放数据!因此首先将DMA外设寄存器地址对应到ADC数据寄存器地址。

2)存储器到外设

  当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例DMA 外设寄存器的地址对应的就是串口数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。

3)存储器到存储器

  当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。DMA 外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。方向我们设置外设(即内部 FLASH)为源地址。

跟上面两个不一样的是,这里需要把 DMA_CCR 位 14: MEM2MEM:存储器到存储器模式配置为 1,启动 M2M 模式。

4.STM32F1 DMA配置步骤

具体步骤如下:(DMA相关库函数在stm32f10x_dma.c和stm32f10x_dma.h文件中)

(1)使能DMA控制器(DMA1或DMA2)时钟

void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

(2)初始化DMA通道,包括配置通道、外设和内存地址、传输数据量等

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct);

typedef struct

{

  uint32_t DMA_PeripheralBaseAddr; // 外设地址

  uint32_t DMA_MemoryBaseAddr; // 存储器地址

  uint32_t DMA_DIR; // 传输方向

  uint32_t DMA_BufferSize; // 传输数目

  uint32_t DMA_PeripheralInc; // 外设地址增量模式

  uint32_t DMA_MemoryInc; // 存储器地址增量模式

  uint32_t DMA_PeripheralDataSize; // 外设数据宽度

  uint32_t DMA_MemoryDataSize; // 存储器数据宽度

  uint32_t DMA_Mode; // 模式选择

  uint32_t DMA_Priority; // 通道优先级

  uint32_t DMA_M2M; // 存储器到存储器模式

} DMA_InitTypeDef;

该结构体各成员的作用介绍如下:

DMA_PeripheralBaseAddr:外设地址,通过DMA_CPAR寄存器设置,一般设置为外设的数据寄存器地址,比如要进行串口DMA 传输,那么外设基地址为串口接受发送数据存储器USART1->DR 的地址,表示方法为&USART1->DR。如果是存储器到存储器模式则设置为其中一个存储区地址。

  DMA_Memory0BaseAddr:存储器地址,通过DMA_CMAR寄存器设置,一般设置为我们自定义存储区的首地址,即我们存放DMA传输数据的内存地址。比如我们定义一个u32类型数组,将数组首地址(直接使用数组名即可)赋值给DMA_Memory0BaseAddr,在DMA传输的时候就可以把数组内的数据发送或接收。

  DMA_DIR:数据传输方向选择,可选择外设到存储器、存储器到外设以及存储器到存储器。通过设定DMA_CCR寄存器的DIR[1:0]位的值决定。比如本章实验是从内存读取数据发送到串口,所以数据传输方向为存储器到外设,配置为DMA_DIR_MemoryToPeripheral。

  DMA_BufferSize:用来设置一次传输数据的大小,通过DMA_CNDTR寄存器设置。

  DMA_PeripheralInc:用来设置外设地址是递增还是不变,通过DMA_CCR寄存器的PINC位设置,如果设置为递增,那么下一次传输的时候地址加1。通常外设只有一个数据寄存器,所以一般不会使能该位,即配置为DMA_PeripheralInc_Disable。

  DMA_MemoryInc:用来设置内存地址是否递增,通过DMA_CCR寄存器的MINC位设置。我们自定义的存储区一般都是存放多个数据的,所以需要使能存储器地址自动递增功能,即配置为DMA_MemoryInc_Enable。

  DMA_PeripheralDataSize:外设数据宽度选择,可以为字节(8位)、半字(16位)、字(32位),通过DMA_CCR寄存器的PSIZE[1:0]位设置。例如本章实验数据是按照8位字节传输,所以配置为DMA_PeripheralDataSize_Byte。

  DMA_MemoryDataSize:存储器数据宽度选择,可以为字节(8位)、半字(16位)、字(32位),通过DMA_CCR寄存器的MSIZE[1:0]位设置。本章实验同样设置为8位字节传输,这个要和我们定义的数组对应,所以配置为DMA_MemoryDataSize_Byte。

  DMA_Mode:DMA传输模式选择,可选择一次传输或者循环传输,通过DMA_CCR寄存器的CIRC位来设定。比如我们要从内存(存储器)中传输64个字节到串口,如果设置为循环传输,那么它会在64个字节传输完成之后继续从内存的第一个地址传输,如此循环。这里我们设置为一次传输完成之后不循环。所以设置值为DMA_Mode_Normal。

  DMA_Priority:用来设置DMA通道的优先级,有低,中,高,超高四种级别,可通过DMA_CCR寄存器的PL[1:0]位来设定。DMA优先级只有在多个DMA数据流同时使用时才有意义,本章实验我们只使用了一个DMA数据流,所以可以任意设置DMA优先级,这里我们就设置为中等优先级,配置参数为DMA_Priority_Medium。

DMA_M2M:用来设置存储器到存储器模式,使用存储器到存储器时用到,设定DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式

了解结构体成员功能后,就可以进行配置,本章实验配置代码如下

  DMA_InitTypeDef  DMA_InitStructure;

  DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址

  DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存储器0地址

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存储器到外设模式

  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_Medium;//中等优先级

  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输

  DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化DMA

(3)使能外设DMA功能(DMA请求映射图对应的外设)

USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);  //使能串口1的DMA发送

(4)开启DMA的通道传输

void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

DMA_Cmd(DMA1_Channel4,ENABLE);

(5)查询DMA传输状态

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

例如我们要查询DMA1通道4传输是否完成,方法是:

DMA_GetFlagStatus(DMA1_FLAG_TC4);

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);

5.硬件电路

  本实验使用到硬件资源如下

1)D1和D2指示灯

2)K_UP按键

3)串口1

4)DMA

D1指示灯用来提示系统运行状态,K_UP按键用来控制DMA发送,每按一次K_UP键,DMA就将内存(自定义的一个数组)内数据发送USART1,并通过串口1将发送的内容打印出来。在DMA数据传输的过程中让D2指示灯不断闪烁,直到数据传输完成。D2指示灯闪烁表CPU在执行其他的任务,说明DMA传输是不需要占用CPU的。

6.编写DMA控制程序

  本实验所要实现的功能是:通过K_UP按键控制DMA串口1数据的传送,在传送过程中让D2指示灯不断闪烁,直到数据传送完成。D1指示灯闪烁提示系统正常运行。程序框架如下:

(1)初始化USART1_TX对应的DMA通道相关参数

(2)编写主函数

main.c

#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "key.h"
#include "dma.h"

#define send_buf_len 5000 //5000个字节长度

u8 send_buf[send_buf_len];

void Send_Data(u8 *p)
{
	u16 i;
	for (i=0; i<send_buf_len; i++)
	{
		*p='5';//将5000个字节里的内容全部设为字符5
		p++;
	}
	
}



int main()
{
	u8 i=0;
	u8 key=0;



	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断优先级分组
	LED_Init();
	USART1_Init(9600);
	key_Init();
	DMAx_Init(DMA1_Channel4,(u32)&USART1->DR,(u32)send_buf,send_buf_len);
	Send_Data(send_buf);//将5000个字节里的内容全部设为字符5


		while(1)
	{
		key = KEY_Scan(0);
		
		if(key == KEY_UP)
		{
			USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);//使能串口1的DMA功能
			DMAx_Enable(DMA1_Channel4,send_buf_len);
			
			while(1)
			{
				if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=0)//检查传输是否完成
				{
					DMA_ClearFlag(DMA1_FLAG_TC4);
					break;//完成则退出while,led2就不再闪
						
				}
				led2=!led2; //没完成就一直闪
				delay_ms(300);
				
			}
			
		}

		
		i++;
		if(i%20 ==0)
			{
				led1=!led1;//LED1闪,用来指示主程序循环是否运行
                delay_ms(300);
			}
			
		
	}
		
}


        dma.c

#include "dma.h"
#include "SysTick.h"


void DMAx_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 par,u32 mar,u16 ndtr)
{
	
	DMA_InitTypeDef DMA_InitStructure;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//使能DMA1时钟
	
	DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
	DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存储器0地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//传输方向:存储器到外设
	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_Medium;//中等优先级
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
	DMA_Init(DMAy_Channelx, &DMA_InitStructure);  //初始化DMA通道
		
	
}

void DMAx_Enable(DMA_Channel_TypeDef* DMAy_Channelx,u16 ndtr)
{
	DMA_Cmd(DMAy_Channelx,DISABLE); //
	DMA_SetCurrDataCounter(DMAy_Channelx, ndtr);
	DMA_Cmd(DMAy_Channelx,ENABLE);

}







       程序写到开发板上测试,结果显示如下,LED也正常闪 ,实验是成功的!

             

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1848928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT基础 - 文本文件读写

目录 零. 前言 一.读取文件 二. 写入文件 三. 和二进制读写的区别 零. 前言 在 Qt 中&#xff0c;对文本文件进行读写操作是常见的任务之一。这对于保存和加载配置信息、处理数据文件等非常有用。 Qt 提供了多种方式来读写文本文件&#xff0c;使得文件操作变得相对简单和…

攻防世界-intoU

下载附件发现是wav文件&#xff0c;扔Audacity里面 将采样率&#xff08;右击选择&#xff09;改为900&#xff0c;之后再查看频谱图 再将进度条拉到最后 得到flag&#xff1a; RCTF{bmp_file_in_wav}

最新版ChatGPT对话系统源码 Chat Nio系统源码

最新版ChatGPT对话系统源码 Chat Nio系统源码 支持 Vision 模型, 同时支持 直接上传图片 和 输入图片直链或 Base64 图片 功能 (如 GPT-4 Vision Preview, Gemini Pro Vision 等模型) 支持 DALL-E 模型绘图 支持 Midjourney / Niji 模型的 Imagine / Upscale / Variant / Re…

二,SpringFramework

二、SpringFramework实战指南 目录 一、技术体系结构 1.1 总体技术体系1.2 框架概念和理解 二、SpringFramework介绍 2.1 Spring 和 SpringFramework概念2.2 SpringFramework主要功能模块2.3 SpringFramework 主要优势 三、Spring IoC容器和核心概念 3.1 组件和组件管理概念3…

【深度学习驱动流体力学】OpenFOAM框架剖析

目录 1. applications 目录solvers:存放各种求解器。mesh:网格生成相关工具。2. src 目录3. tutorials 目录其他主要目录和文件参考OpenFOAM 源码文件目录的框架如下,OpenFOAM 是一个开源的计算流体力学 (CFD) 软件包,其源码文件结构设计精巧,分为多个主要目录,每个目录都…

jeecg-boot项目的部署-windows系统

一、基础环境的准备&#xff1a; 1、后台基础环境&#xff1a;JDK、redis、数据库&#xff1a;sqlserver 2、前端基础环境&#xff1a;nginx redis和nginx的安装都很方便&#xff0c;直接去对应的官网&#xff0c;下载zip压缩包&#xff0c;然后解压&#xff0c;执行.exe文件…

制作WIFI二维码,实现一键扫描连接WIFI

在现代社会&#xff0c;Wi-Fi已成为我们日常生活中不可或缺的一部分。无论是在家庭、办公室还是公共场所&#xff0c;我们都希望能够快速方便地连接到Wi-Fi网络。下面小编就来和大家分享通过制作WIFI二维码&#xff0c;来实现一键扫描就可以连接WIFI的方法。连接WIFI不用在告诉…

计算机网络 VLAN间路由单臂路由

一、理论知识 VLAN是一种将物理网络划分成多个逻辑网络的方法。不同的VLAN属于不同的网段&#xff0c;因此互相通信需要通过路由器进行路由。通常情况下&#xff0c;在同一VLAN内的设备可以直接通信&#xff0c;而不同VLAN之间的设备则需要通过路由器转发数据。本实验利用单臂…

洛谷——P2824 排序

题目来源&#xff1a;[HEOI2016/TJOI2016] 排序 - 洛谷https://www.luogu.com.cn/problem/P2824 问题思路 本文介绍一种二分答案的做法&#xff0c;时间复杂度为&#xff1a;(nm)*log(n)*log(n).本题存在nlog(n)的做法&#xff0c;然而其做法没有二分答案的做法通俗易懂. 默认读…

水系统阻力计算

所谓水泵的选取计算其实就是估算&#xff08;很多计算公式本身就是估算的&#xff09;&#xff0c;估算分的细致些考虑的内容全面些就是精确的计算。 特别补充&#xff1a;当设计流量在设备的额定流量附近时&#xff0c;上面所提到的阻力可以套用&#xff0c;更多的是往往都大…

【前端技术】标签页通讯localStorage、BroadcastChannel、SharedWorker的技术详解

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Apple - Text Attribute Programming Topics

本文翻译整理自&#xff1a;Text Attribute Programming Topics&#xff08;更新日期&#xff1a;2004-02-16 https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TextAttributes/TextAttributes.html#//apple_ref/doc/uid/10000088i 文章目录 一、文…

http发展史(http0.9、http1.0、http1.1、http/2、http/3)详解

文章目录 HTTP/0.9HTTP/1.0HTTP/1.1队头阻塞&#xff08;Head-of-Line Blocking&#xff09;1. TCP 层的队头阻塞2. HTTP/1.1 的队头阻塞 HTTP/2HTTP/3 HTTP/0.9 发布时间&#xff1a;1991年 特点&#xff1a; 只支持 GET 方法没有 HTTP 头部响应中只有 HTML 内容&#xff0…

C语言入门系列:可迁移的数据类型

文章目录 1&#xff0c;精确宽度类型(exact-width integer type)2&#xff0c;最小宽度类型&#xff08;minimum width type&#xff09;3&#xff0c;最快的最小宽度类型&#xff08;fast minimum width type&#xff09;4&#xff0c;可以保存指针的整数类型。5&#xff0c; …

云原生微服务开发日趋成熟:有效拥抱左移以改善交付

在软件工程和应用程序开发方面&#xff0c;云原生已经成为许多团队的常用术语。当人们调查云原生的世界时&#xff0c;他们经常会得出这样的观点&#xff1a;云原生的整个过程都是针对大型企业应用程序的。几年前&#xff0c;情况可能确实如此&#xff0c;但随着 Kubernetes 等…

Redis-使用 jedis 操作数据

文章目录 1、Jedis简介2、环境准备3、创建maven普通项目,导入如下依赖4、测试JAVA程序和Redis之间的通信 1、Jedis简介 "Jedis" 通常是作为 "Java Redis" 的缩写或简称来理解的。Java Embedded Data Structures Interface 表示 Java嵌入式数据结构接口 2、…

45、基于深度学习的螃蟹性别分类(matlab)

1、基于深度学习的螃蟹性别分类原理及流程 基于深度学习的螃蟹性别分类原理是利用深度学习模型对螃蟹的图像进行训练和识别&#xff0c;从而实现对螃蟹性别的自动分类。整个流程可以分为数据准备、模型构建、模型训练和性别分类四个步骤。 数据准备&#xff1a; 首先需要收集包…

C# 中的隐式和显式类型转换

当你需要转换值的类型时&#xff0c;可以使用类型转换。只有当你要转换的两种类型兼容时&#xff0c;才有可能。 当你收到错误“无法隐式将一种类型转换为另一种类型”时&#xff0c;说明你转换的两种类型不兼容。 int integer; // 声明一个名为 integer 的整型变量 integer …

【Gradio】如何设置 Gradio 数据框的样式

简介 数据可视化是数据分析和机器学习的关键方面。Gradio DataFrame 组件是一种流行的方式&#xff0c;在网络应用程序中显示表格数据&#xff08;特别是以 pandas DataFrame 对象的形式&#xff09;。 本文将探讨 Gradio 的最新增强功能&#xff0c;这些功能允许用户整合 pand…

解决vmware “处理器不支持 XSAVE。无法打开此虚拟机的电源。“

1,打开windows 10-11 的 虚拟机平台 选择 “开始”&#xff0c;输入“Windows 功能”&#xff0c;然后从结果列表中选择“打开或关闭 Windows 功能 ”。 在刚刚打开的 “Windows 功能”窗口中&#xff0c;找到“虚拟机平台 ”并将其选中。 选择“确定”。 可能需要重启电脑。…