STM32外设ADC的配置和应用

01 ADC简介

模数转换器,ADC(Analog to Digital Converter),是一个将模拟信号转换为数字信号的器件(电路),例如将温度、湿度、压力、位置等信息转换为数字信号。但由于数字信号本身不具有实际意义,仅仅表示一个相对大小。所以ADC都需要一个参考模拟量(REF)作为转换的标准。

ADC

ADC分类

ADC按工作原理可以分成直接ADC和间接ADC。主要有以下几种:
并联比较型ADC;
逐次逼近型ADC;
双积分型ADC。

其中逐次逼近型ADC是一种直接ADC。由于其采样速率中等,分辨率中等,且位数较多时使用元器件较少等原因(成本较低),所以被广泛应用于集成ADC中。

ADC分类

AD转换原理与步骤

A/D转换的作用是将时间、幅值连续的模拟信号转换为时间、幅值离散的数字信号。所以,A/D转换一般要经过采样、保持、量化及编码四个过程。

采样和保持(Sampling and Holding)

采样是指在时间上将模拟信号离散化,即是将时间上连续的信号转为一系列等时间间隔的信号离散序列。其中离散信号脉冲的幅度取决于输入模拟量。下图列举了一个模拟信号从采样到保持的过程。

采样需要满足采样原理: 采样频率大于模拟信号中最高频率成分的两倍时,采样值才能不失真的反映原来模拟信号。

SH

量化和编码(Quantizing and Encoding)

量化是用有限个幅度值近似原来连续变化的幅度值,把模拟信号的连续幅度变为有限数量的有一定间隔的离散值。而编码则是按照一定的规律,把量化后的值用二进制数字表示。下图列举了12bits ADCFSR为3V时的量化到编码的过程

n:分辨率,用于对输入进行量化的位数
FSR: Full-Scale Range,满量程
LSB: Least Significant Bit,最低有效位
MSB: Most Significant Bit,最高有效位

img

分辨率

理论上,n位输出的ADC能区分2^n 个不同等级的模拟输入电压。如上图所示,能分辨的最小输入电压步长LSB = FSR / 2^n = 732uV。

量化误差

量化误差是由于量化过程引入的误差,通常是以输出误差的最大值形式标出。表示ADC实际输出的数字量和理论输出数字量之间的误差。使用“四舍五入法”时,ADC 转换器的量化误差是 ±½ LSB。

img

转换时间

转换时间是指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电路。

img

S/H电路

被采样的脉冲宽度一般是很短的,在下一个采样脉冲到来之前,要暂时保持所采得的样值脉冲幅度,以便进行后续转换。所以,在采样电路之后要加保持电路。下图是一个简单的采样保持电路配置框图。

img

DAC电路

大多SAR ADC的DAC都使用电容式DAC来提供内在的跟踪/保持功能。电容式DAC是采用电荷再分配原理来产生模拟输出电压的。电容式DAC由N个具有二进制权重值的电容器阵列再加上一个“虚拟LSB”电容器组成。

电容器阵列容量总量要等于2C。

img

转换步骤

转换步骤数等于 ADC的分辨率,比如10bits ADC就有10个转换步骤,每个 ADC 时钟产生一个数据位。以下步骤以10bits ADC为例。

采样状态

该状态下,电容充电至电压VIN。Sa切换至VIN,采样期间Sb开关闭合。

img

保持状态

该状态下,输入断开,电容保持输入电压。Sb开关打开,然后S1-S11切换至接地,且Sa切换至VREF。

img

转换(量化和编码)状态

该状态下,每个 ADCCLK 执行一个步骤,每一步完成后 ADC 输出一位数。采用二分法进行逐次逼近到 ADC 的精度(位数)。整个转换过程如下图所示。

img

整个逐次逼近的步骤如下面的二叉树所示。

img

例子

比如2.5V输入到以3.3V为参考电压的SAR ADC中,则转换过程如下所示。

第一个逼近步骤时,MSB先设置为1。DAC以1/2 REF去和VIN比较,若VIN > 1/2 REF,则保持MSB = 1(反之则MSB = 0)。等待下一个ADCCLK,执行下一步。

img

第二个逼近步骤时,MSB往后移动1个bit,再以3/4 REF去和VIN进行比较,若VIN > 3/4 REF,则保持MSB = 1(反之则MSB = 0)。等待下一个ADCCLK,执行下一步,一直到所有bit确定,然后输出编码值。

img

转换时间

STM32中的ADC转换时间 = 采样周期 + 转换周期

采样周期

采样周期设置决定,要注意的是,该值需要和外部电路的输入阻抗匹配。从而保证在采用阶段,采样保持电容有足够的时间充电。

如前述采样保持电路所示,其中有一个采样保持电容C。

转换周期

该值取决于ADC的转换精度,STM32F4xx的SAR ADC默认为12bits,可配置为10、8、6bits。

NOADC精度转换时间
112 bits12 x ADCCLK
210 bits10 x ADCCLK
38 bits8 x ADCCLK
46 bits6 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);
}

提高采样精度的硬件方法

  1. 保证参考电压噪声最小化
  2. IO引脚串扰最小化
  3. 加入屏蔽减少EMI
  4. 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)
196.7100
2194.5200
3498.8500
4796800
514951500
拟合

在数学工具(Matlab或Excel等)中做线性拟合(线性、多项式、指数皆可),得到校准公式和相关系数R值。

image-20220428150511735

校准

用拟合步骤中得到的校准公式对采样值进行校准。

NO采样值(mv)目标值(mv)校准值(mv)
196.7100100.1
2194.5200197.9
3498.8500502.5
4796800799.9
5149515001499.4

参考文献

  1. 百度百科
  2. AN2834 如何在STM32微控制器中获得最佳ADC精度
  3. Analog to Digital Converters
  4. 实时控制参考指南
  5. 深入了解 ADC 工作原理及参考电压变动的影响
  • 2
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MorroMaker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值