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请求
- 外设想通过DMA来传输数据:
- 必须先给DMA控制器发送DMA请求
- DMA收到请求,控制器给外设一个应答信号
- 外设应答后
- DMA控制器收到应答引号
- 启动DMA传输
- 不同DMA控制器的通道,对应着不同的外设请求
- DMA有DMA1和DMA2两个控制器
- DMA1有7个通道
- DMA2有5个通道
- DMA请求映像表
- 其中ADC3、SDIO 和TIM8 的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 数据从哪里来,到哪里去
- DMA 传输数据方向:
- 从外设到存储器
- 从存储器到外设
- 从存储器到存储器
- 数据传输方向配置
- 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个数据
- 数据宽度:
- 源地址和目标地址存储的数据宽度必须一致
- 串口数据寄存器是8位
- 定义要发送的数据也必须是8位
- 外设数据宽度:
- 由DMA_CCR的PSIZE[1:0]配置
- 可以是8/16/32位
- 存储器的数据宽度
- 由DMA_CRR的MSIZE[1:0]配置
- 可以是8/16/32位
- 增量模式
- 正确配置两边数据指针的增量模式,数据才能有条不紊的从一个地方搬到另一个地方
- 外设地址指针
- 由DMA_CRRx的PINC配置
- 存储器地址指针
- 由DMA_CRRx的MINC配置
3.3 什么时候传输完成
判断数据是否传出完成
- 查询标志位或者通过中断的方式来鉴别
- 标志位:每个DMA通道都有相应的标志位
- 传输过半
- 传输完成
- 传输错误
- 传输完成两种模式
- 一次传输
- 传输一次之后就停止
- 再次传输,必须关断DMA使能后再重新配置后才能继续传输
- DMA_ISR控制
- 循环传输
- 一次传输后恢复第一次传输时的配置循环传输,不断重复
- DMA_CCR寄存器的CIRC循环模式位控制
4 DMA初始化结构体详解
- 标准库函数对每个外设:
- 建立了一个初始化结构体****xxx_InitTypeDef(xxx 为外设名称)
- 结构体成员用于设置外设工作参数
- 并由标准库函数xxx_Init() 调用这些设定参数进入设置外设相应的寄存器,达到配置外设工作环境的目的
- 结构体xxx_InitTypeDef 和库函数xxx_Init 配合使用是标准库精髓所在
- 理解了结构体xxx_InitTypeDef 每个成员意义基本上就可以对该外设运用自如
- 结构体xxx_InitTypeDef 定义在stm32f10x_xxx.h(后面xxx 为外设名称) 文件中
- 库函数xxx_Init 定义在stm32f10x_xxx.c 文件中
- 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;
- DMA_PeripheralBaseAddr:外设地址,设定DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。
- DMA_Memory0BaseAddr:存储器地址,设定DMA_CMAR 寄存器值;一般设置为我们自定义存储区的首地址。
- DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定DMA_CCR 寄存器的DIR[1:0] 位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。
- DMA_BufferSize:设定待传输数据数目,初始化设定DMA_CNDTR 寄存器的值。
- DMA_PeripheralInc:如果配置为DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定DMA_CCR 寄存器的PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。
- DMA_MemoryInc:如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定DMA_CCR 寄存器的MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。
- DMA_PeripheralDataSize:外设数据宽度,可选字节(8 位)、半字(16 位) 和字(32 位),它设定DMA_CCR 寄存器的PSIZE[1:0] 位的值。
- DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字(16 位) 和字(32 位),它设定DMA_CCR 寄存器的MSIZE[1:0] 位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。
- DMA_Mode:DMA 传输模式选择,可选一次传输或者循环传输,它设定DMA_CCR 寄存器的CIRC 位的值。例程我们的ADC 采集是持续循环进行的,所以使用循环传输模式。
- DMA_Priority:软件设置通道的优先级,有4 个可选优先级分别为非常高、高、中和低,它设定DMA_CCR 寄存器的PL[1:0] 位的值。DMA 通道优先级只有在多个DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。
- DMA_M2M:存储器到存储器模式,使用存储器到存储器时用到,设定DMA_CCR 的位14MEN2MEN 即可启动存储器到存储器模式。
5 DMA存储器到存储器模式实验
- 本章只讲解存储器到存储器和存储器到外设这两种模式,其他功能模式在其他章节使用到的时候再讲。
- 存储器到存储器模式可以实现数据在两个内存的快速拷贝。
- 我们先定义一个静态的源数据,存放在内部FLASH,然后使用DMA 传输把源数据拷贝到目标地址上(内部SRAM),最后对比源数据和目标地址的数据,看看是否传输准确。
5.1 硬件设计
DMA 存储器到存储器实验不需要其他硬件要求,只用到RGB 彩色灯用于指示程序状态。
5.2 软件设计
5.2.1 编程要点
- 使能DMA 时钟;
- 配置DMA 数据参数;
- 使能DMA,进行传输;
- 等待传输完成,并对源数据和目标地址数据进行比较。
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 }
- 使用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。
5.2.4 存储器数据对比
- 判断指定长度的两个数据源是否完全相等
- 如果完全相等返回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 }
- 首先定义一个变量用来保存存储器数据比较结果。
- 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 彩色灯为红色。
5.2.6 下载验证
确保开发板供电正常,编译程序并下载。观察RGB 彩色灯变化情况。正常情况下RGB 彩色灯先为紫色,然后变成蓝色。如果DMA 传输出错才会为红色。