STM32学习笔记——十七、DMA——直接存储区访问

0 前言

  • 本章参考资料:《STM32F10X-中文参考手册》DMA 控制器章节。

1 DMA 简介

DMA直接存储器存区(Direct Memory Access):

  • 单片机的一个外设
  • 主要功能:
    • 用来数据搬运,且不需要占用CPU
    • 传输数据时,CPU可以干其他事情
  • 数据传输
    • 从外设到存储器
    • 从存储器到存储器
    • 存储器可以是SRAM或者FLASH
  • DMA控制器
    • DMA1有7个通道
    • DMA2有5个通道(主要用于大容量产品和互联型产品中)

2 DMA功能框图

DMA控制器:独立于内核,属于一个单独的外设,结构比较简单
在这里插入图片描述

2.1 DMA请求

  1. 外设想通过DMA来传输数据
  • 必须先给DMA控制器发送DMA请求
  • DMA收到请求,控制器给外设一个应答信号
  • 外设应答后
  • DMA控制器收到应答引号
  • 启动DMA传输
  1. 不同DMA控制器的通道,对应着不同的外设请求
  • DMA有DMA1和DMA2两个控制器
  • DMA1有7个通道
  • DMA2有5个通道
  • DMA请求映像表
    • 其中ADC3、SDIO 和TIM8 的DMA 请求只在大容量产品中存在
      在这里插入图片描述在这里插入图片描述

2.2 通道

DMA 具有12 个独立可编程的通道

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

2.3 仲裁器

仲裁器:当发生多个DMA通道请求时,有先后相应处理的顺序问题,仲裁器进行管理
仲裁器管理DMA通道请求

  • 分为两个阶段
  • 第一阶段:软件阶段
    • DMA_CCRx寄存器中设置
    • 有4个等级:非常高、高、中、低
  • 第二阶段:硬件阶段
    • 通道请求设置的优先级一样时启用
    • 则优先级取决于通道编号,编号越低优先级越高
  • 大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级

3 DMA 数据配置

DMA最核心:配置要传输的数据

  • 数据从哪里来
  • 到哪里去
  • 数据的单位
  • 传输多少数据
  • 一次传输/循环传输

3.1 数据从哪里来,到哪里去

  1. DMA 传输数据方向
  • 从外设到存储器
  • 从存储器到外设
  • 从存储器到存储器
  1. 数据传输方向配置
  • DMA_CRR 位 4 DIR
    • 0 表示外设到存储器
    • 1 表示存储器到外设
  • 外设地址:DMA_CPAR
  • 存储器地址:DMA_CMAR

3.1.1 外设到存储器

以ADC采集为例

  • DMA外设寄存器地址:ADC数据寄存器地址
  • DMA存储器地址:自定义的变量地址
  • 方向:设置外设为源地址

3.1.2 存储器到外设

以串口向电脑端发送数据为例

  • DMA 外设寄存器地址:串口数据寄存器地址
  • DMA 存储器:自定义的变量地址
  • 方向:设置外设为目标地址

3.1.3 存储器到存储器

以内部FLASH向内部SRAM赋值数据为例

  • DMA外设寄存器地址:内部FLASH地址
  • DMA 存储器地址:自定义变量地址
  • 方向:设置外设为源地址
  • 区别:需要把DMA_CCR 位 14 : MEM2MEM: 存储器到存储器模式设置为1,启动M2M模式

3.2 数据要传输多少,单位是什么

以串口向电脑发送数据为例

  • DMA_CNDTR配置
    • 32位寄存器,一次最多只能传输65535个数据
  1. 数据宽度
  • 源地址和目标地址存储的数据宽度必须一致
    • 串口数据寄存器是8位
    • 定义要发送的数据也必须是8位
  • 外设数据宽度
    • 由DMA_CCR的PSIZE[1:0]配置
    • 可以是8/16/32位
  • 存储器的数据宽度
    • 由DMA_CRR的MSIZE[1:0]配置
    • 可以是8/16/32位
  1. 增量模式
  • 正确配置两边数据指针的增量模式,数据才能有条不紊的从一个地方搬到另一个地方
  • 外设地址指针
    • 由DMA_CRRx的PINC配置
  • 存储器地址指针
    • 由DMA_CRRx的MINC配置

3.3 什么时候传输完成

判断数据是否传出完成

  1. 查询标志位或者通过中断的方式来鉴别
  • 标志位:每个DMA通道都有相应的标志位
    • 传输过半
    • 传输完成
    • 传输错误
  1. 传输完成两种模式
  • 一次传输
    • 传输一次之后就停止
    • 再次传输,必须关断DMA使能后再重新配置后才能继续传输
    • DMA_ISR控制
  • 循环传输
    • 一次传输后恢复第一次传输时的配置循环传输,不断重复
    • DMA_CCR寄存器的CIRC循环模式位控制

4 DMA初始化结构体详解

  1. 标准库函数对每个外设:
  • 建立了一个初始化结构体****xxx_InitTypeDef(xxx 为外设名称)
  • 结构体成员用于设置外设工作参数
  • 并由标准库函数xxx_Init() 调用这些设定参数进入设置外设相应的寄存器,达到配置外设工作环境的目的
  1. 结构体xxx_InitTypeDef库函数xxx_Init 配合使用是标准库精髓所在
  • 理解了结构体xxx_InitTypeDef 每个成员意义基本上就可以对该外设运用自如
  • 结构体xxx_InitTypeDef 定义在stm32f10x_xxx.h(后面xxx 为外设名称) 文件中
  • 库函数xxx_Init 定义在stm32f10x_xxx.c 文件中
  1. DMA_ InitTypeDef 初始化结构体
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;
  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 即可启动存储器到存储器模式。

5 DMA存储器到存储器模式实验

  • 本章只讲解存储器到存储器存储器到外设这两种模式,其他功能模式在其他章节使用到的时候再讲。
  • 存储器到存储器模式可以实现数据在两个内存的快速拷贝。
  • 我们先定义一个静态的源数据,存放在内部FLASH,然后使用DMA 传输把源数据拷贝到目标地址上(内部SRAM),最后对比源数据和目标地址的数据,看看是否传输准确。

5.1 硬件设计

DMA 存储器到存储器实验不需要其他硬件要求,只用到RGB 彩色灯用于指示程序状态。

5.2 软件设计

5.2.1 编程要点

  1. 使能DMA 时钟;
  2. 配置DMA 数据参数;
  3. 使能DMA,进行传输;
  4. 等待传输完成,并对源数据和目标地址数据进行比较。

5.2.2 DMA宏定义及相关变量定义

1 // 当使用存储器到存储器模式时候,通道可以随便选,没有硬性的规定
2 #define DMA_CHANNEL DMA1_Channel6
3 #define DMA_CLOCK RCC_AHBPeriph_DMA1
4
5 // 传输完成标志
6 #define DMA_FLAG_TC DMA1_FLAG_TC6
7
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 空间上。

5.2.3 DMA 数据配置

1 void DMA_Config(void)
2 {
3 		DMA_InitTypeDef DMA_InitStructure;
4
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 }
  1. 使用DMA_InitTypeDef 结构体定义一个DMA 初始化变量,这个结构体内容我们之前已经有详细讲解。
  2. 调用RCC_AHBPeriphClockCmd 函数 开启DMA 时钟,使用DMA 控制器之前必须开启对应的时钟。
  3. 源地址目标地址使用之前定义的数组首地址,传输的数据量为宏BUFFER_SIZE 决定,源和目标地址指针地址递增,使用一次传输模式不能循环传输,因为只有一个DMA 通道,优先级随便设置,最后调用DMA_Init 函数完成DMA 的初始化配置
  4. DMA_ClearFlag 函数用于清除DMA 标志位,代码用到传输完成标志位,使用之前先清除传输完成标志位以免产生不必要干扰。DMA_ClearFlag 函数需要1 个形参,即事件标志位,可选有传输完成标志位、半传输标志位、FIFO 错误标志位、传输错误标志位等等,非常多,我们这里选择传输完成标志位,由宏DMA_FLAG_TC 定义。
  5. DMA_Cmd 函数用于启动或者停止DMA 数据传输,它接收两个参数,第一个是DMA 通道,另外一个是开启ENABLE 或者停止DISABLE。

5.2.4 存储器数据对比

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

5.2.5 主函数

1 int main(void)
2 {
3 		/* 定义存放比较结果变量*/
4 		uint8_t TransferStatus;
5
6 		/* LED 端口初始化*/
7 		LED_GPIO_Config();
8
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 }
  1. 首先定义一个变量用来保存存储器数据比较结果。
  2. RGB 彩色灯用来指示程序进程,使用之前需要初始化它,LED_GPIO_Config 定义在bsp_led.c 文件中。开始设置RGB 彩色灯为紫色,LED_PURPLE 是定义在bsp_led.h 文件的一个宏定义。
  3. Delay 函数只是一个简单的延时函数。
  4. 调用DMA_Config 函数完成DMA 数据流配置并启动DMA 数据传输。
  5. DMA_GetFlagStatus 函数获取DMA 事件标志位的当前状态,这里获取DMA 数据传输完成这个标志位,使用循环持续等待直到该标志位被置位,即DMA 传输完成这个事件发生,然后退出循环,运行之后程序。
  6. 确定DMA 传输完成之后就可以调用Buffercmp 函数比较源数据与DMA 传输后目标地址的数据是否一一对应。
  7. TransferStatus 保存比较结果,如果为1 表示两个数据源一一对应相等说明DMA传输成功;相反,如果为0 表示两个数据源数据存在不等情况,说明DMA 传输出错。
  8. 如果DMA 传输成功设置RGB 彩色灯为蓝色,如果DMA 传输出错设置RGB 彩色灯为红色。

5.2.6 下载验证

确保开发板供电正常,编译程序并下载。观察RGB 彩色灯变化情况。正常情况下RGB 彩色灯先为紫色,然后变成蓝色。如果DMA 传输出错才会为红色。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值