提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一,DMA简介
DMA(Direct Memory Access)直接存储器存取(在存储器之间进行数据转运)
DMA可以提供外设(即外设寄存器)和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
每个通道都支持软件触发和特定的硬件触发(每个外设的硬件触发源并不相同,需要我们额外配置)
STM32F103C8T6 DMA资源:DMA1(7个通道)
存储器映像
ROM只读存储器,非易失性,掉电不丢失的存储器。
RAM随机存储器,易失性,掉电丢失的存储器。
DMA框图
STM32为了高效,有条理的访问存储器,设计了总线矩阵。如图,矩阵左边是主动单元(拥有访问权),右边是被动单元(只能被左边的主动单元读写)。
可见,DMA1有7个通道,DMA2有5个通道,各个通道分别可以设置它们的源地址和目的地址,这样通道就可以独立工作了。
仲裁器:虽说DMA中通道可以分别地设置源地址和目的地址,独立的工作,但DMA总线只有一条,故需要分时复用DMA总线,这时,仲裁器来判断优先级。
补充:总线矩阵处也存在仲裁器,来 防止DMA与CPU访问同一目标时,产生冲突,但CPU仍能得到一半的带宽,使其仍能正常工作。
AHB从设备:连接AHB总线,存在于DMA中,属于DMA自身的寄存器,用来配置DMA参数。故可见,DMA即是主动单元,也是AHB总线上的被动单元。
DMA请求:连接各个外设,为DMA的硬件触发源。
补充:Flash只读存储器,只可读取数据,不能写入,如果DMA的目的地址指向Flash,程序出错。(想要强行更改Flash时,需配置Flash接口控制器)。
DMA基本结构
转运方向:DMA的数据转运,可以是从外设寄存器到存储器,也可以是存储器到外设寄存器。(转运方向有参数配置)。
外设和存储器:各有三个参数,分别为,起始地址,数据宽度,地址是否自增。
起始地址:即数据存储的首地址。(开始转运的第一个数据的地址)
数据宽度:指定转运一次要按多大的数据宽度进行。(可选择字节Byte,半字HalfWord,字Word。分别对应,8位,16位,32位)
地址是否自增:一次转运完成后,决定下一次转运是否需要地址移动到下一给位置(两边存储器可独立配置,如配置外设为不自增,存储器为自增,现象为存储器不同位置不断接收外设起始地址的数据)
注:虽然上文一直介绍的是外设(即外设寄存器)和存储器,但同样适用于存储器与存储器的相关配置,不必拘泥于图中的外设寄存器(只是名字而已,用来配置另一个存储器也适用)。
传输计数器:自减计数器,用来决定DMA转运几次,等于零时,DMA会恢复到起始地址,进行新一轮的转运。
自动重装器:决定当传输计数器为0时,是否恢复到最初的值。不重装为单次模式。重装为循环模式,模式选择取决于工程需求。如转运一个数组到其他存储器,使用单次模式,尽快转运完成;使用ADC循环模式时,通常配置ADC不断刷新数据,使用循环模式。
M2M(memory to memory)存储器到存储器:决定DMA使用软件触发还是硬件触发。M2M为1时选择软件触发(不同于ADC的软件触发,不需每转运一次调用一次,调用软件触发函数后,程序会尽快完成全部转运),为0时选择硬件触发。
最后,使用DMA必须满足三个条件(程序配置时额外注意):
DMA使能(调用DMA_Cmd()函数);传输计数器大于0;触发源,必须有触发信号。
注:更改传输计数器的值时,需先关闭DMA,再用相关函数配置传输计数器的值,然后再使能DMA。
可见,DMA使用不同外设信号作为触发源时,需选择指定的通道。(参数配置时注意对照上图选择所选通道)。另外,一个通道上有多个外设,具体选择你哪个外设作为触发信号,需在相关外设库函数中调用xxx_DMACmd()函数,开启触发信号输出。图中通道号越小,优先级越高,也可在程序中配置优先级,非常灵活。
数据宽度与对齐
简单来说,如果源端数据宽度与目标数据宽度相等,则数据正常转运;源端数据宽度小于目标数据宽度时,目标数据高位补零,低位为实际数据,数据成功转运;源端数据宽度大于目标数据宽度,源端数据高位丢失,低位为目标数据。
程序配置
ADC扫描模式+DMA
ADC为扫描模式,不断将通道里的数据存储到数据寄存器中,我们需要让DMA配合ADC,实现数据寄存器里的数据在被覆盖前搬运至其它存储器中。
主要配置流程:外设寄存器起始地址为ADC数据寄存器的地址,存储器首地址为自定义数组(AD_Value[])用来接收转运数据,数据宽度都为半字,外设地址不自增,存储器地址自增(以达到接收不同ADC中通道值的效果),计数器是否自动重装,取决于ADC的配置,单词扫描则单次转运,连续扫描则连续转运。触发源选择ADC硬件触发。
库函数介绍
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);//DMA使能
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); //DMA设置当前数据寄存器(写入DMA传输寄存器中)
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);//清除中断挂起位
ADC配合DMA实现多通道转运代码
uint16_t AD_Value[4];
void MyADC_DMA(void);
void AD_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*配置ADCCLK预分频系数*/
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
/*配置GPIO*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AIN;//配置位模拟输入模式
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
/*选择通道*/
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);//ADC规则组通道配置
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
/*配置ADC转化器*/
ADC_InitTypeDef ADC_InitStruct;
ADC_InitStruct.ADC_Mode=ADC_Mode_Independent;//独立模式
ADC_InitStruct.ADC_DataAlign=ADC_DataAlign_Right ;//右对齐模式
ADC_InitStruct.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//软件触发
ADC_InitStruct.ADC_ContinuousConvMode=ENABLE;//连续模式
ADC_InitStruct.ADC_ScanConvMode=ENABLE;//扫描模式
ADC_InitStruct.ADC_NbrOfChannel=4;
ADC_Init(ADC1,&ADC_InitStruct);
/*开启ADC*/
ADC_Cmd(ADC1, ENABLE);
/*ADC校准*/
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1)==SET);//等待是否校准完成
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1)==SET);
MyADC_DMA();
//ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}
void MyADC_DMA()
{
/*开启时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA连接AHB总线,故使用时开启AHP总线上的时钟
/*DMA初始化*/
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//外设寄存器地址
DMA_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//一个字节的数据宽度
DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//完成一次,后地址不自增,一直转运ADC数据寄存器里的数据
DMA_InitStruct.DMA_Priority=DMA_Priority_Medium;//中级优先级
DMA_InitStruct.DMA_MemoryBaseAddr=(uint32_t)AD_Value;//数据寄存器地址
DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//选择半字的数据宽度,对应16位的ADC数据寄存器。
DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable;//完成一次,后地址自增,对应着将ADC不同通道不断刷新的数据分别搬运至指定的存储器中。
DMA_InitStruct.DMA_Mode=DMA_Mode_Circular; //循环模式,对应ADC的连续模式
DMA_InitStruct.DMA_BufferSize=4;//传输寄存器值的大小
DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC;//配置数据传输方向以外设寄存器的数据位源数据
DMA_InitStruct.DMA_M2M=DMA_M2M_Disable;//软件触发失能,使用ADC触发
DMA_Init(DMA1_Channel1,&DMA_InitStruct);
/*启动DMA通道*/
ADC_DMACmd(ADC1,ENABLE);//使能ADC中DMA的硬件触发源
DMA_Cmd(DMA1_Channel1,ENABLE);
/*DMA传输数据的三个条件,
传输数据器大于0,
DMA启动,
信号触发源*/
}
注:对于(uint32_t)&ADC1->DR,ADC1为结构体指针,DR为结构体的成员,ADC1->DR为指向结构体中的DR,即指向DR(ADC数据寄存器),取地址后,再强制类型转换为无符号32位后,传递地址。
可见,ADC1的基地址(ADC的起始地址)被强制转换为结构体指针,ST公司定义了一个结构体变量一一对应ADC的各个寄存器(即地址经过映射后,变量的地址和寄存器的地址相同),来供我们调用ADC的各个寄存器。(其他硬件同理)