在上一讲单通道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目标引脚为模拟输入模式;
(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宏定义
#define TEMP_NOFCHANEL 3 /*=====================通道1 IO======================*/ // PB0 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验 #define TEMP_ADC_GPIO_PORT1 GPIOB #define TEMP_ADC_GPIO_PIN1 GPIO_Pin_0 #define TEMP_ADC_GPIO_CLK1 RCC_AHB1Periph_GPIOB #define TEMP_ADC_CHANNEL1 ADC_Channel_8 /*=====================通道2 IO ======================*/ // PB1 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验 #define TEMP_ADC_GPIO_PORT2 GPIOB #define TEMP_ADC_GPIO_PIN2 GPIO_Pin_1 #define TEMP_ADC_GPIO_CLK2 RCC_AHB1Periph_GPIOB #define TEMP_ADC_CHANNEL2 ADC_Channel_9 /*=====================通道3 IO ======================*/ // PA6 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验 #define TEMP_ADC_GPIO_PORT3 GPIOA #define TEMP_ADC_GPIO_PIN3 GPIO_Pin_6 #define TEMP_ADC_GPIO_CLK3 RCC_AHB1Periph_GPIOA #define TEMP_ADC_CHANNEL3 ADC_Channel_6 // ADC 序号宏定义 #define TEMP_ADC ADC1 #define TEMP_ADC_CLK RCC_APB2Periph_ADC1 // ADC DR寄存器宏定义,ADC转换后的数字值则存放在这里 #define TEMP_ADC_DR_ADDR ((u32)ADC1+0x4c) // ADC DMA 通道宏定义,使用DMA传输 #define TEMP_ADC_DMA_CLK RCC_AHB1Periph_DMA2 #define TEMP_ADC_DMA_CHANNEL DMA_Channel_0 #define TEMP_ADC_DMA_STREAM DMA2_Stream0
定义多个通道进行多通道ADC实验,并且定义DMA相关配置。
(2)ADC GPIO初始化
static void Temp_ADC_GPIO_Config(void) { GPIO_InitTypeDef GPIO_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工作模式
static void Temp_ADC_Mode_Config(void) { DMA_InitTypeDef DMA_InitStructure; ADC_InitTypeDef ADC_InitStructure; ADC_CommonInitTypeDef ADC_CommonInitStructure; // --------------DMA Init 结构体参数初始化------------- // 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; // 禁止DMA FIFO ,使用直连模式 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时钟 // -------------ADC Common 结构体 参数初始化---------------- ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;// 独立ADC模式 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;// 时钟为fpclk x分频 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; // 禁止DMA直接访问模式 ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles; // 采样时间间隔 ADC_CommonInit(&ADC_CommonInitStructure); // -------------------ADC Init 结构体 参数初始化-------------------------- 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);// 使能ADC DMA 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程序
/** 主函数 */ int main(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("\r\n PB0 value = %f V \r\n",ADC_ConvertedValueLocal[0]); printf("\r\n PB1 value = %f V \r\n",ADC_ConvertedValueLocal[1]); printf("\r\n PA6 value = %f V \r\n",ADC_ConvertedValueLocal[2]); Delay(0xffffff); } }
主函数先调用Debug_USART_Config函数配置调试串口相关参数,接下来调用Temp_Init函数进行ADC初始化配置并启动ADC。配置了DMA数据传输,它会自动把ADC转换完成后数据保存到数组ADC_ConvertedValue内,我们只要使用数组就可以了。经过简单地计算就可以得到每个通道对应的实际电压。
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。