01 ADC简介
模数转换器,ADC(Analog to Digital Converter),是一个将模拟信号转换为数字信号的器件(电路),例如将温度、湿度、压力、位置等信息转换为数字信号。但由于数字信号本身不具有实际意义,仅仅表示一个相对大小。所以ADC都需要一个参考模拟量(REF)作为转换的标准。
ADC分类
ADC按工作原理可以分成直接ADC和间接ADC。主要有以下几种:
并联比较型ADC;
逐次逼近型ADC;
双积分型ADC。
其中逐次逼近型ADC是一种直接ADC。由于其采样速率中等,分辨率中等,且位数较多时使用元器件较少等原因(成本较低),所以被广泛应用于集成ADC中。
AD转换原理与步骤
A/D转换的作用是将时间、幅值连续的模拟信号转换为时间、幅值离散的数字信号。所以,A/D转换一般要经过采样、保持、量化及编码四个过程。
采样和保持(Sampling and Holding)
采样是指在时间上将模拟信号离散化,即是将时间上连续的信号转为一系列等时间间隔的信号离散序列。其中离散信号脉冲的幅度取决于输入模拟量。下图列举了一个模拟信号从采样到保持的过程。
采样需要满足采样原理: 采样频率大于模拟信号中最高频率成分的两倍时,采样值才能不失真的反映原来模拟信号。
量化和编码(Quantizing and Encoding)
量化是用有限个幅度值近似原来连续变化的幅度值,把模拟信号的连续幅度变为有限数量的有一定间隔的离散值。而编码则是按照一定的规律,把量化后的值用二进制数字表示。下图列举了12bits ADCFSR为3V时的量化到编码的过程
n:分辨率,用于对输入进行量化的位数
FSR: Full-Scale Range,满量程
LSB: Least Significant Bit,最低有效位
MSB: Most Significant Bit,最高有效位
分辨率
理论上,n位输出的ADC能区分2^n 个不同等级的模拟输入电压。如上图所示,能分辨的最小输入电压步长LSB = FSR / 2^n = 732uV。
量化误差
量化误差是由于量化过程引入的误差,通常是以输出误差的最大值形式标出。表示ADC实际输出的数字量和理论输出数字量之间的误差。使用“四舍五入法”时,ADC 转换器的量化误差是 ±½ LSB。
转换时间
转换时间是指ADC从转换控制信号触发开始,到输出端得到稳定的数字信号所经过的时间。该时间受ADC类型、ADC时钟和外部输入阻抗等因素影响。
02 STM32中的SAR ADC
STM32中的ADC是逐次逼近型ADC(Successive Approximation ADC),是逐个产生比较电压VREF,并逐次与输入电压分别比较,以逐渐逼近的方式进行A/D转换的。
SAR ADC的转换原理是把输入的模拟信号按规定的时间间隔采样(采样),并与一系列标准的数字信号相比较,数字信号逐次收敛,直至两种信号相等为止(量化),最后输出代表此信号的二进制数(编码)。
结构
结构上主要包括采样保持电路(S/H),比较器(COMPARATOR,COMP),SAR逻辑控制电路、时钟(CLOCK)和时序(TIMING)控制电路及DAC电路。
S/H电路
被采样的脉冲宽度一般是很短的,在下一个采样脉冲到来之前,要暂时保持所采得的样值脉冲幅度,以便进行后续转换。所以,在采样电路之后要加保持电路。下图是一个简单的采样保持电路配置框图。
DAC电路
大多SAR ADC的DAC都使用电容式DAC来提供内在的跟踪/保持功能。电容式DAC是采用电荷再分配原理来产生模拟输出电压的。电容式DAC由N个具有二进制权重值的电容器阵列再加上一个“虚拟LSB”电容器组成。
电容器阵列容量总量要等于2C。
转换步骤
转换步骤数等于 ADC的分辨率,比如10bits ADC就有10个转换步骤,每个 ADC 时钟产生一个数据位。以下步骤以10bits ADC为例。
采样状态
该状态下,电容充电至电压VIN。Sa切换至VIN,采样期间Sb开关闭合。
保持状态
该状态下,输入断开,电容保持输入电压。Sb开关打开,然后S1-S11切换至接地,且Sa切换至VREF。
转换(量化和编码)状态
该状态下,每个 ADCCLK 执行一个步骤,每一步完成后 ADC 输出一位数。采用二分法进行逐次逼近到 ADC 的精度(位数)。整个转换过程如下图所示。
整个逐次逼近的步骤如下面的二叉树所示。
例子
比如2.5V输入到以3.3V为参考电压的SAR ADC中,则转换过程如下所示。
第一个逼近步骤时,MSB先设置为1。DAC以1/2 REF去和VIN比较,若VIN > 1/2 REF,则保持MSB = 1(反之则MSB = 0)。等待下一个ADCCLK,执行下一步。
第二个逼近步骤时,MSB往后移动1个bit,再以3/4 REF去和VIN进行比较,若VIN > 3/4 REF,则保持MSB = 1(反之则MSB = 0)。等待下一个ADCCLK,执行下一步,一直到所有bit确定,然后输出编码值。
转换时间
STM32中的ADC转换时间 = 采样周期 + 转换周期。
采样周期
由采样周期设置决定,要注意的是,该值需要和外部电路的输入阻抗匹配。从而保证在采用阶段,采样保持电容有足够的时间充电。
如前述采样保持电路所示,其中有一个采样保持电容C。
转换周期
该值取决于ADC的转换精度,STM32F4xx的SAR ADC默认为12bits,可配置为10、8、6bits。
NO | 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 2^n) / VREF,n为ADC的分辨率。以上述10bits的ADC为例,则ADC 转换的数值 = (VIN x 1024) / VREF。
03 ADC的配置和应用
单通道转换
定义公共信息
/** 定义ADC GPIO*/
#define ADC_IN0_GPIO_PORT GPIOA
#define ADC_IN0_GPIO_PIN GPIO_Pin_0
#define ADC_IN0_GPIO_CLK RCC_AHB1Periph_GPIOA
/** 定义ADC 相关信息*/
#define DEBUG_ADC ADC1
#define DEBUG_ADC_BASE ADC1_BASE
#define DEBUG_ADC_CLK RCC_APB2Periph_ADC1
#define DEBUG_ADC_CHANNEL ADC_Channel_0
#define DEBUG_ADC_DR_ADDR ((uint32_t)DEBUG_ADC_BASE + 0x4C)
/** 定义ADC DMA相关信息*/
#define DEBUG_ADC_DMA_CLK RCC_AHB1Periph_DMA2
#define DEBUG_ADC_DMA_CHANNEL DMA_Channel_0
#define DEBUG_ADC_DMA_STREAM DMA2_Stream0
/** 定义数据存储变量*/
uint16_t adcConvertedValue;
配置ADC和DMA
这里默认APB2的CLK PCLK2为60MHz。那么当设置ADC分辨率为12bits,ADCCLK = PCLK2 / 2 = 30MHz时。结合前述“转换时间”章节中讲到的计算方式,可以得知单次
ADC转换时间 = 采样周期 + 转换周期
= 3 x ADCCLK + 12 x ADCCLK
= 15 ADC CLK
= 15 / 30MHz
= 0.5 us
每次ADC转换间隔为20 x ADCCLK。
/** 初始化ADC IO*/
void ADC_GPIOConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(ADC_IN0_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = ADC_IN0_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(ADC_IN0_GPIO_PORT, &GPIO_InitStructure);
}
/** 初始化ADC相应的DMA*/
void ADC_DMAConfig(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(DEBUG_ADC_DMA_CLK, ENABLE);
/** ADC 数据寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = DEBUG_ADC_DR_ADDR;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&adcConvertedValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
/** ADC DR数据大小为半字,即两个字节*/
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_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_InitStructure.DMA_Channel = DEBUG_ADC_DMA_CHANNEL;
DMA_Init(DEBUG_ADC_DMA_STREAM, &DMA_InitStructure);
DMA_Cmd(DEBUG_ADC_DMA_STREAM, ENABLE);
}
/** 初始化ADC*/
void ADC_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
RCC_APB2PeriphClockCmd(DEBUG_ADC_CLK , ENABLE);
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
/** 设置ADCCLK为PCLK2的2分频*/
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
/** 采样时间 间隔*/
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
ADC_StructInit(&ADC_InitStructure);
/** ADC 分辨率*/
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
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;
/** 转换通道 1个*/
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(DEBUG_ADC, &ADC_InitStructure);
/** 配置 ADC 通道转换顺序为1,采样时间为3个时钟周期*/
ADC_RegularChannelConfig(DEBUG_ADC, DEBUG_ADC_CHANNEL, 1, ADC_SampleTime_3Cycles);
/** 初始化ADC的DMA*/
ADC_DMAConfig();
/** 使能DMA请求*/
ADC_DMARequestAfterLastTransferCmd(DEBUG_ADC, ENABLE);
/** 使能ADC DMA*/
ADC_DMACmd(DEBUG_ADC, ENABLE);
/** 使能ADC*/
ADC_Cmd(DEBUG_ADC, ENABLE);
/** 开始软件触发 ADC转换*/
ADC_SoftwareStartConv(DEBUG_ADC);
}
多通道扫描
定义公共信息
相对于单通道,GPIO和ADC数据存储器相应的增加了定义。
/** 定义ADC GPIO*/
#define ADC_IN0_GPIO_PORT GPIOA
#define ADC_IN0_GPIO_PIN GPIO_Pin_0
#define ADC_IN0_GPIO_CLK RCC_AHB1Periph_GPIOA
#define ADC_IN1_GPIO_PORT GPIOA
#define ADC_IN1_GPIO_PIN GPIO_Pin_1
#define ADC_IN1_GPIO_CLK RCC_AHB1Periph_GPIOA
#define ADC_IN2_GPIO_PORT GPIOA
#define ADC_IN2_GPIO_PIN GPIO_Pin_2
#define ADC_IN2_GPIO_CLK RCC_AHB1Periph_GPIOA
/** 定义ADC 相关信息*/
#define DEBUG_ADC ADC1
#define DEBUG_ADC_BASE ADC1_BASE
#define DEBUG_ADC_CLK RCC_APB2Periph_ADC1
#define DEBUG_ADC_CHANNEL0 ADC_Channel_0
#define DEBUG_ADC_CHANNEL1 ADC_Channel_1
#define DEBUG_ADC_CHANNEL2 ADC_Channel_2
#define DEBUG_ADC_DR_ADDR ((uint32_t)DEBUG_ADC_BASE + 0x4C)
/** 定义ADC DMA相关信息*/
#define DEBUG_ADC_DMA_CLK RCC_AHB1Periph_DMA2
#define DEBUG_ADC_DMA_CHANNEL DMA_Channel_0
#define DEBUG_ADC_DMA_STREAM DMA2_Stream0
/** 定义数据存储数组*/
#define ADC_CONV_CH_SIZE 3
uint16_t adcConvertedValue[ADC_CONV_CH_SIZE];
配置ADC和DMA
相对于单通道,ADC需要开启扫描模式,DMA配置为存储器增量,ADC Channel数增加到3个。
/** 初始化ADC IO*/
void ADC_GPIOConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(ADC_IN0_GPIO_CLK, ENABLE);
RCC_AHB1PeriphClockCmd(ADC_IN1_GPIO_CLK, ENABLE);
RCC_AHB1PeriphClockCmd(ADC_IN2_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = ADC_IN0_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(ADC_IN0_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = ADC_IN1_GPIO_PIN;
GPIO_Init(ADC_IN1_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = ADC_IN2_GPIO_PIN;
GPIO_Init(ADC_IN2_GPIO_PORT, &GPIO_InitStructure);
}
/** 初始化ADC相应的DMA*/
void ADC_DMAConfig(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(DEBUG_ADC_DMA_CLK, ENABLE);
/** ADC 数据寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = DEBUG_ADC_DR_ADDR;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&adcConvertedValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
/** buffer size 和需扫描的ADC通道数一致*/
DMA_InitStructure.DMA_BufferSize = ADC_CONV_CH_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
/** 存储器地址递增*/
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
/** ADC DR数据大小为半字,即两个字节*/
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_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_InitStructure.DMA_Channel = DEBUG_ADC_DMA_CHANNEL;
DMA_Init(DEBUG_ADC_DMA_STREAM, &DMA_InitStructure);
DMA_Cmd(DEBUG_ADC_DMA_STREAM, ENABLE);
}
/** 初始化ADC*/
void ADC_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
RCC_APB2PeriphClockCmd(DEBUG_ADC_CLK , ENABLE);
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
/** 设置ADCCLK为PCLK2的2分频*/
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
/** 采样时间 间隔*/
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
ADC_StructInit(&ADC_InitStructure);
/** ADC 分辨率*/
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
/** 开启扫描模式*/
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通道数一致*/
ADC_InitStructure.ADC_NbrOfConversion = ADC_CONV_CH_SIZE;
ADC_Init(DEBUG_ADC, &ADC_InitStructure);
/** 配置 各ADC通道转换顺序,采样时间都为3个时钟周期*/
ADC_RegularChannelConfig(DEBUG_ADC, DEBUG_ADC_CHANNEL0, 1, ADC_SampleTime_3Cycles);
ADC_RegularChannelConfig(DEBUG_ADC, DEBUG_ADC_CHANNEL1, 2, ADC_SampleTime_3Cycles);
ADC_RegularChannelConfig(DEBUG_ADC, DEBUG_ADC_CHANNEL2, 3, ADC_SampleTime_3Cycles);
/** 初始化ADC的DMA*/
ADC_DMAConfig();
/** 使能DMA请求*/
ADC_DMARequestAfterLastTransferCmd(DEBUG_ADC, ENABLE);
/** 使能ADC DMA*/
ADC_DMACmd(DEBUG_ADC, ENABLE);
/** 使能ADC*/
ADC_Cmd(DEBUG_ADC, ENABLE);
/** 开始软件触发 ADC转换*/
ADC_SoftwareStartConv(DEBUG_ADC);
}
提高采样精度的硬件方法
- 保证参考电压噪声最小化
- IO引脚串扰最小化
- 加入屏蔽减少EMI
- PCB将模拟和数字分开布局和铺设
提高采样精度的软件方法
采样平均
在硬件抗干扰能力不足的情况下,我们可以牺牲采用速率,采用软件方法进行滤波,从而得到更加稳定的采样值。一般应用的情况下,“抗脉冲干扰均值滤波”算法就能够满足需求。也可以根据情况使用更复杂更合适的滤波算法。
/** Anti-interference Average Filtering*/
uint16_t Anti_AverageFilter(uint16_t *buffer, uint16_t length)
{
uint16_t temp;
uint16_t i,j;
/**Bubbling Sort*/
for(j = 0; j < length - 1; j++)
{
for(i = 0; i < length - j - 1; i++)
{
if(buffer[i] > buffer[i + 1])
{
temp = buffer[i];
buffer[i] = buffer[i + 1];
buffer[i + 1] = temp;
}
}
}
temp = 0;
for(i = 1; i < length - 1; i++)
{
temp += buffer[i];
}
temp /= (length - 2);
return temp;
}
数字信号滤波
可通过数字低通、高通等滤波器进行软件滤波。比如知道被测信号中的噪声来自50 Hz供电线,通过适当的数字滤波,可以只抑制50 Hz频率并传输无此噪声的数据信号。
ADC软件校准
如果采样值较为固定,但离目标值相差大,还可以利用线性拟合(线性校准曲线)的方式让采样值更接近目标值。
采样
首先基于标准源表采样足够多的点(点数越多,拟合越准确)。
NO | 采样值(mv) | 目标值(mv) |
---|---|---|
1 | 96.7 | 100 |
2 | 194.5 | 200 |
3 | 498.8 | 500 |
4 | 796 | 800 |
5 | 1495 | 1500 |
拟合
在数学工具(Matlab或Excel等)中做线性拟合(线性、多项式、指数皆可),得到校准公式和相关系数R值。
校准
用拟合步骤中得到的校准公式对采样值进行校准。
NO | 采样值(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 |