STM32学习笔记12:DMA 的学习和使用_芯片的dma-程序员宅基地

技术标签: stm32  学习  笔记  STM32学习笔记  

芯片型号:STM32F103RC

软件开发包:标准外设库

一、DMA 简介

  • DMA(Direct Memory Access)直接存储器存取
  • DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
  • 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道),DMA2 只存在于大容量的单片机中
  • 每个通道都支持软件触发和特定的硬件触发

二、DMA 框图

DMA 框图

2.1 DMA 请求

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

2.2 通道

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

2.3 仲裁器

当多个 DMA 通道请求同时发生时,仲裁器负责管理其处理顺序。

仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先级越高,比如通道 0 高于通道 1。此外,在大容量产品和互联型产品中, DMA1 控制器的优先级高于 DMA2 控制器。

三、DMA 基本结构

DMA 基本结构

3.1 站点以及相关参数

如图,外设寄存器、Flash 和 SRAM 分别是数据转运的两大站点,前者是外设寄存器站点,后两者组成存储器站点。在 STM32 中,存储器一般特指 Flash 和 SRAM,不包含外设寄存器,所以就描述成外设到存储器、存储器到存储器。

DMA 的数据转运可以是从外设到存储器,也可以从存储器到外设,通过配置参数来控制方向。另外还可以从存储器转运到存储器,比如 Flash 到 SRAM或者 SRAM 到 SRAM。由于 Flash 是只读的,所以 DMA 不可以进行 SRAM 到 Flash,或者 Flash 到 Flash 的转运操作。

进行数据转运就需要指定起始点和终点以及转运方式,对此,外设和存储器都有 3 个参数,分别是起始地址,数据宽度和地址是否自增

起始地址分为外设端的起始地址和存储器端的起始地址,决定了数据起始点和终点;数据宽度指定了一次转运要按多大的数据宽度来进行,可以选择字节、半字和字;地址是否自增指定一次转运完成后下一次转运是否要移动地址。

如果要进行存储器到存储器的数据转运,就需要把其中一个存储器的地址放在外设的站点。只要在外设起始地址里写 Flash 或者 SRAM 的地址,那它就会去Flash 或者 SRAM 找数据。这个站点只是叫做外设寄存器,并没限制只能指定为外设,存储器站点也是如此。

3.2 传输计数器

下面有个传输计数器,用于指定转运次数的,是一个自减计数器,当减到 0 之后,DMA 就不会再进行数据转运了。另外,它减到 0 之后,之前自增的地址也会恢复到起始地址,以方便新一轮的转换。

在传输寄存器的右边有一个自动重装器,作用为传输计数器减到 0 之后,是否要自动恢复到最初的值。比如最初传输计数器值为 5,如果不使用自动重装器,转运 5 次后,DMA 就停止了;如果使用自动重装器,转运 5 次,计数器减到 0 后,就会立即恢复到 5。所以,自动重装器决定了转运的模式,单次模式或循环模式。

3.3 触发控制

再下面就是 DMA 的触发控制,有硬件触发和软件触发, 由 DMA_CCRx 寄存器的 MEM2MEM 位控制。

软件触发不是调用某个函数一次,触发一次,而是以最快的速度,连续不断地触发 DMA,尽快使传输计数器清零,完成一轮的转换。**软件触发和循环模式不能同时使用,因为软件触发就是想把传输计数器清零,循环模式是清零后自动重装,如果同时用的话,DMA 就停不下来了。**软件触发一般适用于存储器到存储器的转运,因为存储器到存储器的转运是软件启动,不需要时机,并且想尽快完成。

硬件触发源可以选择 ADC、串口、定时器等,使用硬件触发的转运,一般都与外设有关,这些转运需要一定的时机,比如 ADC 转换完成。串口收到数据。定时时间到等,所以需要使用硬件触发,在硬件达到这些时机时,传递信号来触发 DMA 进行转运。

3.4 开关控制

最后还有一个开关控制,也就是 DMA_Cmd 函数。当 DMA 使能后,DMA 就准备就绪了,可以进行转运了。

3.5 转运的条件

DMA 进行转运有几个条件:

  1. 开关控制,必须使能 DMA;
  2. 传输计数器必须大于 0;
  3. 必须有触发信号,触发一次,转运一次,传输计数器自减 1,当传输计数器 = 0,且没有自动重装时,这时无论是否触发,DMA 都不会再进行转运了。此时就需要 DMA_Cmd 给 DISABLE,关闭 DMA,再为传输计数器写如一个大于 0 的数,再 DMA_Cmd 给 ENABLE,使能 DMA。

注意:写传输计数器时,必须先关闭 DMA,再进行。不能在 DMA 开启时,写传输计数器。

四、DMA请求

DMA请求映像

如图是 DMA1 的请求映像,有 7 个通道,每个通道都有一个数据选择器,可以选择软件触发或硬件触发。每个通道的硬件触发源不同,所以需要根据触发源选择对应的通道。如果使用软件触发,就可以任意选择通道,因为每个通道的软件触发都是一样的。

五、数据宽度与对齐

数据宽度与对齐

如图,如果源端宽度等于目标宽度,数据正常转运;如果源端宽度小于目标宽度,目标数据高位就会补 0;如果源端宽度大于目标宽度,目标数据高位就会舍去。

六、DMA 初始化结构体详解

typedef struct
{
    
    uint32_t DMA_PeripheralBaseAddr; 	// 外设地址
    uint32_t DMA_PeripheralDataSize; 	// 外设数据宽度
    uint32_t DMA_PeripheralInc; 		// 外设地址是否自增
    uint32_t DMA_MemoryBaseAddr; 		// 存储器地址
    uint32_t DMA_MemoryDataSize; 		// 存储器数据宽度
    uint32_t DMA_MemoryInc; 			// 存储器地址是否自增
    uint32_t DMA_DIR; 					// 传输方向
    uint32_t DMA_BufferSize; 			// 传输数目,对应结构图的传输计数器的值
    uint32_t DMA_Mode; 					// 模式选择,对应结构图就是是否使用自动重装
    uint32_t DMA_M2M; 					// 选择是否使用存储器到存储器模式,其实就是选择硬件触发还是软件触发    
    uint32_t DMA_Priority; 				// 通道优先级
} DMA_InitTypeDef;
  1. DMA_PeripheralBaseAddr:外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。
  2. DMA_PeripheralDataSize:外设数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位),它设定 DMA_CCR 寄存器的 PSIZE[1:0] 位的值。
  3. DMA_PeripheralInc:使能或失能外设地址自增功能,它设定 DMA_CCR 寄存器的 PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不使能该功能。
  4. DMA_MemoryBaseAddr:存储器地址,设定 DMA_CMAR 寄存器的值;一般设置为自定义存储区的首地址。、
  5. DMA_MemoryDataSize:存储器数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位),它设定 DMA_CCR 寄存器的 MSIZE[1:0] 位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为相同大小。
  6. DMA_MemoryInc:使能或失能存储器地址自增功能,它设定 DMA_CCR 寄存器的 MINC 位的值;自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自增功能。
  7. DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定 DMA_CCR 寄存器的DIR[1:0] 位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。
  8. DMA_BufferSize:设定待传输数据数目,设定 DMA_CNDTR 寄存器的值。
  9. DMA_Mode:DMA 传输模式选择,可选单次传输或者循环传输,它设定 DMA_CCR 寄存器的 CIRC 位的值。
  10. DMA_M2M:存储器到存储器模式,使用存储器到存储器时用到,置位 DMA_CCR 的 MEN2MEN 位即可启动存储器到存储器模式。
  11. DMA_Priority:软件设置通道的优先级,有 4 个可选优先级分别为非常高、高、中和低,它设定 DMA_CCR 寄存器的 PL[1:0] 位的值。 DMA 通道优先级只有在多个 DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。

七、举例(数据转运+DMA)

7.1 任务分析

数据转运+DMA

对照 DMA 的基本结构分析该任务:

  1. 外设站点和存储器站点的三个参数。

    • 在该任务里,外设地址是 DataA 数组的首地址,存储器地址是 DataB 的首地址;
    • 然后是数据宽度,两个数组的类型都是 uint8_t,所以数据宽度设置为 8 位的字节;
    • 之后地址是否自增,图中的效果是 DataA[0] 转到 DataB[0] 、DataA[1] 转到 DataB[1] 等等,两个数组的位置一一对应,所以转运完 DataA[0] 和 DataB[0] 之后,两个站点都应该自增,移动到下一个数据的位置,继续转运。
  2. 数组有 7 个元素,要转运 7 次,所以传输计数器写 7,自动重装暂时不需要。

  3. 触发选择部分,因为是存储器到存储器,所以使用软件触发。

  4. 使能 DMA,这样数据就会从 DataA 转运到 DataB。

这里的数据转运是一种复制转运,转运完成后,DataA 的数据并不会消失。

7.2 软件设计

为了提高程序的复用功能,将外设地址、存储器地址和传输数据数目设为初始化函数 UserDMA_Init 的参数,之后,在调用初始化函数 UserDMA_Init 时,就可以根据具体情况进行传参。同时,为了可以在数据更改后,可以继续使用 DMA 转运数据,编写函数 UserDMA_Transfer,并将使能 DMA 的工作交给该函数。

static uint16_t transfer_size; // 静态变量,存储 DMA 传输数目

void UserDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t size)
{
    
    DMA_InitTypeDef DMA_InitStructure;

    transfer_size = size; // 保存传输大小

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 使能 DMA1 时钟

    DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; 						// 设置外设基地址
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据宽度为字节
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; 		// 使能外设地址自增
    DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; 							// 设置存储器基地址
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 		// 存储器数据宽度为字节
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 				// 使能存储器地址自增
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; 						// 设置数据传输方向为外设到存储器
    DMA_InitStructure.DMA_BufferSize = size; 								// 设置传输数据数目
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; 							// 设置为单次模式
    DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; 							// 使能存储器到存储器模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 					// 设置优先级为中等
    DMA_Init(DMA1_Channel1, &DMA_InitStructure); 							// 初始化 DMA1 的通道1
}

void UserDMA_Transfer(void)
{
    
    DMA_Cmd(DMA1_Channel1, ENABLE); 						// 使能 DMA1 的通道1
    while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); 		// 等待传输完成标志位置位
    DMA_ClearFlag(DMA1_FLAG_TC1); 							// 清除传输完成标志位
    
    DMA_Cmd(DMA1_Channel1, DISABLE); 						// 失能 DMA1 的通道1
    DMA_SetCurrDataCounter(DMA1_Channel1, transfer_size); 	// 设置当前数据传输数目
}

注意: 传给初始化函数 UserDMA_Init 的前两个参数只是表示地址的整形数,并不是真的地址,因为 C 语言中使用指针变量保存地址的,如果直接传地址,会因为类型不一致,而产生警告。比如,想将数组 DataA 的数据转运到数组 DataB 中,那么应该传递的参数是 (uint32_t)DataA(uint32_t)DataB,而不是直接传 DataA 和 DataB。

参考视频源于B站up主: 野火科技、江协科技
参考文档:《STM32库开发实战指南——基于野火MINI开发板》

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_52540136/article/details/135345482

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue