代码:
#include "stm32f10x.h" // Device headeruint16_t MyDMA_Size;void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)//函数初始化DMA
{MyDMA_Size = Size;
//第一步,RCC开启DMA的时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备,所以要用AHB开启时钟的函数,第一个参数在函数中的定义是:对于互联型设备,这个参数可以是下面这些值的组合;对于其他设备是这下面的组合。互联型是STM32F105/107的型号,F103的在其它设备下面的参数表里选RCC_AHBPeriph_DMA1。第二个参数ENABLE,开启DMA1的时钟。//第二步,参数初始化配置。直接调用DMA_Init,初始化各个参数,包括外设和存储器站点的起始地址、数据宽度、地址是否自增、方向、传输计数器、是否需要自动重装、选择触发源、通道优先级。这些所有的参数,通过一个结构体,就可以配置好了。DMA_InitTypeDef DMA_InitStructure;//外设站点的:DMA_InitStructure.DMA_PeripheralBaseAddr=AddrA;//起始地址,外设站点的基地址,这在里要写一个32位的地址,比如 0x20000000 这样的地址,对于SRAM的数组,他的地址是编译器分配的,并不是固定的。所以一般不会写绝对地址,而是通过数组名来获取地址。在这里就把这个地址提取成初始化函数的参数,这样在初始化的时候,想转运哪个数组就把哪个数组的地址传进来就行了,所以这里函数void MyDMA_Init(void),改成void MyDMA_Init(uint32_t AddrA),然后把AddrA放在这个参数这里。这样外设站点的地址就完成了。DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//数据宽度,函数定义中介绍是,指定数据宽度的参数可以是以下值:
//DMA_PeripheralDataSize_Byte Byte,字节,就是uint8_t
//DMA_PeripheralDataSize_HalfWord HalfWord,半字,就是uint16_t
//DMA_PeripheralDataSize_Word Word,字,就是uint32_t
//这里需要以字节的方式传输,所以就填入DMA_PeripheralDataSize_ByteDMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//是否自增,定义解释是,指定外设地址是自增或者不是,参数取值:
//DMA_PeripheralInc_Enable 自增
//DMA_PeripheralInc_Disable 不自增
//数组之间的转运,地址需要自增,所以这里选择DMA_PeripheralInc_Enable//存储器站点的:DMA_InitStructure.DMA_MemoryBaseAddr=AddrB;//起始地址,存储器站点的基地址,也把它提取成参数,在void MyDMA_Init(uint32_t AddrA)函数这里加一个参数变为void MyDMA_Init(uint32_t AddrA,uint32_t AddrB),然后参数这里放AddrB,作为存储器站点的起始地址。DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//数据宽度,和外设站点一样也选择Byte参数,以字节传输。
//DMA_MemoryDataSize_Byte Byte,字节,就是uint8_t
//DMA_MemoryDataSize_HalfWord HalfWord,半字,就是uint16_t
//DMA_MemoryDataSize_Word Word,字,就是uint32_tDMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//是否自增,和外设站点一样也选择地址自增。
// DMA_MemoryInc_Enable 自增
// DMA_MemoryInc_Disable 不自增
//这样外设站点和存储器站点的参数就配置好了。DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向,函数定义解释是,指定外设站点是源端还是目的地。
//DMA_DIR_PeripheralDST 外设站点作为DST,destination,目的地,外设站点作为目的地,其实就是传输方向是存储器站点到外设站点。
//DMA_DIR_PeripheralSRC 外设站点作为SRC,source,源头,也就是外设站点到存储器站点的传输方向。
//函数设计将DataA放在外设站点,DataB放在存储器站点。传输方向就是外设站点到存储器站点,所以这里选择 DMA_DIR_PeripheralSRC参数,外设站点作为数据源。DMA_InitStructure.DMA_BufferSize=Size;//缓存区大小,其实就是传输计数器。函数定义解释是,以数据单元指定缓存区大小,数据单元等于外设数据宽度或者存储器数据宽度,数据宽度取决于传输方向。以数据单元指定缓存区大小,就是说需要传送几个数据单元,这个数据单元等于传输源站点的DataSize,简单理解就是,DMA_BufferSize就是传输计数器,指定传输几次。可以查看DMA_Init函数的源码,DMA_BufferSize参数其实就是直接赋值给了传输计数器的寄存器,它的取值是0~65535.这里把DMA_BufferSize参数也提取到函数的参数里来,然后把Size放到DMA_BufferSize参数这里,这样传输次数就完成了。DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//传输模式,其实就是指定传输计数器是否使用自动重装。函数定义解释,指定操作方式有对应参数取值列表。还有一个注意事项,写的是循环模式,也就是自动重装,不能应用在存储器到存储器的情况下。也就是之前博文说的,自动重装和软件触发不能同时使用,如果同时使用,DMA就会连续触发,永远也不会停下来。
//DMA_Mode_Circular 循环模式,就是传输计数器自动重装
//DMA_Mode_Normal 正常模式,就是传输计数器不自动重装,自减到0后停下来。
//这里转运数组,是存储器到存储器的传输,转运一次停下来就行了。选择正常模式DMA_Mode_NormalDMA_InitStructure.DMA_M2M=DMA_M2M_Enable;//选择是否存储到存储器,其实就是选择硬件触发还是软件触发。函数定义解释是,DMA是否应用于存储器到存储器的转运,存储器到存储器的模式就是软件触发。
//DMA_M2M_Enable 使用软件触发
//DMA_M2M_Disable 不使用软件触发,也就是使用硬件触发。
//这里转运数组,所以选择DMA_M2M_EnableDMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级,按照参数要求,给一个优先级。函数定义解释,指定通道的软件优先级。
// DMA_Priority_VeryHigh 非常高
// DMA_Priority_High 高
// DMA_Priority_Medium 中等
// DMA_Priority_Low 低
//如果有多个通道,可以指定一下优先级,确保紧急的转运有更高的优先级。这里只有一个通道,那优先级可以任意。//这些参数可以对应结构图来看,每一个参数都是可以配置的DMA_Init(DMA1_Channel1,&DMA_InitStructure);//第一个参数:DMAy_Channelx,y可以是1或2,用来选择是哪个DMA;对于DMA1,x可以是1~7,或者对于DMA2,x可以是1~5.用来选择是哪一个通道。所以DMA_Init函数的第一个参数,既选择了是哪个DMA,也选择了是DMA的哪个通道。
//DMAy_Channelx,这里y写为1,选择DMA1;x选择通道,这里因为是存储器到存储器的转运,用的是软件触发,所以通道可以任意选择。这里x给1,通道1.
//把结构体的地址放在第二个参数的位置,这样就是把结构体指定的参数,配置到DMA1的通道1里面去。
//以上DMA的参数就配置完成了。参数虽然比较多,但是对照框图来理解的话,就比较清晰。
//到目前为止,DMA还不能工作。DMA转运有三个条件:第一个条件,传输计数器大于0;第二个条件,触发源有触发信号;第三个条件,DMA使能。三个条件缺一不可,目前如果传一个大于0的Size的话,第一个条件满足。触发源为软件触发,所以一直都有触发信号,第二个条件满足。最后一个条件,DMA还没有使能,第三个条件不满足。所以到目前为止DMA还不会工作。如果想在初始化之后就立刻工作的话,可以在最后加上DMA_Cmd函数
//第三步,开关控制,调用DMA_Cmd函数,给指定的通道使能,就完成了。 DMA_Cmd(DMA1_Channel1,ENABLE);//使能DMA之后,三个条件满足,DMA就会进行数据转运了。转运一次,传输计数器自减一次,当传输计数器减到0之后,转运完成。转运完成的同时第一个条件就不满足了,转运停止。这样就完成了一次数组之间的数据转运。
// DMA_Cmd(DMA1_Channel1,DISABLE);//这里先给DISABLE,不让DMA初始化之后,就立刻进行转运。而是等调用了Transfer函数之后,再进行转运。调用一次,转运一次,这样来工作。}void MyDMA_Transfer(void)//调用一次这个函数,就再启动一次DMA转运。
{//函数里面需要重新给传输计数器赋值,传输计数器赋值,必须要先给DMA失能,所以这里,调用DMA_Cmd函数,DMA_Cmd(DMA1_Channel1,DISABLE);//第二个参数给DISABLE。然后就可以给传输计数器赋值了。DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);//第一个参数,选择DMA和通道。第二个参数是指定要给传输计数器写入的值,这里需要获取一下初始化这里的Size参数,但是这俩参数不在一个函数,不能直接传递过来。可以在代码块最前面定义一个全局变量MyDMA_Size,初始化的时候把Size往这个全局变量里也存一份,之后在这个函数块里,就可以使用全局变量的MyDMA_Size了。这样就可以重新给传输计数器赋值了。//最后,再次调用DMA_Cmd函数,DMA_Cmd(DMA1_Channel1,ENABLE);//第二个参数给ENABLE//这样DMA传输的3个条件又重新满足了。DMA就会再次开始转运
//转运开始之后还需要做一个工作,就是等待转运完成,因为转运也是要花一些时间的,等待转运完成调用 DMA_GetFlagStatus函数,查看标志位while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
//总共四种标志位,以DMA1的通道1举例,其它所有的通道,都是这4种标志位。
//DMA1_FLAG_GL1:全局标志位
//DMA1_FLAG_TC1:转运完成标志位
//DMA1_FLAG_HT1:转运过半标志位
//DMA1_FLAG_TE1:转运错误标志位
//这里需要检查DMA1通道1转换完成的标志位,所以选择DMA1_FLAG_TC1参数。//转运完成之后,标志位置1,所以需要加一个while循环,等待这个标志位==RESET,如果没有完成,就一直循环等待,这样就实现了等待转运完成的效果了。标志位置1之后,不要忘记清除标志位,这个标志位需要手动清除。调用 DMA_ClearFlag函数DMA_ClearFlag(DMA1_FLAG_TC1);}