文章目录
- DMA
- DMA简介
- 存储器映像
- DMA框图
- DMA基本结构
- DMA请求
- 数据宽度与对齐
- 数据转运+DMA
- 变量与常量实验
- 外设寄存器访问
- DMA 配置与编程思路
- DMA 代码实现与测试
- DMA模块主要代码
DMA
DMA简介
DMA 简介
- 功能与权限:英文全称 direct memory access,可直接访问 STM32 内部存储器,包括运行内存、程序存储器、Flash 和寄存器等,能提供外设和存储器或存储器和存储器之间高速数据传输,无需 CPU 干预,节省 CPU 资源。
- 通道数量:STM32 的 DMA 有 12 个独立可配置通道,DMA1 有 7 个通道,DMA2 有 5 个通道,通道是数据转运路径,多个通道转运互不干扰 。
- 触发方式:每个通道支持软件触发和特定硬件触发;存储器到存储器转运一般用软件触发,可快速完成数据转运;外设到存储器转运一般用硬件触发,且每个通道硬件触发源不同,要使用对应外设触发源需用其连接的通道 。
- 芯片资源:STM32F103C8T6 芯片只有 DMA1 的 7 个通道,无 DMA2 。只有DMA1。
存储器映像
STM32 的存储器映像
- 分类与地址:存储器分 ROM 和 RAM 两大类;ROM 包括程序存储器 Flash(地址 0X08000000 )、系统存储器(地址 1FFF 开头 )和选项字节(地址 1FFF 开头 );RAM 包括运行内存 SRAM(地址 0X2000000 )、外设寄存器(地址 0X40000000 )和内核外设寄存器(地址 0X E 0000000 ) 。
- 用途:Flash 存储编译后的程序代码;系统存储器存储 boot loader 用于串口下载;选项字节存储独立于程序代码的配置参数;SRAM 存储运行中的临时变量;外设寄存器存储外设配置参数;内核外设寄存器存储内核外设配置参数 。
- 寻址范围:CPU 为 32 位,寻址范围大,最大支持 4GB 容量存储器,但 STM32 存储器为 KB 级别,地址使用率不到 1%;0 地址有别名区,程序运行起始地址映射位置由 boot0 和 boot1 引脚决定 。
类型 | 起始地址 | 存储器 | 用途 |
---|---|---|---|
ROM | 0x0800 0000 | 程序存储器Flash | 存储C语言编译后的程序代码 |
0x1FFF F000 | 系统存储器 | 存储BootLoader,用于串口下载 | 存储BootLoader,用于串口下载 |
0x1FFF F800 | 选项字节 | 存储一些独立于程序代码的配置参数 | 存储一些独立于程序代码的配置参数 |
RAM | 0x2000 0000 | 运行内存SRAM | 存储运行过程中的临时变量 |
0x4000 0000 | 外设寄存器 | 存储各个外设的配置参数 | 存储各个外设的配置参数 |
0xE000 0000 | 内核外设寄存器 | 存储内核各个外设的配置参数 | 存储内核各个外设的配置参数 |
DMA框图
DMA 框图结构
- 总线矩阵:左端主动单元有内核(含 ICcode 和系统总线)和 DMA 总线(DMA1、DMA2 各有一条 ),有存储器访问权;右边被动单元是各种存储器,只能被主动单元读写;仲裁器用于调度通道,防止冲突,当与CPU发生冲突会停止会停止访问,但是会保证 CPU 得到一半总线带宽 。
- AHB 从设备:即 DMA 自身寄存器,连接在 AHB 总线上,既是总线矩阵主动单元可读写存储器,也是 AHB 总线上被动单元,CPU 可通过线路配置 DMA 。
- DMA 请求:即硬件触发源,各外设通过此线路向 DMA 发出硬件触发信号,触发 DMA 执行数据转运工作 。
- Flash 读写特性:Flash 是 ROM 只读存储器,通过总线直接访问时,CPU 和 DMA 只能读不能写,若 DMA 执行数据转化目的地址在 Flash 区域会出错;但可配置 Flash 接口控制器对 Flash 进行擦除和写入操作,只是流程复杂 。
DMA基本结构
DMA 基本结构
- 转运站点参数:数据转运有外设寄存器和存储器(含 Flash 和 SRAM )两个站点;各站点有起始地址、数据宽度(可选字节(8位)、半字(16位)、字(32位) )、地址是否自增三个参数;方向参数可控制转运方向;可在外设站点写存储器地址,存储器站点写外设地址实现特殊转运 。
- 传输计数器与自动重装器:传输计数器是自减计数器,指定转运次数,减到 0 后停止转运,自增地址恢复到起始地址;自动重装器决定计数器减到 0 后是否自动恢复到初始值,不重装是单次模式,重装是循环模式 。
- 触发控制:触发源有硬件触发和软件触发,由 M2M(memory to memory )参数决定;M2M 为 1 时选择软件触发,以最快速度连续触发 DMA 清零传输计数器(所以软件触发与循环模式不能同时用),与外部中断和 ADC 软件触发不同(这里可以理解为连续触发),适用于存储器到存储器转运,并且软件触发不能与自动重装同时使用(因为软件触发是为了使传输计数器清零,自动重装是计数器清零重新重载,如果同时使用会使DMA不可以停止);M2M 为 0 时选择硬件触发,触发源可选 ADC、串口、定时器等,用于与外设有关的转运 。
- 开关控制:即 DMA CMD 函数,使能后 DMA 准备就绪可转运;转运需满足开关控制使能、传输计数器大于 0、有触发信号三个条件;传输计数器为 0 且无自动重装时,需先关闭 DMA,设置传输计数器大于 0 后再开启 DMA 才能继续工作(这里器件手册要求的) 。
DMA请求
以 DMA1 请求图为例,通道有数据选择器,m2m 位控制选择硬件或软件触发,en 位控制数据选择器工作与否;每个通道硬件触发源不同,使用某个硬件触发源需用其所在通道,软件触发通道可任意选;多个硬件触发源开启时由或门选择,默认通道号越小优先级越高,也可在程序中配置 。
数据宽度与对齐
数据宽度不同时,小数据转到大数据高位补 0,大数据转小数据高位舍弃,宽度相同时正常转运 。
数据转运+DMA
数据转运+ DMA:任务是将 SRAM 里数组 data a 转运到 data b;外设站点起始地址填 data a 首地址,存储器站点填 data b 首地址,数据宽度选 8 位字节,地址都自增,方向是外设到存储器,传输计数器填 7,不自动重装,用软件触发,使能 DMA 后数据从 data a 复制到 data b 。
变量与常量实验
- 变量定义与验证:定义变量 unit8_t AA=0X66,在主函数中通过 OLED 显示其内容和地址,验证变量存储于 SRAM 区,地址 20 开头。
- 常量定义与特性:在变量前加 const 关键字定义常量,常量存储在 Flash 区,地址 08 开头,程序中只能读不能写,可节省 SRAM 空间。
外设寄存器访问
-
地址查询:外设寄存器地址固定,如 ADC1 的 Dr 寄存器地址可通过手册计算,起始地址加偏移得到。如果想计算某个寄存器的地址,查手册(首先查存储映像 就是寄存器所在外设的基地址,然后再查有关外设中 要查寄存器的基地址偏移量)
-
结构体访问:使用结构体访问寄存器,结构体成员顺序与寄存器实际存放顺序一致,指针指向外设起始地址时,访问结构体成员即访问对应寄存器。
-
指针访问:也可用指针直接访问物理地址,就是宏定义 物理地址,想要访问地址的时候解引用直接访问地址,效果与结构体访问相同。
DMA 配置与编程思路
-
数组定义:定义源端数组 data a 和目的数组 data b,用于 DMA 数据转运测试。
-
模块添加:在 system 里添加 my DMA 模块,防止与库函数重复。
-
初始化函数:在 my DMA 模块中创建初始化函数 void my DMA init (void),初始化步骤包括开启时钟、配置参数、使能通道等。
-
库函数介绍:介绍 DMA 相关库函数,如 deinit、init、cmdid、itconfig 等,用于配置和控制 DMA。
-
参数配置:通过结构体配置 DMA 初始化参数,包括外设和存储器站点的起始地址、数据宽度、地址是否自增、传输方向、缓冲区大小、传输模式、触发方式、通道优先级等。
-
工作条件:DMA 转运需满足传输计数器大于 0、有触发信号、DMA 使能三个条件。
DMA 代码实现与测试
-
函数声明与调用:在头文件声明相关函数,在 main.c 中调用 my DMA init 函数初始化 DMA,传入源端地址、目的地址和传输次数参数。
-
数据显示:转运前后通过 OLED 显示 data a 和 data b 的内容,验证数据转运效果。
-
再次转运:编写 void my DMA transfer (void) 函数,重新给传输计数器赋值,实现再次转运功能。
-
等待转运完成:使用 DMA 的标志位函数等待转运完成,并清除标志位。
-
Flash 数据转运:将 data a 定义为 const 常量存储在 Flash 中,验证 Flash 到 SRAM 的数据转运。
DMA模块主要代码
// MyDMA.c#include "stm32f10x.h" // Device header//设置DMA的传输计数器的次数
uint16_t MyDMA_Size;void MyDMA_Init(uint32_t ARR1, uint32_t ARR2, uint16_t Size)
{MyDMA_Size = Size;//1.开始DMA1时钟 , 挂载在AHB总线上RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//2.初始化DMADMA_InitTypeDef DMA_InitStruct;//外设基地址,这里地址为32位DMA_InitStruct.DMA_PeripheralBaseAddr = ARR1;//配置传输数据的位数 全字 32位DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//内存递增配置DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;DMA_InitStruct.DMA_MemoryBaseAddr = ARR2;DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;//传输计数器 次数DMA_InitStruct.DMA_BufferSize = MyDMA_Size;//传输方向 外设寄存器作为源头DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;//配置触发方式 使能就是软件触发 否则就是 硬件触发 DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;//模式配置 配置是否循环 就是是否数据重装DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;DMA_Init(DMA1_Channel1, &DMA_InitStruct);//如果传输完成要使用中断,就配置 ifconfig函数,然后再配置nvic就可以了//这里不使用中断//配置 DMA1 总开关//要注意 在进行配置传输寄存器时,要将总开关关掉DMA_Cmd(DMA1_Channel1, DISABLE);
}//每调用一次就数据传输一次
void MyDMA_Transfrom(void)
{DMA_Cmd(DMA1_Channel1, DISABLE);//因为初始化没有打开数据传输的总开关DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);//使能总开关DMA_Cmd(DMA1_Channel1, ENABLE);//数据传输也需要时间,所以要查看标志位、while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//之前都是自动清除,这个要手动清除DMA_ClearFlag(DMA1_FLAG_TC1);
}