DMA知识
Stm32F103c8t6的DAM资源:DMA1
DMA通道数:7个
对于大容量的STM32芯片有2个DMA控制器 两个DMA控制器,DMA1有7个通道,DMA2有5个通道。
名称:Direct Memory Access,直接存储器访问。
存储器:
ROM:只读存储器(全写为 read-only memory),掉电不丢失。
RAM:随机存储器(Random Access Memory),掉电丢失
两种存储器类型对应的具体存储器(参考自江协):
补充说明:寄存器属于存储器的一种。
一些存储器被称为寄存器,是因为它们是CPU内部的一种非常快速的存储器件,用于暂存CPU执行指令和数据的地方。寄存器具有高速读写的特点,主要用于存储临时数据和进行地址计算。
存在非CPU的寄存器,主要是为了与CPU交互信息方便。在接口内部一般会设置一些可以被CPU直接访问的寄存器,这些寄存器虽然不属于CPU,但它们是主机的一部分,用于提高数据传输和处理的效率。
运输方向:
①外设存储器<——>存储器
②存储器<——>存储器
外设:指连接的设备,相对于STM32内核上的板子上的设备而言,它们是外部设备的简称,指的是集成电路芯片外部的设备。外设就是挂载在总线上的设备,也就是在启动时需要先开启外设时钟才能使用。(?不太确定)
数据宽度的转运问题:
①高位数寄存器的数据——>低位数的寄存器:每一个数据都会舍去高位数据。
如16位数据放在8位数据寄存器,则只留下低8位的数据,高8位被越界舍弃。
②同位数数据寄存器之间相传:数据不变。
③低位数数据寄存器——>高位数数据寄存器:高位补0,数值不变。
工作特点:配置好后无需CPU干预,硬件自发完成(转运,地址自增),节省了CPU资源。
触发方式:
①软件触发:多用于存储器之间,目的是为了快速传输完数据。(适用于已知全部数据源的情况,不能和重装计数器一起用,否则卡死)
②硬件触发:外设存储器<—>存储器,目的是为了在保证外设输入完整数据,或外设接收完整数据之后,再进行下一次传输。(适用于需要一定时间等待数据完整传输的情况)
访问权:
①因为DMA可以硬件转运存储器、外设存储器之间的数据,所以DMA有对外设存储器的访问权。
②又因为DMA的启动需要软件配置,所以CPU对DMA有访问权
③所以DMA虽然也挂在上AHB上(让CPU访问),又拥有对挂在在AHB上其他寄存器的访问权
④Flash是ROM类型,无论是CPU还是DMA都是能直接读,不能直接写,要写的话需要通过Flash寄存器进行间接操作。
DMA请求:
①外设可以产生DMA请求(硬件信号),让DMA转运数据。不同触发源的通道不同,详细见表。
②软件触发可以直接触发,但是硬件触发则需要选择对应通道才能触发
③优先级是序号越小优先级越高
④ADC的扫描模式,虽然在ADC转换一次数据之后不会产生EOC转换完成标志位,但是会产生DMA请求
配置内容(接收双方都需要,只要是DMA可以直接访问进行直接读写操作的都可以,否则只读的只能作为发送源):
①数据传输的起始地址:数据源和接收方的地址,用变量传输的话那就是直接给变量地址
②数据的宽度(数据个数):要用DMA传输几个数据
③存储器地址是否自增:
数据源地址(发送方)自增,每次传输一次之后指向下一个地址,能够传输不同数据
接受方地址自增,能够在接收一次数据之后,指向下一个存储空间,防止许多数据都放在同一个空间导致新数据覆盖旧数据,避免发送多个数据却只能收到最新数据的情况
④传输计数器(循环次数,配置重装计数器):
每转一次,自减一次,减完后地址重置为起始地址,同时重装值重置,配合ADC的连续+扫描模式。
当传输计数器为0时且没有配置重装计数器时,哪怕使能了也不会再转运,需要失能后重新写入值才能接着转运
⑤M2M:Memory to Memory(存储器到存储器)
置1:软件触发——快转,多用于存储器到存储器
置0:硬件触发——需时机,多用于外设存储器到存储器
⑥接收字节对齐:左对齐(降低精度),右对齐(直接取值)
⑧DMA通道: 每个DMA通道都可以独立选择软硬件触发
主要代码(参考自江科大)
DMA的初始化
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t 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后,再开始
}
DMA的调度代码
void MyDMA_Transfer(uint16_t MyDMA_Size)//每次启动DMA需要传参说明转运多少个数据
{
DMA_Cmd(DMA1_Channel1, DISABLE); //DMA失能,在写入传输计数器之前,需要DMA暂停工作
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size); //写入传输计数器,指定将要转运的次数
DMA_Cmd(DMA1_Channel1, ENABLE); //DMA使能,开始工作
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //等待DMA工作完成
DMA_ClearFlag(DMA1_FLAG_TC1); //清除工作完成标志位
}