DMA存储器的介绍
DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。
一、DMA总体架构图&配置图
二、DMA模块的相关函数配置(MyDMA_Init)
2.1、DMA常见的初始化函数
2.1.1、DMA_DeInit()
//将DMAy Channelx寄存器初始化为其默认重置值。
DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx)
2.1.2、DMA_StructInit()
//用默认值填充每个DMA_InitStruct成员。(即结构体初始化)
DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct)
2.1.3、DMA_Init()
//根据DMA_InitStruct中指定的参数初始化DMAy通道。
DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
2.1.4、DMA_Cmd()
//启用或禁用指定的DMAy通道。(即使能)
DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState)
2.1.5、DMA_ITConfig()
//中断输出使能
DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState)
2.1.6、DMA_GetFlagStatus()
//获取标志为状态
DMA_GetFlagStatus(uint32_t DMAy_FLAG)
2.1.7、DMA_ClearFlag()
//清除标志位
DMA_ClearFlag(uint32_t DMAy_FLAG)
2.1.8、DMA_GetITStatus()
//获取中断状态
DMA_GetITStatus(uint32_t DMAy_IT)
2.1.9、DMA_ClearITPendingBit()
//清除中断挂起位
DMA_ClearITPendingBit(uint32_t DMAy_IT)
2.2、DMA寄存器函数
2.2.1、DMA_SetCurrDataCounter()
//设置当前DMAy通道传输中的数据单元数(即给传输计数器写数据)
DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber)
2.2.2、DMA_GetFlagStatus()
//获取当前数据寄存器,返回传输计数器的值
DMA_GetFlagStatus(uint32_t DMAy_FLAG)
三、配置MyDMA_Init()
根据上图配置,其中使用DMA_Init()便可以配置以上相关参数。
3.1、开启时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE)
3.2、DMA_Init参数配置
- DMA_PeripheralBaseAddr
指定DMAy通道的外设基址,即外设的起始地址,需要填写一个32位的地址,而对于SRAM的数组,他的地址是编译器分配的,并非固定地址,因此我们可以通过数组名来获取地址。我们可以吧这个地址提取成一个初始化函数的参数,以便我们可以更改外设起始地址。 - DMA_PeripheralDataSize
外设数据宽度。可以是下图中指定的值
其中(148)Byte,字节,对应着shi uint8_t,(149)HalfWord,半字节,对应着uint16_t,(150)Word,字,对应着uint32_t - DMA_PeripheralInc
外设地址是否自增。有以下两个参数选项,自增or不自增 - DMA_MemoryBaseAddr&DMA_MemoryDataSize&DMA_MemoryInc
这三个参数分别对应着寄存器地址&寄存器数据宽度&寄存器是否自增,与1~3点类似。 - DMA_BufferSize
传输计数器。(该例程中把这个参数提取到初始化函数——uint32_t,以便更好修改) - DMA_DIR
指定外设是源端还是目标端。
参数可以从下图选择。DST—目的地,SRC—源头(外设站点到存储器 站点的传输方向)。 - DMA_M2M
是否应用于存储器到存储器的转运,即软件触发模式。
参数从下图选择。ENABLE—软件触发,DISABLE—硬件触发 - DMA_Mode
传输模式,即是否使用自动重装。(注意自动重装和软件触发不可同时使用)
参数从下图选择。Circular—自动重装,Normal—正常模式(非自动重装) - DMA_Priority
指定通道的软件优先级。
3.3、DMA使能
DMA_Cmd(DMA1_Channel1,DISABLE);
以下是DMA_Init函数完整代码
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)
{
MyDMA_Size=Size;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr=AddrA; //外设的起始地址
DMA_InitStructure.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte; //外设的数据宽度
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable; //外设是否自增
DMA_InitStructure.DMA_MemoryBaseAddr= AddrB; //存储器的起始地址
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte; //存储器的数据宽度
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable; //存储器是否自增
DMA_InitStructure.DMA_BufferSize=Size; //传输计数器
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC; //传输方向
DMA_InitStructure.DMA_M2M= DMA_M2M_Enable; //选择硬件触发or软件触发
DMA_InitStructure.DMA_Mode= DMA_Mode_Normal; //传输模式,即是否使用自动重装
DMA_InitStructure.DMA_Priority= DMA_Priority_Medium; //优先级
DMA_Init(DMA1_Channel1,&DMA_InitStructure);
DMA_Cmd(DMA1_Channel1,DISABLE);
}
四、数据多次转运
(3.3)的完整代码仅能完成一次的数据转运。若想完成多次数据的转运,需要给计数器重新赋值,而给计数器重新赋值,需要令DMA_Cmd()处于使能状态,完成计数器赋值后再令其使能。为便于实现以上功能。我么使用MyDMA_Transfer()函数将以上功能封装起来
void MyDMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1,DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);
DMA_Cmd(DMA1_Channel1,ENABLE);
while(DMA_GetFlagStatus(DMA1_FLAG_GL1)==RESET);
DMA_ClearFlag(DMA1_FLAG_GL1);
}
- 首先令DMA_Cmd()失能。
- DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber)
设置当前DMAy通道传输中的数据单元数。即赋值计数器。
为保证前后转运时计数器的值一致,我们需要获取DMA_Init函数中的参数Size,但是又因为Size仅在DMA_Init()中起作用,因此我们定义一个全局变量uint16_t MyDMA_Size,吸收Size的值,并传入DMA_SetCurrDataCounter()。 - DMA_Cmd(DMA1_Channel1,ENABLE);
赋值完成后重新令Cmd使能。 - DMA_GetFlagStatus(uint32_t DMAy_FLAG)
检查是否设置了指定的DMAy通道标志。GL1—全局标志位,TC1—转运完成标志位,HT1—转运过半标志位,TE1—转运错误标志位
while(DMA_GetFlagStatus(DMA1_FLAG_GL1)==RESET)//等待转运的完成 - DMA_ClearFlag(DMA1_FLAG_GL1);
清除标志位,转运完成标志位需要手动清除,
五、现象代码&现象
#include "stm32f10x.h" // Device header
#include "Delay.h" // Device header
#include "OLED.h"
#include "MyDMA.h"
uint8_t DaTaA[]={0x01,0x02,0x03,0x04};
uint8_t DaTaB[]={0,0,0,0};
int main(void)
{
OLED_Init(); //OLED初始化
OLED_Clear();
MyDMA_Init((uint32_t)DaTaA,(uint32_t)DaTaB,4);
OLED_ShowString(1,1,"DaTaA:");
OLED_ShowString(3,1,"DaTaB:");
OLED_ShowHexNum(1,8,(uint32_t)DaTaA,8);
OLED_ShowHexNum(3,8,(uint32_t)DaTaB,8);
OLED_ShowHexNum(2,1,DaTaA[0],2);
OLED_ShowHexNum(2,4,DaTaA[1],2);
OLED_ShowHexNum(2,7,DaTaA[2],2);
OLED_ShowHexNum(2,10,DaTaA[3],2);
OLED_ShowHexNum(4,1,DaTaB[0],2);
OLED_ShowHexNum(4,4,DaTaB[1],2);
OLED_ShowHexNum(4,7,DaTaB[2],2);
OLED_ShowHexNum(4,10,DaTaB[3],2);
while(1)
{
DaTaA[0]++;
DaTaA[1]++;
DaTaA[2]++;
DaTaA[3]++;
OLED_ShowHexNum(2,1,DaTaA[0],2);
OLED_ShowHexNum(2,4,DaTaA[1],2);
OLED_ShowHexNum(2,7,DaTaA[2],2);
OLED_ShowHexNum(2,10,DaTaA[3],2);
OLED_ShowHexNum(4,1,DaTaB[0],2);
OLED_ShowHexNum(4,4,DaTaB[1],2);
OLED_ShowHexNum(4,7,DaTaB[2],2);
OLED_ShowHexNum(4,10,DaTaB[3],2);
Delay_ms(1000);
MyDMA_Transfer();
OLED_ShowHexNum(2,1,DaTaA[0],2);
OLED_ShowHexNum(2,4,DaTaA[1],2);
OLED_ShowHexNum(2,7,DaTaA[2],2);
OLED_ShowHexNum(2,10,DaTaA[3],2);
OLED_ShowHexNum(4,1,DaTaB[0],2);
OLED_ShowHexNum(4,4,DaTaB[1],2);
OLED_ShowHexNum(4,7,DaTaB[2],2);
OLED_ShowHexNum(4,10,DaTaB[3],2);
Delay_ms(1000);
}
}
注意:该现象代码中的MyDMA_Init()中的MDA_Cmd被设置成了DMA_Cmd(DMA1_Channel1,DISABLE)。以令其无法立即转运。