STM32系统学习——DMA(直接储存器访问)

发布者:Qingliu2022最新更新时间:2019-02-14 来源: eefocus关键字:STM32  DMA  直接储存器访问 手机看文章 扫描二维码
随时随地手机看文章

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彩色灯为红色。

关键字:STM32  DMA  直接储存器访问 引用地址:STM32系统学习——DMA(直接储存器访问)

上一篇:STM32系统学习——I2C (读写EEPROM)
下一篇:STM32 DMA的特性

推荐阅读最新更新时间:2024-03-16 16:24

基于STM32和CAN总线的印染机同步控制系统设计
0 引言 随着社会生活的发展,人们对现在的印染品的要求也越来越高,特别是布匹与包装外壳,那么对现代印染工艺的要求也越来越高。随着工艺的增加,对印染设备是个不小的挑战,这里面最主要的是大型印染联合机中多电机的同步控制问题。 在印染设备中,电机的同步控制主要有3方面决定:一是处理器对张力传感器数据的处理速度,以及电机对张力传感器的反应速度;二是不同的电机组之间机械性能的差异以及它们产生的实时同时控制问题;三是控制单元与各电机组之间的通信问题,包括速率,抗干扰等。传统印染联合机的做法是采用单片机加AD/DA芯片进行数据的处理与执行,也有为了提高数据的处理能力而采用DSP加单片机的做法。随着现在技术的发展,在研究了基于ARM的CORTE
[单片机]
基于<font color='red'>STM32</font>和CAN总线的印染机同步控制系统设计
STM32 485通信 自我学习总结
准备总结一下学习过程中的485通信知识!---------------------------------------------------- 先描述一下学习STM32与485通信的时候想实现的功能-------- 首先是完成双机通信中的双向通信----这里定义A为主机发送指令给从机B,从机B在接收到主机A的指令后,判断有效位的正确性,如果正确将这个指令通过RS232串口显示到串口助手里,观察整个指令是否接收正确-----同时在从机B接收到主机A指令后,向主机A发送指定数据----主机A在接收到从机B数据后判断有效位的正确性,如果正确则通过RS232打印到串口助手,然后观察完整数据。--------这里的判断比较简单----
[单片机]
<font color='red'>STM32</font> 485通信 自我学习总结
基于stm32通用定时器设置的学习心得
stm32 单片机的定时器资源相当丰富,它的定时器分为高级控制定时器、通用定时器和基本定时器,具体这些定时器资源在哪个系列的片子有就得看不同的片子的手册了。他们具体有什么区别,我也是刚接触这个,看他的数据手册介绍也是茫然,主要是刚开始摸,那些功能都没用到,反正用做定时作用的话哪种定时器都行。在这我就把我自己配置通用定时器的方法及心得简短做个总结,以防以后忘记了。我配置的是定时器2(TIM2)。 通用 定时器 的时钟可来自于外部或内部,选用默认即是采用内部的。通用定时器的时钟来源为APB1总线,所以首先,得将APB1外设时钟打开。 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENAB
[单片机]
基于<font color='red'>stm32</font>通用定时器设置的学习心得
esp32能取代stm32吗?哪个好?
在学生群体或许能替代,因为超高性价比。 站在产品的角度替代不了,产品选型考虑的因素很多。 ESP32和STM32都是广泛使用的微控制器,它们都有自己的优缺点。 如果简单地说一个完全可以取代另一个,其实并不现实。 下面列举几个ESP32无法完全取代STM32的理由: 1. 应用场景 STM32的应用场景更加广阔,能做的产品更多。 ESP32通常用于物联网设备,家庭自动化,Wi-Fi控制,而STM32更适合用于消费类、工业控制、机器人、医疗设备、汽车等应用程序。 通常情况,ESP32更多是作为一个蓝牙、WiFi的中继功能。 如果考虑到产品后续的功能升级,比较好的方式是STM32或者其它MCU+ESP32,这样后面扩展更加灵活。 如
[单片机]
STM32 usb_pwr.c文件分析
usb_pwr.c 这个文件看文件名就知道跟功耗有关了,有很多的状态:上电、掉电、挂起、恢复。 当首先是usb的上电和断电函数的定义了。 usb上电函数如下: /******************************************************************************* * Function Name : PowerOn * Description : 上电 * Input : None. * Output : None. * Return : USB_SUCCESS. ***************************************
[单片机]
stm32引脚的VCC与VDD如何连接
stm32单片机作为一种常见的嵌入式设备,是许多电子设备和系统中必不可少的一部分。而在单片机的设计和应用中,电源电压是一个非常重要的参数,其中VCC和VDD作为单片机中的电源引脚,是最为关键的两个引脚之一。本文将介绍VCC和VDD的区别及其在单片机中的应用。 一、VCC与VDD的区别 在单片机中,VCC是最常见的电源引脚,通常用于提供数字电路中需要的正向电压。VCC的电压通常是3.3V或5V,但也有其他电压等级的单片机。 而VDD是一种特殊类型的电源电压,它是指在CMOS电路中使用的电源电压。在CMOS电路中,晶体管使用的是MOSFET,其工作需要两种类型的电压:正向电压和反向电压。VDD是指MOSFET工作时需要的正向电压,
[单片机]
<font color='red'>stm32</font>引脚的VCC与VDD如何连接
基于STM32的DAC8760 菊花链驱动
环境:STM32F103RC,主频 72MHz(外部晶振)或64MHz(HSI) 两块DAC8760菊花链链接,采用SPI1驱动 note:发生电流回路开路等时,ALARM脚持续低,当电流回路等正常,DAC芯片能自动恢复正常,故程序无需特殊处理。 代码实现: //DAC8760.c #define DAC8760_GLOBALS #include DAC8760.h static const uint8_t ORange = { 0, // 0~5V(OFF) 0, // 1~5V 1, // 0~10V 3, // -10~10V 6, // 0~20mA 5, //
[单片机]
SD NAND在STM32应用上的保姆级教程
SD NAND与正点原子精英板的连接 由于正点原子精英板没有SD NAND接口,只有TF卡接口,所以SD NAND需要用到转接板来连接。 SD NAND正常运行现象 本次实验的程序是正点原子的SD卡实验例程,先用读卡器把SD NAND接到电脑上,并复制一个文件进去,再插到开发板上; 用送的数据线连接USB UART接口,下载好程序,打开电脑上的串口助手,按下KEY0,即可读取到数据, 具体实验步骤和现象可以看例程文件夹中的readme, 另外LED-DS0闪烁也表示SD NAND芯片在正常运行, SD NAND芯片用的是MK-米客方德的工业级芯片MKDV1GIL-AS;MK-米客方德家还有其他各种型号的SD NAN
[单片机]
SD NAND在<font color='red'>STM32</font>应用上的保姆级教程
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved