需要用到的DMA配置函数
DMA_Init();//DMA初始化
DMA_StructInit();//给DMA结构体赋默认初始值
DMA_Cmd();//使能/失能DMA
DMA_SetCurrDataCounter();//设置当前数据寄存器(给传输计数器写数据)
DMA_GetCurrDataCounter();//获取当前数据寄存器(返回传输计数器的值)
![](https://i-blog.csdnimg.cn/blog_migrate/7c2ec27651501bcef187914a795332e2.png)
配置DMA时参考该图流程
任务:让DMA把DataA的数据转运到DataB里(存储器SRAM到存储器SRAM)
1、完成DMA驱动文件导入操作和编写驱动程序基本代码(参考之前文章)
//将【MyDMA】驱动文件放在【System】文件夹中(DMA不涉及外围硬件电路)
//【MyDMA】名称是为了防止与库函数里面的DMA重复
2、在MyDMA.c中初始化函数MyDMA_Init
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size){//第一步:RCC开启DMA的时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备//第二步:初始化DMA参数DMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//外设【起始地址】32位,//SRAM数组地址由编译器分配,并不固定,因此不写绝对地址,而是通过数组名来获取地址//此处把这个地址提取为初始化函数的参数,在初始化时需要转运哪个数组,传数组地址就行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;//缓存区大小【传输计数器】(传输多少次)//设置一个参数用于直接配置【传输计数器】的寄存器(0~65535)DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//硬件触发or软件触发DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//是否使用自动重装器DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//按照参数要求给定优先级DMA_Init(DMA1_Channel1,&DMA_InitStructure);//第三步:开关控制【DMACmd】DMA_Cmd(DMA1_Channel1,ENABLE);//如果选择的是【硬件触发】,要在对应外设调用【PPP_DMACmd】开启触发信号的输出}
3、在MyDMA.h中声明初始化函数MyDMA_Init
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size);
4、在主程序main.c中#include "MyDMA.h"
#include "MyDMA.h"
5、在主循环中编写程序主体
//定义DMA转运的【源端数组】和【目的数组】uint8_t DataA[] = {0x01 , 0x02 , 0x03 , 0x04};//源端数组uint8_t DataB[] = {0 , 0 , 0 , 0}; //目的数组//数组名就是地址,因此传地址时不用加取地址符号“&”int main(void){OLED_Init();//显示转运前DataA和DataB的数据OLED_ShowHexNum(1,1,DataA[0],2);OLED_ShowHexNum(1,4,DataA[1],2);OLED_ShowHexNum(1,7,DataA[2],2);OLED_ShowHexNum(1,10,DataA[3],2);OLED_ShowHexNum(2,1,DataB[0],2);OLED_ShowHexNum(2,4,DataB[1],2);OLED_ShowHexNum(2,7,DataB[2],2);OLED_ShowHexNum(2,10,DataB[3],2);//DMA立刻工作,把DataA数组的数据转运到DataB里面MyDMA_Init( (uint32_t)DataA , (uint32_t)DataB , 4);//参数1:源端地址(传入DataA数组的首地址)//参数2:目的地址(传入DataB数组的首地址)//参数3:传输次数(有4个数据,传输4次)//显示转运后DataA和DataB的数据OLED_ShowHexNum(3,1,DataA[0],2);OLED_ShowHexNum(3,4,DataA[1],2);OLED_ShowHexNum(3,7,DataA[2],2);OLED_ShowHexNum(3,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){}}
实现功能:上电后OLED进行初始化,先显示转运前DataA和DataB的数据,经过DMA转运一次后显示转运后DataA和DataB的数据,DataA的数据成功转运(复制)到了DataB,源端数据不发生变化
上面的程序是DMA初始化后立刻开始转运,并且转运一次后DMA停止
如果DataA的数据发生变化,需要再转运:给传输计数器重新赋值
(修改MyDMA_Init部分配置)
2、在MyDMA.c中初始化函数MyDMA_Init
uint16_t MyDMA_Size;void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size){MyDMA_Size = Size;//第一步:RCC开启DMA的时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备//第二步:初始化DMA参数DMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//外设【起始地址】32位,//SRAM数组地址由编译器分配,并不固定,因此不写绝对地址,而是通过数组名来获取地址//此处把这个地址提取为初始化函数的参数,在初始化时需要转运哪个数组,传数组地址就行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;//缓存区大小【传输计数器】(传输多少次)//设置一个参数用于直接配置【传输计数器】的寄存器(0~65535)DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//硬件触发or软件触发DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//是否使用自动重装器DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//按照参数要求给定优先级DMA_Init(DMA1_Channel1,&DMA_InitStructure);//第三步:开关控制【DMACmd】DMA_Cmd(DMA1_Channel1,DISABLE);//不让DMA初始化后立刻进行转运//等调用MyDMA_Transfer函数后再进行转运//如果选择的是【硬件触发】,要在对应外设调用【PPP_DMACmd】开启触发信号的输出}编写连续转运函数MyDMA_Transfer//调用一次这个函数,就再启动一次DMA转运void MyDMA_Transfer(void){//先给DMA失能DMA_Cmd(DMA1_Channel1,DISABLE);//给传输计数器重新赋值DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);//获取初始化的Size参数(定义全局变量以跨函数传递)//使能DMADMA_Cmd(DMA1_Channel1,ENABLE);//等待转运完成while( DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET );//转运完成后标志位置1(没有完成一直while循环,转运完成跳出循环)//手动清除标志位DMA_ClearFlag(DMA1_FLAG_TC1);}
3、在MyDMA.h中声明初始化函数MyDMA_Init和连续转运函数MyDMA_Transfer
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size);void MyDMA_Transfer(void);
4、在主程序main.c中#include "MyDMA.h"
#include "MyDMA.h"
5、在主循环中编写程序主体
//定义DMA转运的【源端数组】和【目的数组】uint8_t DataA[] = {0x01 , 0x02 , 0x03 , 0x04};//源端数组uint8_t DataB[] = {0 , 0 , 0 , 0}; //目的数组//数组名就是地址,因此传地址时不用加取地址符号“&”int main(void){OLED_Init();MyDMA_Init( (uint32_t)DataA , (uint32_t)DataB , 4);//参数1:源端地址(传入DataA数组的首地址)//参数2:目的地址(传入DataB数组的首地址)//参数3:传输次数(有4个数据,传输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);while(1){//变换DataA的源端测试数据DataA[0] ++;DataA[1] ++;DataA[2] ++;DataA[3] ++;//转运前的DataA数据和DataB数据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();//转运后的DataA数据和DataB数据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);}}
实现功能:上电后OLED进行初始化并显示Data字符和地址,DMA初始化函数确定传输配置,DataA数据不断加1,并每隔一秒传入DataB中通过OLED显示