ADC基本原理与STM32F030ADC应用

一、什么是ADC

1.ADC的定义

ADC(Attack Damage Carry/Core)是普通攻击持续输出核心的简称,是一场游戏中伤害输出核心之一(最主要是能以普通攻击来造成持续伤害输出,以免输出出现空窗期),主要应用于MOBA类游戏中(划掉)

图1   这个不是我们今天讨论的ADC

模拟数字转换器即A/D转换器,或简称ADC,通常是指一个将模拟信号转变为数字信号的电子元件。通常的模数转换器是将一个输入电压信号转换为一个输出的数字信号。由于数字信号本身不具有实际意义,仅仅表示一个相对大小。故任何一个模数转换器都需要一个参考模拟量作为转换的标准,比较常见的参考标准为最大的可转换信号大小。而输出的数字量则表示输入信号相对于参考信号的大小。

图2   这个才是我们今天讨论的ADC

2.ADC的作用

将一个模拟信号转换为数字信号,通常为电压值。这些电压值中往往存在着我们需要的信息,简单来说,一般情况下ADC起到的就是信息传递的作用。

二、为什么要有ADC

  1. 现实世界中很多物理量都需要通过传感器转化为电压进行信息的传递、比较。

图3   一种血氧传感器设计,可以看到其中应用了ADC

    2.由于模拟量易受温度、电压波动等情况干扰,数字电路不仅可以进行算术运算,还可以进行逻辑运算等诸多优点,被广泛应用。

图4   相较于模拟电路,数字电路稳定性更好,功耗更低

      3.在实际应用场景中,单片机、单板机、计算机都是基于数字电路工作的。

图5   由意法半导体公司生产的STM32芯片

三、一些基本概念

1.ADC精度

我们通常用“位数”这个概念来衡量ADC的精度。这个“位数”指的其实就是系统中用来表示电压的二进制数的位数。比如一个八位ADC,就是采样电压能够用0-255来表示,如果采样电压与基准电压相等,就输出编码255,用二进制数表示就是8个1,此时采样电压也就是能够采集到的最大电压,如果采样电压为0V,就输出编码0。需要注意的是,部分ADC可以既能检测正电压,也能检测负电压,其检测范围变为正负参考电压。此时ADC采用-128-128来编码电压,可以看到由于检测范围的提升,电压的准确率下降了。因而ADC精度不仅与位数相关,也与其工作范围有关。

举个例子,如果我使用STM32F1系列的单片机进行采样,其ADC精度为12位,基准电压就是单片机的供电电压3.3V且其无法量化负电压,检测范围为0-3.3V。假设待测的电压为1.8V,我得到的输出应当是1.8/(3.3/(2^12))=2234,用二进制数来表示就是100010111010。同理,我可以根据我得到的二进制数换算出我要采样电压的值。

2.运算放大器

运算放大器是什么组成的,我们在此略去不表,具体可以等大家在模电课上详细学习,在此我们只需要知道其几个特性与经典电路即可。

1)运算放大器输入阻抗趋于无穷大,虚断特性

图6   经典运算放大器结构

2)在深度负反馈情况下有虚短特性。

3)积分器的基本结构与输出。

图7   积分器

u0(t)=1/rcdt=ui/rc,这个公式后续我们会用到

4)比较器的基本结构与输出。

图8   比较器

当IN1与IN2大小发生变化时,比较器输出会翻转,依靠比较器的输出我们可以得到IN1与IN2的大小信息

3.采样定律

要从抽样信号中无失真地恢复原信号,抽样频率应大于2倍信号最高频率。 抽样频率小于2倍频谱最高频率时,信号的频谱有混叠。 抽样频率大于2倍频谱最高频率时,信号的频谱无混叠。举个例子,假设需要通过采样画出频率为50HZ的正弦波曲线,那么采样率必须达到100HZ以上才能够绘制出完整的正弦曲线。

图9   信号混叠

四、ADC原理

  1. 数模转换的基本过程

  A/D转换一般要经历取样、保持、量化及编码4个过程。

1)取样-保持过程

由于在实际环境中电信号经常受到高频信号的干扰,因而在取样前,ADC输入端通常会有低通滤波电路来对信号进行筛选。低通滤波电路,顾名思义,就是对采集到的信号中高频的部分进行滤除,低频的部分照常通过。利用了电容通交阻直的特点,高频信号通过电容与地导通被地吸收,低频信号继续向下传递。

图10   一阶低通滤波电路

低通滤波之后的取样电路本质为一个开关结构,由芯片内部的开关控制器所控制。当开关导通时,内外电压相等,完成采样,开关断开时,由于运算放大器输入端输入电阻趋于无穷大的特性,实际当然不可能有那么大,但多数运算放大器的输入电阻常常在MΩ级别且开关已经断开,电容两端的电压也即后面这个运放输入端电压会保留一段时间,以确保后续量化与编码过程的顺利进行。

取样-保持过程中,原来连续的模拟量由于开关的脉冲特性变为了在时间与电压上都离散的值。

图11   采样后的波形

2)量化-编码过程

依据量化-编码过程的不同,ADC可以分为间接型ADC与直接型ADC两大类,直接型ADC又可分为并联比较型ADC、逐次逼近式ADC等。

量化的方式有两种,一种为只舍不入法,即当电压不够一份时,直接舍弃,向下取整。另一种为有舍有入法,当尾数不足一半时舍弃该尾数,尾数超过一半时则进一位。我们可以看到,后者的误差为基准电压均分份数的一半,精确度更高,因而多数ADC采用的是这套取整办法。

2.ADC种类

1)间接型ADC

间接ADC是先将输入模拟电压转换成时间或频率,然后再把这些中间量转换成数字量,常用的有中间量是时间的双积分型ADC。

双积分型ADC核心原理为电荷守恒原理,其基本结构为积分器与比较器串联,积分器一段为单刀双掷电子开关,一端接待测电压,另一端接参考电压,比较器则一段与积分器输出相接,另一端与GND相接。当输入开关闭合后,同时开始计时,积分电路对待测电压进行积分,此积分时间由开关控制信号决定,当开关控制信号降低至低电平时,计时停止,可以得到积分器电压表达式,正向积分结束。接着开关从输入侧向参考电压侧闭合,进行反向积分。参考电压为以确定的电压,接入参考电压后,电容两端电压开始下降,当积分器输出为0即比较器输出翻转时,计时停止。通过积分器电压输出特性,与电荷守恒的原理,我们可以得到待测电压与参考电压之比与两次积分时间相等,进而可以读出待测电压的值,以二进制编码输出。

图12   双积分型ADC结构与积分器输出

举个例子,如果某天学校想知道今天有多少人摸鱼没来做核酸,由于我们做的是十混一,十人一管,二十管一盘,学校只需要统计一共有多少盘就知道今天一共有多少人做了核酸,多少人没做。在这个过程中,我们可以把学生来排队做核酸看做积分式ADC的前半部分,学校统计人数看做后半部分。

双积分型ADC精度较高,转换时间较长,因而被广泛应用在高精度低频率的场合,如高精度直流电压表内部大多集成的便是这种ADC。

2)直接型ADC

常用的直接型ADC主要有两种,一为并联比较型ADC,一为逐次逼近型ADC。下面我们分别简单介绍一下这两类ADC的原理。

a.并联比较型

并联比较型ADC电路比较简单粗暴,利用了最基础的电阻分压关系对参考电压进行等分,比较器两引脚分别与等分之后的电压与待测电压相连,根据比较器输出得到待测电压范围。在网上还看到另一种设计,不是利用等分电阻对参考电压进行分压,优化了待测电压较小时ADC的表现。这种ADC原理简单且其速度几乎只被数字转换电路所限制,可以做到很高的频率。但缺点也很明显,随着精确度也就是位数的上升,所需要的电阻、比较器与寄存器等元件随之成比例的增长,如果我想用这个原理搭建一个八位ADC,我需要2^8-1=255组元器件。同时,由于采用电阻对参考电压进行分压,元件的功耗会很大。电阻的阻值不能无限制的增大,我们前面说过,运算放大器的输入电阻为MΩ级别,如果运算放大器的输入电阻不能远大于分压电阻,运算放大器的工作状态势必受到影响。

图13   并联比较型ADC,其优势与劣势都很明显

图14   ADC输出与电压对应区间

对此也有改进方法,对待测电压进行分级比较,图中输入模拟信号v1,经取样-保持电路后分两路,一路先经第一级5位并行A/D转换进行粗转换得到输出数字量的高5位,另一路送至法发器,与高5位D/A转换得到的模拟电压相减。由于相减所得到的差值电压小于1VLSB(最小分辨率),为保证第二级A/D转换器的转换精度,将差值放大25=32倍,送第二级5位并行比较A/D转换器,得到低5位输出。这种方法虽然在速度上作了牺牲,却使元件数大为减少,在需要兼顾分辨率和速度的情况下常被采用。

图15   分级并联ADC,体现了折中的原则

b.逐次逼近型

逐次逼近型ADC由4个主要子电路构成:采样保持放大器(SHA)、 模拟比较器、参考数模转换器(DAC)和逐次逼近型寄存器(SAR)。 由于 SAR 控制着转换器的运行,因此,逐次逼近型转换器一般 称为SAR ADC。

该ADC依靠DAC转换器工作,DAC首先将最高位置1,即输出参考电压的一半与采样到的模拟电压进行对比,如果DAC输出电压高于采样电压,则将最高位置0,反之则反,确定第一位后,继续对其他位进行相同操作直至最后一位。简单来说,就是利用二分法来确定采样电压范围,得出编码。

图16   逐次逼近型ADC,STM32集成的便是这种ADC

该ADC工作原理类似于天平,即通过不断改变砝码的数量与质量,观察天平的偏向以更改砝码,最终得到信息。在ADC中,比较器充当了天平的角色,写入DAC的寄存器值则与砝码类似。

3.其他概念

1)误差来源

主要误差来源应当是由于量化过程中因为位数导致的分辨率有限,最多会有1/2LSB的误差。其次是由于高频信号干扰或是器件本身时钟不良震荡导致的干扰

2)过采样

过采样的原理,恕在下才疏学浅,实在是没有看懂(别骂了别骂了)在这里留一个链接,感兴趣的同学可以去看看过采样技术详细介绍_工程师看海的博客-CSDN博客_过采样技术

但是不影响我们利用这个原理得到结论,过采样可以在一定范围内提高分辨率,大概可以提高2到4位,可以将12位ADC转化为16位ADC使用。

五、以STM32F030为例演示ADC使用

1.CUBE配置

我们需要注意的有Clock Prescaler(决定ADC工作频率与挂载总线位置) 、Resolution (ADC分辨率)、Scan Conversion Mode(扫描模式,多通道时自动打开)、Continuous Conversion Mode(连续转换模式,决定ADC各通道采样次数)、Data Alignment (数据对齐方式)Sampling Time (采样时间)这几个选项。

在Clock Prescaler下我们可以选择ADC工作在同步或异步模式下,对应挂载的时钟不同。同时还可以选预分频次数。分辨率前文中已经提到过,在此表去不谈。扫描模式对应多ADC通道时ADC的采样顺序,选择“Forward”模式时ADC自动从编号小的通道向编号大的通道依次采样,“Backward”模式则刚好相反。如不使能连续转换模式则ADC各通道采样一次后便不再继续采样,根据需要我们一般使能该功能。数据对齐方式则是由于单片机中采用8字节储存数据,因而12位无法占满所有位数,因而需要选择左对齐或右对齐。左对齐与右对齐相关知识可以参考这篇博客PIC单片机精通_ADC左对齐与右对齐的数据读取问题_树懒的聪明的博客-CSDN博客_adc左对齐和右对齐在此略去不表。采样时间则与时钟频率相关,多少次循环进行一次采样。

关于DMA配置,需要注意的是最下方的字长度配置,我们选择“Word”模式进行配置。

图17   ADC配置_1

图18   ADC配置_2

2.代码部分

在这里我们只介绍DMA模式的写法,关于轮询与中断的写法大家可以上网去找百度或者谷歌,在CSDN、博客园等网站类似的博客有很多,大家可以参考下。

最后,在中断回调函数中对数据进行译码,即可得到电压值。

图19   调用DMA的代码

图20   译码部分代码

部分图片与资料来源于网络,侵删

  • 17
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
STM32F103可以通过使用ADC和DAC模块以及中断处理函数来实现低通滤波。 以下是具体实现步骤: 1. 配置ADC模块并使能转换完成中断。 2. 在中断处理函数中读取ADC转换结果,并将其存储到一个数组中。 3. 在主程序中,使用一个定时器来控制DAC输出的频率。 4. 在DAC输出中断处理函数中,从数组中读取ADC转换结果,进行低通滤波处理,然后将结果写入DAC数据寄存器,输出到外部模拟电路中。 下面是一个简单的低通滤波实现示例: ```c #include "stm32f10x.h" #define ADC_NUM_SAMPLES 32 // ADC采样点数 #define DAC_NUM_SAMPLES 32 // DAC输出点数 uint16_t adcResult[ADC_NUM_SAMPLES]; // 存储ADC转换结果 uint16_t dacResult[DAC_NUM_SAMPLES]; // 存储DAC输出结果 // ADC转换完成中断处理函数 void DMA1_Channel1_IRQHandler(void) { if(DMA_GetITStatus(DMA1_IT_TC1)) { DMA_ClearITPendingBit(DMA1_IT_TC1); ADC_ClearITPendingBit(ADC1, ADC_IT_EOC); // 读取ADC转换结果 for(uint8_t i = 0; i < ADC_NUM_SAMPLES; i++) { adcResult[i] = ADC_GetConversionValue(ADC1); } } } // DAC输出中断处理函数 void TIM6_DAC_IRQHandler(void) { if(TIM_GetITStatus(TIM6, TIM_IT_Update)) { TIM_ClearITPendingBit(TIM6, TIM_IT_Update); static uint8_t index = 0; uint16_t value = 0; // 计算低通滤波结果 for(uint8_t i = 0; i < ADC_NUM_SAMPLES; i++) { value += adcResult[i]; } value = value / ADC_NUM_SAMPLES; // 将结果写入DAC数据寄存器 dacResult[index++] = value; if(index >= DAC_NUM_SAMPLES) { index = 0; } DAC_SetChannel1Data(DAC_Channel_1, dacResult[index]); } } int main(void) { // 配置ADC模块 ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_ADCCLKConfig(RCC_PCLK2_Div6); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&adcResult; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = ADC_NUM_SAMPLES; 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_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE); // 配置DAC模块 DAC_InitTypeDef DAC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO; DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; DAC_Init(DAC_Channel_1, &DAC_InitStructure); DAC_Cmd(DAC_Channel_1, ENABLE); // 配置定时器 TIM_TimeBaseInitTypeDef TIM_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); TIM_InitStructure.TIM_Period = 100; TIM_InitStructure.TIM_Prescaler = 72 - 1; TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_InitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM6, &TIM_InitStructure); TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); TIM_Cmd(TIM6, ENABLE); NVIC_EnableIRQ(DMA1_Channel1_IRQn); NVIC_EnableIRQ(TIM6_DAC_IRQn); while(1) { } } ``` 在这个示例中,使用ADC1的通道0进行模拟信号采样,采样频率为ADC时钟频率的1/55。使用DMA1的通道1将ADC转换结果存储到一个数组中。 使用DAC1的通道1输出模拟信号,DAC输出频率由定时器TIM6控制,输出波形为直流电压。在TIM6_DAC_IRQHandler中,从数组中读取ADC转换结果,进行低通滤波处理,然后将结果写入DAC数据寄存器,输出到外部模拟电路中。 注意:本示例中的低通滤波算法非常简单,仅仅是对一组采样点求平均值。实际应用中需要根据信号特点选择合适的滤波算法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值