1. DMA初始化结构体详解
标准库函数对每个外设都建立了一个初始化结构体xxx_InitTypeDef(xxx为外设名称),结构体成员用于设置外设工作参数, 并由标准库函数xxx_Init()调用这些设定参数进入设置外设相应的寄存器,达到配置外设工作环境的目的。
结构体xxx_InitTypeDef和库函数xxx_Init配合使用是标准库精髓所在,理解了结构体xxx_InitTypeDef每个成员意义基本上就可以对该外设运用自如。 结构体xxx_InitTypeDef定义在stm32f10x_xxx.h(后面xxx为外设名称)文件中,库函数xxx_Init定义在stm32f10x_xxx.c文件中, 编程时我们可以结合这两个文件内注释使用。
DMA_ InitTypeDef初始化结构体
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; // 外设地址
uint32_t DMA_MemoryBaseAddr; // 存储器地址
uint32_t DMA_DIR; // 传输方向
uint32_t DMA_BufferSize; // 传输数目
uint32_t DMA_PeripheralInc; // 外设地址增量模式
uint32_t DMA_MemoryInc; // 存储器地址增量模式
uint32_t DMA_PeripheralDataSize; // 外设数据宽度
uint32_t DMA_MemoryDataSize; // 存储器数据宽度
uint32_t DMA_Mode; // 模式选择
uint32_t DMA_Priority; // 通道优先级
uint32_t DMA_M2M; // 存储器到存储器模式
} DMA_InitTypeDef;
1) DMA_PeripheralBaseAddr: 外设地址,设定DMA_CPAR寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。
2) DMA_Memory0BaseAddr: 存储器地址,设定DMA_CMAR寄存器值;一般设置为我们自定义存储区的首地址。
3) DMA_DIR: 传输方向选择,可选外设到存储器、存储器到外设。它设定DMA_CCR寄存器的DIR[1:0]位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。
4) DMA_BufferSize: 设定待传输数据数目,初始化设定DMA_CNDTR寄存器的值,单位是字节数,最大值为2^16。
5) DMA_PeripheralInc: 如果配置为DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定DMA_CCR寄存器的PINC位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。
6) DMA_MemoryInc: 如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定DMA_CCR寄存器的MINC位的值;自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。
7) DMA_PeripheralDataSize: 外设数据宽度,可选字节(8位)、半字(16位)和字(32位),它设定DMA_CCR寄存器的PSIZE[1:0]位的值。
8) DMA_MemoryDataSize: 存储器数据宽度,可选字节(8位)、半字(16位)和字(32位),它设定DMA_CCR寄存器的MSIZE[1:0]位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。
9) DMA_Mode: DMA传输模式选择,可选一次传输或者循环传输,它设定DMA_CCR寄存器的CIRC位的值。例程我们的ADC采集是持续循环进行的,所以使用循环传输模式。
10) DMA_Priority: 软件设置通道的优先级,有4个可选优先级分别为非常高、高、中和低,它设定DMA_CCR寄存器的PL[1:0]位的值。DMA通道优先级只有在多个DMA通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。
11) DMA_M2M: 存储器到存储器模式,使用存储器到存储器时用到,设定DMA_CCR的位14 MEN2MEN即可启动存储器到存储器模式。
2. DMA数据转运
初始化的步骤,我们还是看一下PPT的这个基本结构图。
- 第一步,RCC开启DMA的时钟
- 第二步,直接调用DMA_Init,初始化这里各个参数,包括外设和存储器站点的起始地址,数据宽度,地址是否自增、方向、传输计数器、是否需要自动重装,选择触发源、通道优先级,那这所有的参数,通过一个结构体,就可以配置好了。
- 第三步,就可以进行开关控制,调用DMA_Cmd函数,给指定的通道使能,就完成了。
- 在这里,如果你选择的是硬件触发不要忘了在对应的外设调用一下XXX_DMACmd,开启一下触发信号的输出;如果你需要DMA的中断,那就调用DMA_ITConfig,开启中断输出,再在NVIC里,配置相应的中断通道,然后写中断函数就行了。中断的配置各个外设都一样,上面的结构图暂时没有画中断的部分。
- 第四步,在运行的过程中,如果转运完成,传输计数器清0了。这时想再给传输计数器赋值的话,就DMA失能、写传输计数器、DMA使能,这样就行了,这就是dma的编程思路。
DMA库函数
//恢复缺省配置
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);//初始化
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);//结构体初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);//使能
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);//中断输出使能
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);//设置当前计数器,给传输计数器写数据的
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);//获取当前数据寄存器,是返回传输计数器的值
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);//获取标志位状态
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);//清除标志位
void DMA_ClearFlag(uint32_t DMAy_FLAG);//获取中断状态
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);//清除中断挂起位
void DMA_ClearITPendingBit(uint32_t DMAy_IT);
初始化第一步,RCC开启DMA的时钟
注意:这里开启DMA时钟的时候,根据型号不同开启时钟参数也不同
字节,uint8_t
半字,uint16_t
字,uint32_t
第一个是外设站点为DST,目的地,传输方向是:存储器站点到外设站点
第二个是外设站点为SRC,源端,传输方向是: 外设站点到存储器站点
打算把DataA放在外设站点,把DataB放在存储器站点,传输方向是: 外设站点到存储器站点
第一个是循环模式,就是传输计数器自动重装。
第二个是正常模式,就是传输计数器不自动重装,自减到0后停下来。
这里我们转运数组是,存储器到存储器的传输,所以选正常模式。因为循环模式不能应用于存储器到存储器的情况,因为自动重装和软件触发不能同时使用,如果同时使用,DMA就会连续触发,永远也不会停下来。
第一个M2M_Enable,就是使用软件触发。
第二个M2M_Disable, 就是不使用软件触发,也就是使用硬件触发。
MyDMA.c
#include "stm32f10x.h" // Device header
uint16_t MyDMA_Size; //定义全局变量,用于Init函数的Size,供Transfer函数使用
/**
*函数:DMA初始化
*参数:AddrA 源数组的首地址
*参数:AddrB 目的数组的首地址
*参数:size转运的数据大小
*/
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size) //对于SRAM的数组,它的地址是编译器分配的,并不是固定的,是通过数组名来获取地址,这里把这个地址提取成初始化函数的参数。这样在初始化的时候,你相转运那个数组,就把哪个数组的地址传进来就行。
{
MyDMA_Size = Size; //初始化时,把size往全局变量也存一份
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启时钟,DMA是AHB总线的设备
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; //存储器站点的起始地址。 给定形参AddrB
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器站点的数据宽度。 以字节的方式传输
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器站点的地址是否自增。 地址自增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向。 SRC外设站点为数据源,外设站点到存储器站点
DMA_InitStructure.DMA_BufferSize = Size; //缓存区大小,其实就是配置传输计数器,指定传输几次
DMA_Ini