在上一讲单通道ADC电压采集的基础上,本节主要介绍CKS32F4xx系列产品基于DMA传输的ADC多通道电压采集转换实现。
DMA传输在ADC中的应用
DMA是直接存储器存取,通常在使用ADC时,需要通过MCU内核不停的读取数据,如果使用DMA,那么读取的过程会绕过MCU,减轻MCU内核的处理压力,这样有利于资源的充分利用,提高ADC数据的处理效率。由于ADC规则通道组只有一个数据寄存器中,当转换多个通道时,使用DMA还可以避免丢失已经存储在ADC_DR寄存器中的数据。在使能DMA模式的情况下,每完成规则通道组中的一个通道转换后,都会生成一个DMA请求,便可将转换的数据从ADC_DR寄存器传输到用指定的目标内存位置。这样取代单通道实验使用中断服务的读取方法,可以实现多通道ADC应用中高速高效的采集。
软件设计要点
跟单通道例程一样,编写两个ADC驱动文件,bsp_adc.h和bsp_adc.c,用来存放ADC所用IO引脚的初始化函数以及ADC和DMA相关配置函数,主要流程为:
(1)初始化配置ADC目标引脚为interwetten与威廉的赔率体系 输入模式;
(2)使能ADC时钟和DMA时钟;
(3)配置DMA从ADC数据寄存器传输数据到指定的存储区;
(4)配置通用ADC为独立模式;
(5)设置ADC为12位分辨率,启动扫描,连续转换,不需要外部触发;
(6)设置ADC转换通道顺序及采样时间;
(7)使能DMA请求,DMA在AD转换完自动传输数据到指定的存储区;
(8)启动ADC模块;
(9)软件使能触发ADC转换。
这里需要注意的是,在使用ADC+DMA功能时,如果在启动ADC转换之后使能DMA,ADC采样数据可能会出现异常。因此建议先配置ADC及DMA相关参数,最后启动ADC转换。
代码实现
受篇幅限制,这里只介绍核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本课程配套的例程。相关核心代码实现如下:
(1)ADC宏定义
#defineTEMP_NOFCHANEL3 /*=====================通道1IO======================*/ //PB0ADCIO宏定义,可用杜邦线接3V3或者GND来实验 #defineTEMP_ADC_GPIO_PORT1GPIOB #defineTEMP_ADC_GPIO_PIN1GPIO_Pin_0 #defineTEMP_ADC_GPIO_CLK1RCC_AHB1Periph_GPIOB #defineTEMP_ADC_CHANNEL1ADC_Channel_8 /*=====================通道2IO======================*/ //PB1ADCIO宏定义,可用杜邦线接3V3或者GND来实验 #defineTEMP_ADC_GPIO_PORT2GPIOB #defineTEMP_ADC_GPIO_PIN2GPIO_Pin_1 #defineTEMP_ADC_GPIO_CLK2RCC_AHB1Periph_GPIOB #defineTEMP_ADC_CHANNEL2ADC_Channel_9 /*=====================通道3IO======================*/ //PA6ADCIO宏定义,可用杜邦线接3V3或者GND来实验 #defineTEMP_ADC_GPIO_PORT3GPIOA #defineTEMP_ADC_GPIO_PIN3GPIO_Pin_6 #defineTEMP_ADC_GPIO_CLK3RCC_AHB1Periph_GPIOA #defineTEMP_ADC_CHANNEL3ADC_Channel_6 //ADC序号宏定义 #defineTEMP_ADCADC1 #defineTEMP_ADC_CLKRCC_APB2Periph_ADC1 //ADCDR寄存器宏定义,ADC转换后的数字值则存放在这里 #defineTEMP_ADC_DR_ADDR((u32)ADC1+0x4c) //ADCDMA通道宏定义,使用DMA传输 #defineTEMP_ADC_DMA_CLKRCC_AHB1Periph_DMA2 #defineTEMP_ADC_DMA_CHANNELDMA_Channel_0 #defineTEMP_ADC_DMA_STREAMDMA2_Stream0
定义多个通道进行多通道ADC实验,并且定义DMA相关配置。
(2)ADC GPIO初始化
staticvoidTemp_ADC_GPIO_Config(void) { GPIO_InitTypeDefGPIO_InitStructure; /*=====================通道1======================*/ RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK1,ENABLE);//使能GPIO时钟 GPIO_InitStructure.GPIO_Pin=TEMP_ADC_GPIO_PIN1;//配置IO GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不上拉不下拉 GPIO_Init(TEMP_ADC_GPIO_PORT1, GPIO_InitStructure); /*=====================通道2======================*/ RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK2,ENABLE);//使能GPIO时钟 GPIO_InitStructure.GPIO_Pin=TEMP_ADC_GPIO_PIN2;//配置IO GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不上拉不下拉 GPIO_Init(TEMP_ADC_GPIO_PORT2, GPIO_InitStructure); /*=====================通道3=======================*/ RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK3,ENABLE);//使能GPIO时钟 GPIO_InitStructure.GPIO_Pin=TEMP_ADC_GPIO_PIN3;//配置IO GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不上拉不下拉 GPIO_Init(TEMP_ADC_GPIO_PORT3, GPIO_InitStructure); }
使用到GPIO时候都必须开启对应的GPIO时钟,GPIO用于AD转换功能必须配置为模拟输入模式。
(3)配置ADC工作模式
staticvoidTemp_ADC_Mode_Config(void) { DMA_InitTypeDefDMA_InitStructure; ADC_InitTypeDefADC_InitStructure; ADC_CommonInitTypeDefADC_CommonInitStructure; //--------------DMAInit结构体参数初始化------------- //ADC1使用DMA2,数据流0,通道0, RCC_AHB1PeriphClockCmd(TEMP_ADC_DMA_CLK,ENABLE);//开启DMA时钟 DMA_InitStructure.DMA_PeripheralBaseAddr=TEMP_ADC_DR_ADDR;//外设基址为:ADC数据寄存器地址 DMA_InitStructure.DMA_Memory0BaseAddr=(u32)ADC_ConvertedValue;//AD值存储地址 DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralToMemory;//数据传输方向为外设到存储器 DMA_InitStructure.DMA_BufferSize=TEMP_NOFCHANEL;//缓冲区大小,指一次传输的数据量 DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设寄存器只有一个,地址不递增 DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//存储器地址固定 DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//外设数据大小为半字 DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//存储器数据大小也为半字 DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;//循环传输模式 DMA_InitStructure.DMA_Priority=DMA_Priority_High;//DMA传输通道优先级为高 DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Disable;//禁止DMAFIFO,使用直连模式 DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_HalfFull;//FIFO大小,FIFO禁止时不用配置 DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single; DMA_InitStructure.DMA_Channel=TEMP_ADC_DMA_CHANNEL;//选择DMA通道,通道存在于流中 DMA_Init(TEMP_ADC_DMA_STREAM, DMA_InitStructure);//初始化DMA流, DMA_Cmd(TEMP_ADC_DMA_STREAM,ENABLE);//使能DMA流 RCC_APB2PeriphClockCmd(TEMP_ADC_CLK,ENABLE);//开启ADC时钟 //-------------ADCCommon结构体参数初始化---------------- ADC_CommonInitStructure.ADC_Mode=ADC_Mode_Independent;//独立ADC模式 ADC_CommonInitStructure.ADC_Prescaler=ADC_Prescaler_Div4;//时钟为fpclkx分频 ADC_CommonInitStructure.ADC_DMAAccessMode=ADC_DMAAccessMode_Disabled;//禁止DMA直接访问模式 ADC_CommonInitStructure.ADC_TwoSamplingDelay=ADC_TwoSamplingDelay_20Cycles;//采样时间间隔 ADC_CommonInit( ADC_CommonInitStructure); //-------------------ADCInit结构体参数初始化-------------------------- ADC_StructInit( ADC_InitStructure); ADC_InitStructure.ADC_Resolution=ADC_Resolution_12b;//ADC分辨率 ADC_InitStructure.ADC_ScanConvMode=ENABLE;//扫描模式,多通道采集需要 ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//连续转换 ADC_InitStructure.ADC_ExternalTrigConvEdge=ADC_ExternalTrigConvEdge_None;//禁止外部边沿触发 ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T1_CC1;//外部触发通道,使用软件触发时此值随便赋值即可 ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据右对齐ADC_InitStructure.ADC_NbrOfConversion=TEMP_NOFCHANEL;//转换通道3个 ADC_Init(TEMP_ADC, ADC_InitStructure); //配置ADC通道转换顺序和采样时间周期 ADC_RegularChannelConfig(TEMP_ADC,TEMP_ADC_CHANNEL1,1,ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(TEMP_ADC,TEMP_ADC_CHANNEL2,2,ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(TEMP_ADC,TEMP_ADC_CHANNEL3,3,ADC_SampleTime_3Cycles); ADC_DMARequestAfterLastTransferCmd(TEMP_ADC,ENABLE);//使能DMA请求 ADC_DMACmd(TEMP_ADC,ENABLE);//使能ADCDMA ADC_Cmd(TEMP_ADC,ENABLE);//使能ADC ADC_SoftwareStartConv(TEMP_ADC);//开始ADC转换,软件触发 }
首先,使用DMA_InitTypeDef定义了DMA初始化类型变量,另外使用ADC_InitTypeDef和ADC_CommonInitTypeDef结构体分别定义一个ADC初始化和ADC通用类型变量。
调用RCC_APB2PeriphClockCmd()开启ADC时钟以及RCC_AHB1PeriphClockCmd()开启DMA时钟。
对DMA进行必要的配置。首先设置外设基地址就是ADC的规则数据寄存器地址;存储器的地址就是指定的数据存储区空间,ADC_ConvertedValue是我们定义的一个全局数组名,它是一个无符号16位含有3个元素的整数数组;ADC规则转换对应只有一个数据寄存器,所以地址不能递增,而定义的存储区是专门用来存放不同通道数据的,所以需要自动地址递增。ADC的规则数据寄存器只有低16位有效,实际存放的数据只有12位而已,所以设置数据大小为半字大小。ADC配置为连续转换模式,DMA也设置为循环传输模式。设置好DMA相关参数后就使能DMA的ADC通道。
接下来使用ADC_CommonInitTypeDef结构体变量ADC_CommonInitStructure来配置ADC为独立模式、分频系数4、20个周期的采样延迟,并调用ADC_CommonInit函数完成ADC通用工作环境配置。
使用ADC_InitTypeDef结构体变量ADC_InitStructure来配置ADC1为12位分辨率、使能扫描模式、启动连续转换、使用内部软件触发无需外部触发事件、使用右对齐数据格式、转换通道为3,并调用ADC_Init函数完成ADC1工作环境配置。
ADC_RegularChannelConfifig函数用来绑定ADC通道转换顺序和采样时间。分别绑定3个ADC通道引脚并设置相应的转换顺序。
ADC_DMARequestAfterLastTransferCmd函数控制是否使能ADC的DMA请求,如果使能请求,并调用ADC_DMACmd函数使能DMA,则在ADC转换完成后就请求DMA实现数据传输。ADC_Cmd函数控制ADC转换启动和停止。
最后使用软件触发调用ADC_SoftwareStartConvCmd函数进行使能配置。
(4)Main程序
/**主函数*/ intmain(void) { Debug_USART_Config(); Temp_Init(); while(1) { ADC_ConvertedValueLocal[0]=(float)ADC_ConvertedValue[0]/4096*(float)3.3; ADC_ConvertedValueLocal[1]=(float)ADC_ConvertedValue[1]/4096*(float)3.3; ADC_ConvertedValueLocal[2]=(float)ADC_ConvertedValue[2]/4096*(float)3.3; printf("rnPB0value=%fVrn",ADC_ConvertedValueLocal[0]); printf("rnPB1value=%fVrn",ADC_ConvertedValueLocal[1]); printf("rnPA6value=%fVrn",ADC_ConvertedValueLocal[2]); Delay(0xffffff); } }
主函数先调用Debug_USART_Config函数配置调试串口相关参数,接下来调用Temp_Init函数进行ADC初始化配置并启动ADC。配置了DMA数据传输,它会自动把ADC转换完成后数据保存到数组ADC_ConvertedValue内,我们只要使用数组就可以了。经过简单地计算就可以得到每个通道对应的实际电压。
来源:中科芯MCU
-
存储器
+关注
关注
38文章
7485浏览量
163787 -
adc
+关注
关注
98文章
6497浏览量
544505 -
dma
+关注
关注
3文章
561浏览量
100558 -
电压采集
+关注
关注
2文章
21浏览量
14225
发布评论请先 登录
相关推荐
评论