DMA应用实例详解
闲来无事,学习了下DMA的相关知识和使用。平时看到的DMA都是简单的存储器到寄存器或者寄存器到存储器这样单类的传输。学习完DMA后,我想写个比较综合点的DMA学习实例,不仅能增加自己对DMA的深入应用,也同时发表于此给网友提供参考。之所以说多知识,实例涉及到存储器到寄存器和寄存器到存储器,以及DMA中断使用等相关知识。
实例内容:单片机采集AD值通过DMA传输给RAM存储器(就是定义的变量),同时又将存储器数据传输给串口输出,实现AD采集与串口输出的转换。
下面赋上一些主要的代码进行分析:
1.主函数部分
int main(void)
{
delay_init(); //延时初始化
RCCInit();//时钟初始化
USARTInit(USART2 ,115200);//串口初始化
ADCInit(ADC1 , 1); //AD初始化PA1
Get_ADCValue(ADC1 ,ADC_Channel_1 ,1);//对ADC通道配置
//ADC1-->存储器(adcValue)(注传入地址位要32位)
DMAx_ADCx_Init(DMA1_Channel1 ,DMA_DIR_PeripheralSRC,(u32)(&adcValue), u32)&(ADC1->DR),1);
//存储器(adcValue)-->UASART1
DMAx_USARTx_Init(DMA1_Channel7 ,DMA_DIR_PeripheralDST,(u32)usart_BUFF, (u32)&(USART2->DR),bufferSize);
NVIC_Config();//注意,实测过,DMA中断配置必须放在DMA配置后面才能用
DMAx_Enable(DMA1_Channel1 , 1);
ADC_DMACmd(ADC1, ENABLE);
DMAx_Enable(DMA1_Channel7 , bufferSize);//先启动,使满标志不为0
USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
printf("下面是DMA测试,AD值为:\r\n");
while(1)
{
delay_ms(100);
DMAx_Enable(DMA1_Channel1 , 1);
}
}
2.DMA1_CH1中断函数
void DMA1_Channel1_IRQHandler()
{
if(DMA_GetITStatus(DMA1_IT_TC1) == SET)//获取DMA1_CH1的传输完成标志位
{
DMA_ClearITPendingBit(DMA1_IT_TC1);//清除满标志(注意清除后,不能去等待判满)
//16位整型转化为字符串型(每个字符8位,1字节)(转换中加了个空格,方便显示)
sprintf(usart_BUFF,"%d ",adcValue);
while(!DMA_GetFlagStatus( DMA1_FLAG_TC7));//等待传输完成(注意 等待判满前,不能清除标志)
DMA_ClearFlag(DMA1_FLAG_TC7); //完成后清除
DMAx_Enable(DMA1_Channel7 , bufferSize);//开始串口的DMA传输
}
}
3.ADC初始化
void ADCInit(ADC_TypeDef* ADCx , u8 Channel_num)
{
//=============对ADC1的配置并校准====================//
ADC_InitTypeDef ADC_Inittrue;
ADC_DeInit(ADCx);//复位ADC1
ADC_Inittrue.ADC_ContinuousConvMode = ENABLE;//连续转换
ADC_Inittrue.ADC_DataAlign = ADC_DataAlign_Right;//右对齐。(ADC为12位精度,其数字信号存于16位的寄存器)
ADC_Inittrue.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//触发方式,无外部触发=软件触发
ADC_Inittrue.ADC_Mode = ADC_Mode_Independent;//独立模式
ADC_Inittrue.ADC_NbrOfChannel = Channel_num;//顺序进行规则转换的 ADC 通道的数目(1--16)
ADC_Inittrue.ADC_ScanConvMode = DISABLE;//无扫描模式,即工作在单通道下
ADC_Init(ADCx, &ADC_Inittrue);//初始化配置模块ADC1
ADC_Cmd(ADCx, ENABLE);//使能ADC1
ADC_ResetCalibration(ADCx); //复位校准
while(ADC_GetResetCalibrationStatus(ADCx));
ADC_StartCalibration(ADCx);//开启adc校准
while(ADC_GetCalibrationStatus(ADCx));
}
4.ADC通道配置,并获取相应模块下通道的ADC值(这里只用到通道配置就够了)
u16 Get_ADCValue(ADC_TypeDef* ADCx ,u8 ADC_Channel_x ,u8 rank)
{
//========对ADC的规则通道的配置(选通道x)=========//
ADC_RegularChannelConfig(ADCx,ADC_Channel_x, rank , ADC_SampleTime_28Cycles5);//ADC1的通道4,排在第1个被转换,并且采集时间为28.5个ADC1时钟周期
ADC_SoftwareStartConvCmd(ADCx ,ENABLE);//开启软件转换
//========对ADC转换部分=========//
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
return ADC_GetConversionValue(ADCx)&0x00000fff;//换回ADC1转换后的值
}
5.DMA_ADC初始化
//名称:DMA ADC初始化
//传入参数:通道 传输方向 存储器基地址 外设基地址 数据量
//注:传入地址位数32位
//说明:默认非循环,存储器地址不自加,外设不自加,数据宽度都为16位
void DMAx_ADCx_Init(DMA_Channel_TypeDef* DMAx_Channelx ,u32 DMA_DIR_Peripheralxxx , u32 MemoryBaseAddr, u32 PeripheralBaseAddr,u16 DMA_BufferSize)
{
DMA_InitTypeDef DMA_InitStruct;
if(DMA1_Channel1 <= DMAx_Channelx && DMA1_Channel7 >= DMAx_Channelx)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA位于AHB时钟上
}
if(DMA2_Channel1 <= DMAx_Channelx && DMA2_Channel5 >= DMAx_Channelx)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);//DMA位于AHB时钟上
}
DMA_DeInit(DMAx_Channelx);//
DMA_InitStruct.DMA_BufferSize = DMA_BufferSize;//DMA传输的数据量
DMA_InitStruct.DMA_DIR = DMA_DIR_Peripheralxxx;//传输方向:外设为目标地址(存储器到外设)
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //存储器到存储器传输失能
DMA_InitStruct.DMA_MemoryBaseAddr = MemoryBaseAddr;//存储器基地址(可为sram或flash,程序变量一般位于sram)
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//存储器传输数据宽度16(可选8,16,32位)
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Disable;//存储器地址不递增
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;// 不循环传输模式(循环:地址递增满后,又重载从基地址开始传输)(不循环:递增满后不重载且暂停)
DMA_InitStruct.DMA_PeripheralBaseAddr = PeripheralBaseAddr;//外设寄存器的基地址(多是数据寄存器)
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设寄存器传输数据宽度16(8,16,32位)
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不进行递增
DMA_InitStruct.DMA_Priority = DMA_Priority_High;//传输优先级:高(四种:最高,高,中,低)
DMA_Init(DMAx_Channelx, &DMA_InitStruct);
}
6.DMA_USART初始化
注意此初始化和ADC 的DMA初始化是不一样的,具体不一样处见函数说明, 和调用的实参。
//名称:DMA USART初始化
//传入参数:通道 传输方向 存储器基地址 外设基地址 数据量
//注;传入地址位数32位
//说明:默认非循环,存储器地址自加,外设不自加,数据宽度都为8位
void DMAx_USARTx_Init(DMA_Channel_TypeDef* DMAx_Channelx ,u32 DMA_DIR_Peripheralxxx , u32 MemoryBaseAddr, u32 PeripheralBaseAddr,u16 DMA_BufferSize)
{
DMA_InitTypeDef DMA_InitStruct;
if(DMA1_Channel1 <= DMAx_Channelx && DMA1_Channel7 >= DMAx_Channelx)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA位于AHB时钟上
}
if(DMA2_Channel1 <= DMAx_Channelx && DMA2_Channel5 >= DMAx_Channelx)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);//DMA位于AHB时钟上
}
DMA_DeInit(DMAx_Channelx);
DMA_InitStruct.DMA_BufferSize = DMA_BufferSize;//DMA传输的数据量
DMA_InitStruct.DMA_DIR = DMA_DIR_Peripheralxxx;//传输方向:外设为目标地址(存储器到外设)
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //存储器到存储器传输失能
DMA_InitStruct.DMA_MemoryBaseAddr = MemoryBaseAddr;//存储器基地址(可为sram或flash,程序变量一般位于sram)
DMA_InitStruct.DMA_MemoryDataSize =DMA_MemoryDataSize_Byte;存储器传输数据宽度8位(8,16,32位)
DMA_InitStruct.DMA_MemoryInc =DMA_MemoryInc_Enable;//存储器地址随传输而递增(加多少与数据宽度有关,可见芯片参考手册,这里是地址自加1)
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;//不循环传输模式(循环:地址递增满后,又重载从基地址开始传输)(不循环:递增满后不重载且暂停)
DMA_InitStruct.DMA_PeripheralBaseAddr = PeripheralBaseAddr;//外设寄存器的基地址(多是数据寄存器)
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;// DMA_PeripheralDataSize_HalfWord;//外设寄存器传输数据宽度8位(8,16,32位)
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不进行递增
DMA_InitStruct.DMA_Priority = DMA_Priority_High;//传输优先级:高(四种:最高,高,中,低)
DMA_Init(DMAx_Channelx, &DMA_InitStruct);
}
7.DMA启动传输(用于每次传输开启)
//DMA启动传输
//传入参数:通道 数据量
void DMAx_Enable(DMA_Channel_TypeDef* DMAx_Channelx ,u16 DMA_BufferSize)
{
DMA_Cmd(DMAx_Channelx, DISABLE);//失能
DMA_SetCurrDataCounter( DMAx_Channelx , DMA_BufferSize); //重新设置传输数据长度
DMA_Cmd(DMAx_Channelx, ENABLE);//使能
}
8.中断配置函数
void NVIC_Config()
{
NVICInit(NVIC_PriorityGroup_2, DMA1_Channel1_IRQn , 0, 1); //组别2 抢占0 从占1
DMA_ClearFlag(DMA1_FLAG_TC1);//清满标志
DMA_ClearITPendingBit(DMA1_IT_TC1);
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);//开启全部传输完成中断
}
9.RCC时钟配置
void RCCInit()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO | RCC_APB2Periph_USART1 , ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 , ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADC时钟周期 6分频
}
下面是上位机显示传输的数据:
对于串口,一次只能输出8位(一字节数),即使传进去16位数,也被截取前半段 ADC采集为10为精度,数据存储宽度要大于8位 所以:DMA的ADC传输需要16位,而USART传输只能8位传输 该例程的ADC为连续转换模式,若是单次需要每次对通道重新配置一次 *DMA为循环模式只需要使能一次,否则非循环下需要重新使能 来启动传输 每个DMA通道对应不同外设见手册 DMA中断配置必须放在DMA参数配置后面才能用 注意:等待判满前,不能清除标志;注意:清除后,不能去等待判满,否则死循环。