一、什么是DMA
DMA(Direct Memory Access)直接存储器存取。
CPU作为计算机的大脑,像数据传输这种简单,重复性高的工作交给CPU来做就显得有些浪费,所以DMA就来充当这一角色,DMA可以在硬件外设和存储器之间,或者是存储器与存储器之间进行高速数据转运,不需要CPU的介入,节省了cpu资源。
在STM32中,有DMA1和DMA2两个,其中DMA1有7个通道,DMA2有5个通道,但是根据芯片型号的不同,DMA资源也有区别,具体还要参考手册。对于每个DMA通道,如果是存储器和存储器之间转运数据,选用哪个通道倒是无所谓,但是如果是硬件外设和存储器之间进行转运,就要注意不同的硬件外设是有确定的DMA通道的,不可以乱用。
不同外设对应不同的DMA通道:
二、DMA结构
内核对外设的操作是通过寄存器来间接控制的,所以DMA转运数据就是从一个地址里面读取数据,在送到另外一个地址里。框图里的总线矩阵,其左侧拥有访问权,右侧的只能被访问。
Dcode总线是访问Flash的,DMA要转运数据,就必须要有访问权,而且每个DMA只有一条总线,也就是说,虽然DMA1有7个通道,但是只有一条能与外界交流,而究竟是哪一条进行沟通,就取决于DMA内部的仲裁器,类似于中断的优先级,仲裁器决定哪一个通道先交流。同时,总线矩阵处也有仲裁器,在CPU和DMA同时要访问一个目标时,由仲裁器决定谁优先,如果是DMA优先,仲裁器仍然会给CPU一半的线宽,确保CPU能正常工作,只是在访问此目标时让DMA优先访问。
DMA本身也是一个外设,CPU对其的控制也是要通过寄存器来控制的,AHB就是DMA的寄存器。AHB连接到总线矩阵右侧,说明DMA即可以主动读取,也可以被动被读取。
“DMA请求”来自总线上挂载的各种外设,说明此请求来自外设,属于硬件请求。
三、存储器映像
3.1、ROM
1.程序存储器Flash
起始地址:0x0800 0000。用于存储C语言编译后的代码。(const常量也被存储在Flash)
2.系统存储器
起始地址:0x1FFF F000。存储BootLoader,用于串口下载。
3.选项字节
起始地址:0x1FFF F800。存储一些独立于程序的配置参数。
3.2、RAM
1.运行内存SRAM
起始地址:0x2000 0000。存储运行过程中的临时变量。
2.外设寄存器
起始地址:0x4000 0000。存储各个外设的配置参数。
3.内核外设寄存器
起始地址:0xE000 0000。存储内核各个外设的配置参数。
四、DMA基本结构
“外设”和“存储器”相当于两个站点,内部都有三个部分,起始地址就是要读或者要写的起始地址,数据宽度就是要写几位,地址自增就是写完一位后,是否自动将地址加1。方向就是转运的方向。传输计数器是转运几次,每转运一位数据,计数器减一,减到0此次转运结束。自动重装就是计数器减到0后自动恢复到设置值,重新开始转运。
M2M意思是存储器到存储器的转运,选中该模式时就是软件触发,否则就是硬件触发。软件触发是调用后,以最快速度将传输计数器清零,争取最快转运完成。所以软件触发和自动重装不可以一起用,软件触发将计数器清零,自动重装又重装,导致循环。
最后就是开关控制,使用DMA就要打开DMA的开关,就是CMD函数,其次,如果是硬件外设与存储器的转运,还要打开对应的外设DMA开关。
五、K5代码
5.1、DMA相关函数
//DMA_DeInit 恢复缺省配置
//DMA_Init DMA初始化
//DMA_StructInit DMA结构体初始化
//DMA_Cmd DMA使能
//DMA_ITConfig DMA中断许可
//DMA_SetCurrDataCounter 设置当前数据寄存器,就是给传输计数器写数据的
//DMA_GetCurrDataCounter 获取当前数据寄存器,就是获取传输计数器的数据
//DMA_GetFlagStatus 获取标志位状态
//DMA_ClearFlag 清除标志位
//DMA_GetITStatus 获取中断状态
//DMA_ClearITPendingBit 清除中断挂起位
5.2、初始化函数
/**
* 函 数:DMA初始化
* 参 数:AddrA 原数组的首地址
* 参 数:AddrB 目的数组的首地址
* 参 数:Size 转运的数据大小(转运次数)
* 返 回 值:无
*/
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
MyDMA_Size = Size; //将Size写入到全局变量,记住参数Size
/*开启时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA的时钟
/*DMA初始化*/
DMA_InitTypeDef DMA_InitStructure; //定义结构体变量
DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; //外设基地址,给定形参AddrA
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据宽度,选择字节
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //外设地址自增,选择使能
DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; //存储器基地址,给定形参AddrB
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据宽度,选择字节
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址自增,选择使能
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,选择由外设到存储器
DMA_InitStructure.DMA_BufferSize = Size; //转运的数据大小(转运次数)
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //模式,选择正常模式
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //存储器到存储器,选择使能
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级,选择中等
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //将结构体变量交给DMA_Init,配置DMA1的通道1
/*DMA使能*/
DMA_Cmd(DMA1_Channel1, DISABLE); //这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始
}