Content
0x00 前言
在单片机开发过程中,常常涉及到ADC的使用,市面上大部分便宜的传感器都是采用的ADC来获取其数据,如MQ-2 烟雾传感器、光敏传感器等等。
此类传感器工作原理为根据所采集到的数据变化,造成其电阻率、电导率等参数变化,从而改变输出电压的大小。而我们通过采集传感器模块的输出电压,即可分析到所需要的信息,这个过程是由模拟信号(因为数据并不是直接以数字体现,而是以电压形式给出,所以称模拟)转化为数字信号(我们需要的是一个数值:浓度,强度),所以这个外设被称为ADC(Anology to Digital),其中C表示的Convert,为转换器的意思。
那么通俗的来说,ADC就是将模拟信号转换为数字信号的一个外设。通过ADC,可以获取到以电压形式体现的信息。还是以烟雾传感器传感器为例,假设烟雾传感器的电压输出范围是0-5v,其中显而易见0代表的是烟雾浓度为0%,5v代表的是烟雾浓度为100%,2.5v代表的烟雾浓度为50%。
我们为获取当前环境的烟雾传感器浓度,只需要采集到 烟雾传感器输出的电压即可。
0x01 ADC配置
现在需要确定一下你的引脚选择,既然是要使用ADC,那么所选择的引脚必须是具有ADC功能才对,现在可以在以下表中选择ADC引脚,例如我选择PA1引脚,那么我接下来应该配置ADC1的通道1。
具体的配置参数细节都已经在注释之中了:
void ADC_Init()
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// 使能ADC1外设时钟,以及将PA1引脚设置为模拟输入模式。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1, ENABLE );
// 设置ADC的时钟频率,也就是STM32的主频 72M/分频因子6 = 12M, 不能超过14M,否则会造成ADC采样精度下降
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 配置为模拟输入模式 Anology Input
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
// 开启扫描模式,则会依次扫描通道,例如你有多个烟雾传感器连接在通道1,通道2,通道3 ,通道4
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式
// 单次转换模式或连续转换模式,如果开启了连续转换模式就会自动连续进行转换,否则需要自己需要的时候开启转换
// 使用DMA时开启自动转换也会更加方便
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
//左对齐,数据范围0-65535,右对齐,数据范围0-4065(精度一样都是12位,只是12位数据以不同的对齐方式放在了uint16_t变量中)
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Left; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1;//顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
// 注意这两个函数是不一样的,先复位校准再进行初始校准
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待初始校准
}
至此ADC校准就结束了,此时就可以编写ADC的单次转换函数了
uint16_t GetAdcValue(u8 ch)
{
// 对ADC1的通道1 进行单次转换
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );
//使能指定的ADC1的软件转换启动功能
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
return (ADC_GetConversionValue(ADC1)); // 返回ADC采样值
}
0x02 滤波处理
一般由于ADC采样到的值是不稳定的,并且电压变化的速率以及大于了ADC的采样速率,并且现实中也会有各种的干扰情况发生,所以单次采样的值并不能代表实际采样的值。
为减少这种情况的发生,所以就要进行一定的滤波处理了。
初中老师以及教过,对于化学、物理、生物实验,为了避免实验的偶然性,应当多做实验。ADC 采样也不例外,可以多次测量,取平均值,在短时间内进行多次ADC采样,取得平均值结果。那么这个就是叫做平均值滤波。这个一般只是一个很简单的处理,通常也是误差比较大的,因为加入你采到了一个非常离谱的数据,贸然取平均值便会导致整体采样值偏移。
如图,以下是由180个采样点,插入一些随机的噪声,贸然取平均值得到的数据,整体大致偏移了2000,这算是很大的误差了
那么还有一个方法是参考其他的采样数据,从而剔除不合理的数据,例如采用方差
等数据剔除不合理的数据,筛选一遍过后,计算得到平滑数据,如下:
当然,并非只有一种滤波算法可选,也可以选择一套动态的滤波算法如FFT,适用且可以应用于大多数场景。