历史上的今天
返回首页

历史上的今天

今天是:2025年02月14日(星期五)

正在发生

2019年02月14日 | STM32系统学习——DMA(直接储存器访问)

2019-02-14 来源:eefocus

DMA主要功能是传输数据,但是不需要占用CPU,即在传输数据时,CPU可以做别的事,像多线程。数据传输从外设到存储器或者从存储器到存储器。DMA控制器包含了DMA1和DMA2,其中DMA1有7个通道,DMA2有5个通道,可以理解为传输数据的一种管道。要注意的是,DMA2只存在于大容量单片机中。 


一、DMA框图解析 


DMA控制器独立于内核,属于一个单独外设,结构结合下图来看 

 

这里写图片描述 


1.DMA请求 


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


DMA有DMA1和DMA2两个控制器,DMA1有两个控制器,DMA1有7个通道,DMA2有5个通道,不同DMA控制器的通道有不同的外设请求。 


2、通道 


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


3、仲裁器 


当同时有多个DMA请求时,就意味着有先后响应的问题,这个就由仲裁器管理。仲裁器管理DMA请求分为2个阶段:第一阶段属于软件阶段,可以在MDA_CCRx寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。


这里写图片描述

这里写图片描述

二、DMA数据配置 


使用DMA,最核心的就是配置要传输的数据。 


1、从哪儿来,到哪儿去 


DMA传输数据 的方向有3个:外设到存储器,存储器到外设,存储器到存储器。具体方向由DMA_CCR中第四位DIR配置:0表示外设到存储器,1表示存储器到外设。涉及的地址由DMA_CPAR配置,存储器地址由DMA_CMAR配置。 


1)从外设到存储器 


以ADC采集为例,DMA外部寄存器地址对应ADC数据寄存器地址,DMA存储器地址是我们自定义的变量的地址。方向设置为源地址。 


2)存储器到外设 


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


3)存储器到存储器 


当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。 


DMA外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。方向我们设置外设(即内部 FLASH)为源地址。跟上面两个不一样的是,这里需要把 DMA_CCR 位 14:MEM2MEM:存储器到存储器模式配置为 1,启动 M2M 模式。 


2、要传什么,单位是多少 


以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR 配置,这是一个 32位的寄存器,一次最多只能传输 65535 个数据。 


要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是 8位的,所以我们定义的要发送的数据也必须是 8 位。外设的数据宽度由 DMA_CCR 的PSIZE[1:0]配置,可以是 8/16/32位,存储器的数据宽度由 DMA_CCR 的 MSIZE[1:0]配置,可以是 8/16/32 位。 


在 DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。外设的地址指针由 DMA_CCRx 的 PINC 配置,存储器的地址指针由 MINC 配置。以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加 1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。 


3、什么时候传输完成 


数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。每个DMA 通道在 DMA 传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。有关各个标志位的详细描述请参考 DMA 中断状态寄存器DMA_ISR的详细描述。 


传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传输的话,必须关断 DMA 使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。具体的由 DMA_CCR寄存器的 CIRC 循环模式位控制。


三、DMA初始化结构体 


结构体 xxx_InitTypeDef 定义在stm32f10x_xxx.h(后面xxx为外设名称)文件中,库函数xxx_Init定义在stm32f10x_xxx.c文件中。


                    DMA_ InitTypeDef 初始化结构体

1 typedef struct

2 {

3 uint32_t DMA_PeripheralBaseAddr; // 外设地址

4 uint32_t DMA_MemoryBaseAddr; // 存储器地址

5 uint32_t DMA_DIR; // 传输方向

6 uint32_t DMA_BufferSize; // 传输数目

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

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

9 uint32_t DMA_PeripheralDataSize; // 外设数据宽度

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

11 uint32_t DMA_Mode; // 模式选择

12 uint32_t DMA_Priority; // 通道优先级

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

14 } DMA_InitTypeDef;



1) DMA_PeripheralBaseAddr:外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。 


2) DMA_Memory0BaseAddr:存储器地址,设定 DMA_CMAR 寄存器值;一般设置为我们自定义存储区的首地址。 


3) DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定DMA_CCR 寄存器的 DIR[1:0]位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。 


4) DMA_BufferSize:设定待传输数据数目,初始化设定 DMA_CNDTR 寄存器的值。 


5) DMA_PeripheralInc:如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定 DMA_CCR 寄存器的 PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。 


6) DMA_MemoryInc:如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定 DMA_CCR 寄存器的 MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。 


7) DMA_PeripheralDataSize:外设数据宽度,可选字节(8位)、半字(16位)和字(32位),它设定 DMA_CCR寄存器的 PSIZE[1:0]位的值。 


8) DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字(16 位)和字(32位),它设定 DMA_CCR 寄存器的 MSIZE[1:0]位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。 


9) DMA_Mode:DMA 传输模式选择,可选一次传输或者循环传输,它设定DMA_CCR 寄存器的 CIRC 位的值。例程我们的 ADC 采集是持续循环进行的,所以使用循环传输模式。 


10) DMA_Priority:软件设置通道的优先级,有 4 个可选优先级分别为非常高、高、中和低,它设定 DMA_CCR 寄存器的 PL[1:0]位的值。DMA 通道优先级只有在多个 DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。 


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


四、存储器到存储器的实验 


先定义一个静态的源数据,存放在内部Flash存储器中,使用DMA传输,把源数据拷贝到目标地址上(内部SRAM),最后对比源数据和目标地址的数据,看看是否准确传输。 


1、思路要点 

1)使能DMA时钟 

2)配置DMA数据参数 

3)使能DMA,进行传输 

4)等待传输完成,并对源数据和目标地址数据进行比较。 


2、DMA宏定义以及变量定义


1 // 当使用存储器到存储器模式时候,通道可以随便选,没有硬性的规定

2 #define DMA_CHANNEL DMA1_Channel6

3 #define DMA_CLOCK RCC_AHBPeriph_DMA1

5 // 传输完成标志

6 #define DMA_FLAG_TC DMA1_FLAG_TC6

8 // 要发送的数据大小

9 #define BUFFER_SIZE 32

10 

11 /* 定义 aSRC_Const_Buffer 数组作为 DMA 传输数据源

12 * const 关键字将 aSRC_Const_Buffer 数组变量定义为常量类型

13 * 表示数据存储在内部的 FLASH 中

14 */

15 const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]=

16 {

17 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,

18 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,

19 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,

20 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,

21 0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,

22 0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,

23 0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,

24 0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80

25 };

26 /* 定义 DMA 传输目标存储器

27 * 存储在内部的 SRAM 中

28 */

29 uint32_t aDST_Buffer[BUFFER_SIZE];


aSRC_Const_Buffer[BUFFER_SIZE]定义用来存放源数据,并且使用了const关键字修饰,即常量类型,使得变量存储在内部Flash空间上。


3、DMA数据配置


 void DMA_Config(void)

2 {

3 DMA_InitTypeDef DMA_InitStructure;

5 // 开启 DMA 时钟

6 RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE);

7 // 源数据地址

8 DMA_InitStructure.DMA_PeripheralBaseAddr =

9 (uint32_t)aSRC_Const_Buffer;

10 // 目标地址

11 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;

12 // 方向:外设到存储器(这里的外设是内部的 FLASH)

13 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;

14 // 传输大小

15 DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;

16 // 外设(内部的 FLASH)地址递增

17 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;

18 // 内存地址递增

19 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

20 // 外设数据单位

21 DMA_InitStructure.DMA_PeripheralDataSize =

22 DMA_PeripheralDataSize_Word;

23 // 内存数据单位

24 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;

25 // DMA 模式,一次或者循环模式

26 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;

27 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

28 // 优先级:高

29 DMA_InitStructure.DMA_Priority = DMA_Priority_High;

30 // 使能内存到内存的传输

31 DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;

32 // 配置 DMA 通道

33 DMA_Init(DMA_CHANNEL, &DMA_InitStructure);

34 // 使能 DMA

35 DMA_Cmd(DMA_CHANNEL,ENABLE);

36 }



使用 DMA_InitTypeDef 结构体定义一个 DMA 初始化变量,这个结构体内容我们之前已经有详细讲解。 


调用 RCC_AHBPeriphClockCmd 函数开启 DMA时钟,使用 DMA控制器之前必须开启对应的时钟。 


源地址和目标地址使用之前定义的数组首地址,传输的数据量为宏 BUFFER_SIZE 决定,源和目标地址指针地址递增,使用一次传输模式不能循环传输,因为只有一个 DMA通道,优先级随便设置,最后调用 DMA_Init 函数完成 DMA 的初始化配置。 


DMA_ClearFlag函数用于清除DMA标志位,代码用到传输完成标志位,使用之前先清除传输完成标志位以免产生不必要干扰。DMA_ClearFlag 函数需要 1 个形参,即事件标志位,可选有传输完成标志位、半传输标志位、FIFO 错误标志位、传输错误标志位等等,非常多,我们这里选择传输完成标志位,由宏 DMA_FLAG_TC 定义。 


DMA_Cmd 函数用于启动或者停止 DMA 数据传输,它接收两个参数,第一个是 DMA通道,另外一个是开启 ENABLE 或者停止 DISABLE。


4、存储器数据对比


1 uint8_t Buffercmp(const uint32_t* pBuffer,

2 uint32_t* pBuffer1, uint16_t BufferLength)

3 {

4 /* 数据长度递减 */

5 while (BufferLength--) {

6 /* 判断两个数据源是否对应相等 */

7 if (*pBuffer != *pBuffer1) {

8 /* 对应数据源不相等马上退出函数,并返回 0 */

9 return 0;

10 }

11 /* 递增两个数据源的地址指针 */

12 pBuffer++;

13 pBuffer1++;

14 }

15 /* 完成判断并且对应数据相对 */

16 return 1;

17 }


判断指定长度的两个数据源是否完全相等,如果完全相等返回 1;只要其中一对数据不相等返回 0。它需要三个形参,前两个是两个数据源的地址,第三个是要比较数据长度。 

5、main函数


1 int main(void)

2 {

3 /* 定义存放比较结果变量 */

4 uint8_t TransferStatus;

6 /* LED 端口初始化 */

7 LED_GPIO_Config();

9 /* 设置 RGB 彩色灯为紫色 */

10 LED_PURPLE;

11 

12 /* 简单延时函数 */

13 Delay(0xFFFFFF);

14 

15 /* DMA 传输配置 */

16 DMA_Config();

17 

18 /* 等待 DMA 传输完成 */

19 while (DMA_GetFlagStatus(DMA_FLAG_TC)==RESET)

20 {

21 

22 }

23 

24 /* 比较源数据与传输后数据 */

25 TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);

26 

27 /* 判断源数据与传输后数据比较结果*/

28 if (TransferStatus==0)

29 {

30 /* 源数据与传输后数据不相等时 RGB 彩色灯显示红色 */

31 LED_RED;

32 }

33 else

34 {

35 /* 源数据与传输后数据相等时 RGB 彩色灯显示蓝色 */

36 LED_BLUE;

37 }

38 

39 while (1)

40 {

41 }

42 }


首先定义一个变量用来保存存储器数据比较结果。 


RGB 彩色灯用来指示程序进程,使用之前需要初始化它,LED_GPIO_Config 定义在bsp_led.c 文件中。开始设置 RGB 彩色灯为紫色,LED_PURPLE 是定义在 bsp_led.h 文件的一个宏定义。 


Delay函数只是一个简单的延时函数。 


调用 DMA_Config 函数完成 DMA 数据流配置并启动 DMA 数据传输。 


DMA_GetFlagStatus 函数获取 DMA 事件标志位的当前状态,这里获取 DMA 数据传输完成这个标志位,使用循环持续等待直到该标志位被置位,即 DMA 传输完成这个事件发生,然后退出循环,运行之后程序。 


确定 DMA 传输完成之后就可以调用 Buffercmp 函数比较源数据与 DMA 传输后目标地址的数据是否一一对应。TransferStatus 保存比较结果,如果为 1 表示两个数据源一一对应相等说明 DMA 传输成功;相反,如果为 0 表示两个数据源数据存在不等情况,说明 DMA传输出错。 


如果 DMA传输成功设置 RGB彩色灯为蓝色,如果 DMA传输出错设置 RGB彩色灯为红色。

推荐阅读

史海拾趣

AUK Contractors Co Ltd公司的发展小趣事

近年来,电子行业面临着原材料成本上升、环保要求提高等多重挑战。AUK Contractors Co Ltd积极应对这些挑战,通过优化生产流程、采用环保材料等方式降低成本、提高效益。同时,公司还加大了对新能源、智能制造等领域的研发投入,实现了从传统电子制造向高科技领域的转型升级。

Caddock公司的发展小趣事

在追求经济效益的同时,Caddock公司也注重环保和可持续发展。公司积极推行绿色制造理念,通过优化生产工艺、降低能耗和减少废弃物排放等措施,实现了环保与经济效益的双赢。此外,公司还致力于研发环保型电阻材料和技术,为推动电子行业的绿色发展做出了积极贡献。

这些故事展示了Caddock公司在电子行业发展的不同阶段所取得的成就和面临的挑战。通过不断创新、拓展市场和注重可持续发展,Caddock公司逐渐成为了电子行业中的佼佼者。然而,这些故事仅为虚构创作,实际发展情况可能有所不同。如需了解更多关于Caddock公司的真实发展历程和故事,建议查阅相关官方资料或行业报告。

Giga公司的发展小趣事
用于设置提醒或自动执行某项任务。
Analog Modules Inc公司的发展小趣事
如开机自检、按键去抖等。
Display Engineering Services公司的发展小趣事
通过定时控制LED灯的亮灭,实现视觉效果。
潮州三环(Three-circle)公司的发展小趣事

潮州三环(集团)股份有限公司,最初成立于1970年,主要从事陶瓷基体及固定电阻器的制造和销售。然而,随着电子行业的快速发展,公司意识到单一产品已无法满足市场需求。因此,在1996年,三环集团开始投资生产片式电阻用的氧化铝陶瓷基片,这一决策标志着公司正式进入片式化元件制造领域。通过引进国外先进设备和技术,三环集团成功实现了电阻及瓷体的自动化生产,为公司后续的发展奠定了坚实基础。

问答坊 | AI 解惑

大侠们,为民除害

近日市场出现一种可控电子称,比如买一斤肉,商家通过称上的按键就可让8两显为1斤,按市价15元/斤计算就可烧你3元。若检查人员来查称,他又可恢复正常状态:1斤就是1斤。 现请各位大虾出谋划策,如何查处。 本人有两种思路:1、解密单片机程序,2 ...…

查看全部问答>

新鲜出炉:2008广西电子设计大赛题目

本帖最后由 paulhyde 于 2014-9-15 09:45 编辑 本科组: 本科A 程控音频功率放大器 本科B 点阵电子显示屏制作 本科C 简易发射机 本科D 超声波倒车测距仪 本科E 水温监控及液位报警系统 专科组: 专科A 无线安全监控系统的设计与实现 专科B ...…

查看全部问答>

超薄适配器的应用及实例

前言 现在市面上的笔记本电脑外观设计上,越来越趋于外观新颖,外形纤薄,重量也是越来越轻便。 所以,作为电脑的配件之一的电源也就相应要在外观上做一定的改变,以此来在外形上取得协调及一致性。 但是要想在厚度有一定限制的电源机壳空间里面 ...…

查看全部问答>

5509 大数据空间

我用的是瑞泰5509a的开发板,怎样能申请到70k的指针用于存放数据。 谢谢…

查看全部问答>

硬件加密,保护软件,防拷贝方案

软件被拷贝,产品被抄袭,这是众多方案公司以及企业所面临的难题,自己公司花费众多人力,物 力投资项目,最后做他人嫁衣。样品一发,订单没了;产品刚一上市,市场没了。 我司针对当前市场的无规则和混乱,推出专业硬件加密方案,保护设计者的利 ...…

查看全部问答>

怎样在WDM驱动中动态调用dll库

想在WDM驱动中动态调用一个库,不知应该怎样编写,请各位高手指教! 万分感谢!…

查看全部问答>

香水城深圳ST的代理哪家好点

                                  想选个ST的芯片,深圳哪家好点?谢谢…

查看全部问答>

请教,这个源程序是使用什么编译器器呢?

今天一个同事拿了一个据说是430的源程序给我,但是我不知道zhge程序是采用什么编译器编辑的 请教一下网上的大虾,这个源程序是采用什么编译器编译的 贴上程序的一段 #include <msp430x11x.h> #include<iostream.h> int main( void ...…

查看全部问答>

单片机的电子书

一些单片机的电子书,蛮不错的,…

查看全部问答>

【招聘】软件工程师,电气类相关专业

招聘单位:        浙江兆益电气有限公司,北京招聘,目前不能解决社保问题。招聘需求:        电气类相关专业,熟练使用示波器等相关工具,精通C语言编程,对数字电路,模拟电路,计算机接口有较好的理解。&n ...…

查看全部问答>