引言
本应用笔记提供如何在APM32F4xx系列上配置和应用ADC接口的指南,包括接口框图、代码实现和应用方法。
APM32F4xx微控制器内置最多三个12位的逐次逼近型ADC,三个ADC共享最多21个外部输入通道和3个内部通道,并提供自校准功能。内部通道分别提供测量芯片内置温度传感器电压、参考电压以及备份电源电压的功能。各个A/D转换通道支持单次、连续、扫描和间断的转换方式。ADC转换的结果可以设置成左对齐或右对齐来存储在16位的数据寄存器中,并且支持DMA访问和设置模拟看门狗
ADC简介
模数转换器,ADC(Analog to Digital Converter),是一个将模拟信号转换为数字信号的器件(电路),例如将温度、湿度、压力、位置等信息转换为数字信号。但由于数字信号本身不具有实际意义,仅仅表示一个相对大小。所以ADC都需要一个参考模拟量(REF)作为转换的标准。
图 1 ADC框图
ADC分类
ADC按工作原理可以分成直接ADC和间接ADC。主要有以下几种:
并联比较型ADC;
逐次逼近型ADC;
双积分型ADC。
其中逐次逼近型ADC是一种直接ADC。由于其采样速率中等,分辨率中等,且位数较多时使用元器件较少等原因(成本较低),所以被广泛应用于集成ADC中。
图 2 ADC分类
A/D转换原理
A/D转换的作用是将时间、幅值连续的模拟信号转换为时间、幅值离散的数字信号。所以,A/D转换一般要经过采样、保持、量化及编码四个过程。
A/D转换步骤
采样和保持
采样是指在时间上将模拟信号离散化,即是将时间上连续的信号转为一系列等时间间隔的信号离散序列。其中离散信号脉冲的幅度取决于输入模拟量。
量化和编码
量化是用有限个幅度值近似原来连续变化的幅度值,把模拟信号的连续幅度变为有限数量的有一定间隔的离散值。而编码则是按照一定的规律,把量化后的值用二进制数字表示。下图列举了12bits ADC的FSR为3.3V时的量化到编码的过程。其中:
N:分辨率,用于对输入进行量化的位数。理论上,n位输出的ADC能区分2n个不同等级的模拟输入电压。如下图所示,能分辨的最小输入电压步长LSB = FSR / 2n = 806uV;
FSR: Full-Scale Range,满量程;
LSB: Least Significant Bit,最低有效位;
MSB: Most Significant Bit,最高有效位。
图 3 量化和编码
转换时间
转换时间是指ADC从转换控制信号触发开始,到输出端得到稳定的数字信号所经过的时间。该时间受ADC类型、ADC时钟和外部输入阻抗等因素影响。
APM32中的ADC
APM32中的ADC是逐次逼近型ADC(Successive Approximation ADC),是逐个产生比较电压VREF,并逐次与输入电压分别比较,以逐渐逼近的方式进行A/D转换的。
SAR ADC的转换原理是把输入的模拟信号按规定的时间间隔采样(采样),并与一系列标准的数字信号相比较,数字信号逐次收敛,直至两种信号相等为止(量化),最后输出代表此信号的二进制数(编码)。
ADC的结构
结构上主要包括采样保持电路(S/H),比较器(COMPARATOR,COMP),SAR逻辑控制电路、时钟(CLOCK)和时序(TIMING)控制电路及DAC电路。
图 4 ADC结构
S/H电路
被采样的脉冲宽度一般是很短的,在下一个采样脉冲到来之前,要暂时保持所采得的样值脉冲幅度,以便进行后续转换。所以,在采样电路之后要加保持电路。下图是一个简单的采样保持电路配置框图。
图 5 S/H电路
DAC电路
大多SAR ADC的DAC都使用电容式DAC来提供内在的跟踪/保持功能。电容式DAC是采用电荷再分配原理来产生模拟输出电压的。电容式DAC由N个具有二进制权重值的电容器阵列再加上一个“虚拟LSB”电容器组成。
转换步骤
转换步骤数等于 ADC的分辨率,比如10bits ADC就有10个转换步骤,每个 ADC 时钟产生一个数据位。以下步骤以10bits ADC为例。采样和保持的状态,可以参考上述的S/H等效电路来理解。下面着重看下量化和编码的状态。
量化和编码状态
该状态下,每个 ADCCLK 执行一个步骤,每一步完成后 ADC 输出一位数。采用二分法进行逐次逼近到 ADC 的精度(位数)。整个转换过程如下图所示。
图 6 量化和编码状态
转换例子
比如2.5V输入到以3.3V为参考电压的SAR ADC中,则转换过程如下所示。
第一个逼近步骤时,MSB先设置为1。DAC以1/2 REF去和VIN比较,若VIN > 1/2 REF,则保持MSB = 1(反之则MSB = 0)。等待下一个ADCCLK,执行下一步。
图 7 ADC转换逼近步骤1
第二个逼近步骤时,MSB往后移动1个bit,再以3/4 REF去和VIN进行比较,若VIN > 3/4 REF,则保持MSB = 1(反之则MSB = 0)。等待下一个ADCCLK,执行下一步,一直到所有bit确定,然后输出编码值。
图 8 ADC转换逼近步骤2
转换时间
APM32F4xx中的ADC转换时间 = 采样周期 + 转换周期。
采样周期
由采样周期设置决定,要注意的是,该值需要和外部电路的输入阻抗匹配。从而保证在采用阶段,采样保持电容有足够的时间充电。
转换周期
该值取决于ADC的转换精度,APM32F4xx的SAR ADC默认为12bits,可配置为12、10、8、6bits。
表格 1 ADC精度和转换周期关系
序号 | ADC精度 | 转换周期 |
1 | 12 bits | 12 x ADCCLK |
2 | 10 bits | 10 x ADCCLK |
3 | 8 bits | 8 x ADCCLK |
4 | 6 bits | 6 x ADCCLK |
转换数值
ADC 转换的数值 = (VIN x 2n) / VREF,n为ADC的分辨率。以上述12bits的ADC为例,则
ADC 转换的数值 = (VIN x 4096) / VREF。
ADC的配置和应用
转换周期
输入通道
MINI Board已经将部分ADC的通道通过排针接出,可以根据设计需求使用相关IO。ADC采样容易受外接干扰,使用时注意引脚间的抗干扰设计,以及避免ADC引脚和其他功能电路共用的情况。
电压输入范围
ADC的电压输入范围为VREF- ~ VREF+,MINI板上的VSSA和VREF-接入GND,而VDDA和VREF+接入VDD,所以在MINI板上的ADC电压输入范围是0V ~ 3.3V。
软件设计
软件设计上只讲解关键的配置,部分查询和逻辑代码未设计,详细可直接参考本应用说明配套的例程。
ADC初始化结构体
ADC_Config_T结构体定义在APM32F4xx_adc.h文件中,具体定义如下:
/** * @brief ADC configuration Mode */ typedef struct { ADC_RESOLUTION_T resolution; uint8_t scanConvMode; uint8_t continuousConvMode; ADC_EXT_TRIG_EDGE_T extTrigEdge; ADC_EXT_TRIG_CONV_T extTrigConv; ADC_DATA_ALIGN_T dataAlign; uint8_t nbrOfChannel; } ADC_Config_T; |
结构体中的参数含义:
resolution:用于配置ADC的分辨率,可以配ADC的分辨率为12bit、10bit、8bit和6bit。如本应用说明SAR ADC转换原理所述,ADC的分辨率配置越高,相对的转换时间也越长;
scanConvMode:用于配置是否使扫描模式,一般单通道A/D转换应用时配置为DISABLE,多通道A/D转换应用时配置为ENABLE;
continuousConvMode:用于配置是单次转换,还是启用自动连续转换模式;
extTrigEdge:用于配置外部触发的极性,如果未使能外部触发,可配置为ADC_EXT_TRIG_EDGE_NONE;
extTrigConv:用于配置外部触发源;
dataAlign:用于配置ADC转换结果的数据对齐方式,一般按照习惯,我们配置为右对齐模式;
nbrOfChannel:用于配置A/D转换通道的数目。
ADC通用初始化结构体
ADC_ CommonConfig _T结构体定义在APM32F4xx_adc.h文件中,具体定义如下:
/** * @brief ADC Common Init structure definition */ typedef struct { ADC_MODE_T mode; ADC_PRESCALER_T prescaler; ADC_ACCESS_MODE_T accessMode; ADC_TWO_SAMPLING_T twoSampling; } ADC_CommonConfig_T; |
结构体中的参数含义:
mode:用于配置ADC的工作模式,有独立模式、双重模式和三重模式的配置项可供选择;
prescaler:用于配置ADC时钟的分频系数,ADC时钟由PCLK2提供。PCLK2除以prescaler就是ADC的时钟;
accessMode:用于配置DMA模式;
twoSampling:用于配置两个采样阶段间的延迟。
单通道转换软件设计
以“ADC_ContinuousConversion”例程为例。
配置ADC
开启GPIO时钟后,配置GPIO为模拟输入模式。
/*! * @brief ADC Init * * @param None * * @retval None */ void ADC_Init(void) { GPIO_Config_T gpioConfig; ADC_Config_T adcConfig; /** Enable GPIOA clock */ RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA); /** ADC channel 0 configuration */ GPIO_ConfigStructInit(&gpioConfig); gpioConfig.mode = GPIO_MODE_AN; gpioConfig.pupd = GPIO_PUPD_NOPULL; gpioConfig.pin = GPIO_PIN_0; GPIO_Config(GPIOA, &gpioConfig); |
配置ADC工作方式,开启连续转换模式。
/** Enable ADC clock */ RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1); /** ADC configuration */ ADC_Reset(); ADC_ConfigStructInit(&adcConfig); adcConfig.resolution = ADC_RESOLUTION_12BIT; adcConfig.continuousConvMode = ENABLE; adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT; adcConfig.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE; adcConfig.scanConvMode = DISABLE; ADC_Config(ADC1, &adcConfig); |
配置开启ADC中断,使能ADC并软件触发转换。
/** ADC channel 0 Convert configuration */ ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_0, 1, ADC_SAMPLETIME_112CYCLES); /** Enable complete conversion interupt */ ADC_EnableInterrupt(ADC1, ADC_INT_EOC); /** NVIC configuration */ NVIC_EnableIRQRequest(ADC_IRQn, 1, 1); /** Enable ADC */ ADC_Enable(ADC1); /** ADC start conversion */ ADC_SoftwareStartConv(ADC1); } |
ADC中断服务函数
检测到转换完成后,回调该中断服务函数。并读取此时的ADC转换值,然后将其转换成对应电压值,该值受量化误差和硬件抗干扰能力所影响。这里转换出来的电压单位是mV。
/*! * @brief ADC interrupt service routine * * @param None * * @retval None */ void ADC_Isr(void) { uint16_t adcData = 0; uint16_t voltage = 0; if (ADC_ReadStatusFlag(ADC1, ADC_FLAG_EOC)) { ADC_ClearStatusFlag(ADC1, ADC_FLAG_EOC); adcData = ADC_ReadConversionValue(ADC1); voltage = (adcData * 3300) / 4095; printf("\r\n voltage : %d mV\r\n", voltage); } } |
多通道扫描软件设计
以“ADC_MultiChannelScan”例程为例。
定义公共信息
这里定义了本样例采样的通道数为3个通道,并定义了将用于DMA存储ADC各通道扫描数据的数组adcData[ADC_CH_SIZE]。另外还宏定义了ADC规则数据寄存器的地址为ADC_DR_ADDR。以上这些信息都会用于后续配置和应用中。
/** save adc data*/ #define ADC_CH_SIZE 3 #define ADC_DR_ADDR ((uint32_t)ADC1_BASE + 0x4C) uint16_t adcData[ADC_CH_SIZE]; |
配置DMA
将ADC的规则寄存器地址设置为DMA访问的寄存器地址,设置寄存器递增并规定buffer的大小为3,最后开启循环模式。
DMA的不同通道和数据流规定了其所属的外设,使用时要注意。
/*! * @brief DMA Init * * @param None * * @retval None */ void DMA_Init(void) { DMA_Config_T dmaConfig;
RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_DMA2);
dmaConfig.peripheralBaseAddr = ADC_DR_ADDR; dmaConfig.memoryBaseAddr = (uint32_t)&adcData; dmaConfig.dir = DMA_DIR_PERIPHERALTOMEMORY; dmaConfig.bufferSize = ADC_CH_SIZE; dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE; dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE; dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD; dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD; dmaConfig.loopMode = DMA_MODE_CIRCULAR; |
dmaConfig.priority = DMA_PRIORITY_HIGH; dmaConfig.fifoMode = DMA_FIFOMODE_DISABLE; dmaConfig.fifoThreshold = DMA_FIFOTHRESHOLD_HALFFULL; dmaConfig.memoryBurst = DMA_MEMORYBURST_SINGLE; dmaConfig.peripheralBurst = DMA_PERIPHERALBURST_SINGLE; dmaConfig.channel = DMA_CHANNEL_0; DMA_Config(DMA2_Stream0,&dmaConfig);
DMA_Enable(DMA2_Stream0); } |
配置ADC
和单通道转换配置顺序一样,首先将要扫描的三个通道开启相应时钟和配置为模拟输入模式。
/*! * @brief ADC Init * * @param None * * @retval None */ void ADC_Init(void) { GPIO_Config_T gpioConfig; ADC_Config_T adcConfig; ADC_CommonConfig_T adcCommonConfig; /** Enable GPIOA clock */ RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA); /** ADC channel 0 configuration */ GPIO_ConfigStructInit(&gpioConfig); gpioConfig.mode = GPIO_MODE_AN; gpioConfig.pupd = GPIO_PUPD_NOPULL; gpioConfig.pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2; GPIO_Config(GPIOA, &gpioConfig); |
接着是ADC的通用配置。
/** Enable ADC clock */ RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1); /** ADC configuration */ ADC_Reset();
adcCommonConfig.mode = ADC_MODE_INDEPENDENT; adcCommonConfig.prescaler = ADC_PRESCALER_DIV2; adcCommonConfig.accessMode = ADC_ACCESS_MODE_DISABLED; adcCommonConfig.twoSampling = ADC_TWO_SAMPLING_20CYCLES; ADC_CommonConfig(&adcCommonConfig); |
然后是ADC工作模式的配置,这里配置为连续扫描模式,并设置转换通道数为3个。
ADC_ConfigStructInit(&adcConfig); adcConfig.resolution = ADC_RESOLUTION_12BIT; adcConfig.scanConvMode = ENABLE; adcConfig.continuousConvMode = ENABLE; adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT; adcConfig.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE; adcConfig.extTrigConv = ADC_EXT_TRIG_CONV_TMR1_CC1; adcConfig.nbrOfChannel = ADC_CH_SIZE; ADC_Config(ADC1, &adcConfig); |
配置各通道的转换顺序及采样周期。
/** ADC channel Convert configuration */ ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_0, ADC_SAMPLETIME_480CYCLES); ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_1, 2, ADC_SAMPLETIME_480CYCLES); ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_2, 3, ADC_SAMPLETIME_480CYCLES); |
最后开启DMA、使能ADC并触发转换。到这里多通道扫描的配置就完成了,接着直接轮询ADC转换值存储的数组即可获得各通道的扫描值。
/** Config DMA*/ DMA_Init();
/** Enable ADC DMA Request*/ ADC_EnableDMARequest(ADC1);
/** Enable ADC DMA*/ ADC_EnableDMA(ADC1); /** Enable ADC */ ADC_Enable(ADC1); /** ADC start conversion */ ADC_SoftwareStartConv(ADC1); } |
提高采样精度的硬件方法
1. 保证参考电压噪声最小化;
2. IO引脚串扰最小化;
3. 加入屏蔽减少EMI;
4. PCB将模拟和数字分开布局和铺设。
提高采样精度的软件方法
采样平均
在硬件抗干扰能力不足的情况下,我们可以牺牲采用速率,采用软件方法进行滤波,从而得到更加稳定的采样值。
数字信号滤波
可通过数字低通、高通等滤波器进行软件滤波。比如知道被测信号中的噪声来自50 Hz供电线,通过适当的数字滤波,可以只抑制50 Hz频率并传输无此噪声的数据信号。
ADC软件校准
如果采样值较为固定,但离目标值相差大,还可以利用线性拟合(线性校准曲线)的方式让采样值更接近目标值。
采样
首先基于标准源表采样足够多的点,如下表(点数越多,拟合越准确)。
表格 2 采样表
序号 | 采样值(mV) | 目标值(mV) |
1 | 96.7 | 100 |
2 | 194.5 | 200 |
3 | 498.8 | 500 |
4 | 796 | 800 |
5 | 1495 | 1500 |
拟合
在数学工具(Matlab或Excel等)中做线性拟合(线性、多项式、指数皆可),得到校准公式和相关系数R值。
图 9 拟合
校准
用拟合步骤中得到的校准公式对采样值进行校准。
表格 3采样表
序号 | 采样值(mV) | 目标值(mV) | 校准值(mV) |
1 | 96.7 | 100 | 100.1 |
2 | 194.5 | 200 | 197.9 |
3 | 498.8 | 500 | 502.5 |
4 | 796 | 800 | 799.9 |
5 | 1495 | 1500 | 1499.4 |