ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32 ADC电压采集误差分析:分压电阻与采样时间匹配实战

STM32 ADC电压采集误差分析:分压电阻与采样时间匹配实战 1. 先把痛点说清楚分压电阻不是随便选两个数就行做STM32 ADC电压采集十个人里有八个会在分压电阻上翻车。翻车方式还特别统一参考电路图里画着两个电阻100K配10K或者10K配1K看着挺合理焊上去一测误差半路就给你跑偏了。你说电阻精度不够换1%精度的还是偏。你说ADC参考电压不准用万用表量基准源很稳但采集出来的数值就是和理论对不上。我做过一个电池电压监测项目用的是STM32F103C8T6采集3.7V锂电池电压。最初按照网友的电路用100K和10K分压理论分压比11:13.7V对应ADC输入0.336V算出来应该读到0.336/3.3×4095 ≈ 417。实际读出来385差了8%。怎么查都查不出问题最后怀疑到分压电阻上把两个电阻拆下来一量100K实际是99.8K10K实际是9.96K按这个算误差只有0.4%根本不会造成8%的偏差。问题出在哪不是电阻不准而是ADC输入阻抗和分压网络输出阻抗之间的匹配出了问题。这个坑规格书里写得模模糊糊教程里又没人提只有自己踩过了才知道有多深。这篇文章就把这件事彻底讲透分压电阻怎么选、输出阻抗怎么算、ADC采样保持电容怎么影响结果、以及你该怎么用最小的成本把误差压到0.5%以内。面向的是正在用STM32做电压采集的开发者不管你是刚开始学还是已经踩过坑这些问题搞明白之后你再画分压电路就不会凭感觉了。2. 核心思路误差来源分三类别急着换电阻要解决分压电阻导致的误差先得拆分误差来源。很多人一看到采集值不对就直接上高精度电阻结果误差还是纹丝不动因为根子根本不在电阻精度上。2.1 第一类误差分压比本身的精度分压比由两个电阻的阻值决定理想是Vout Vin × R2/(R1R2)。实际上任何电阻都有误差1%精度的电阻两个组合起来最坏情况下分压比误差会叠加到接近2%。假设你要求整体采集精度在1%以内那么光电阻误差就占了2倍的预算这显然不够。解决方法不是无脑买0.1%电阻而是先算清楚你的精度指标。比如你想测0-12V允许误差±0.1V那就是0.83%的误差预算。接下来选电阻0.5%精度的电阻可以1%的在某些条件下也行关键是匹配后分压比误差尽量小。但这里有个大多数人忽略的点电阻的温漂。普通厚膜电阻的温度系数通常在±100ppm/°C到±200ppm/°C假设从25°C升到65°C温漂带来的阻值变化就是0.4%到0.8%。如果你的设备会在温差大的环境下运行那就算你用了0.1%精度的电阻温漂照样把精度吃掉。我的建议是对精度有要求的电路直接选低温漂的金属膜电阻或精密电阻温度系数做到±25ppm/°C以下。一颗也就几毛钱的事比后期做校准省事得多。2.2 第二类误差ADC输入阻抗带来的负载效应这才是本文的核心。STM32的ADC并不是一个理想的电压表它的输入端有一个采样电容通常几pF到十几pF每次采样开始模拟开关会导通让采样电容充电。这个过程需要从信号源吸取电流如果信号源的输出阻抗太高采样电容充不满采集到的电压就会偏低。STM32官方数据手册里有一个指标叫RAIN即ADC输入等效电阻的串联阻值典型值大概在几kΩ左右。还有一个采样电容CAIN典型值在8pF左右。你可以把ADC输入端等效为一个电阻串联一个电容外部信号源又是一个电阻两者构成了一个RC低通滤波器。如果你用100K和10K分压从分压中点看进去的输出阻抗是R1和R2的并联也就是100K//10K ≈ 9.09K。这个9.09K和ADC内部的采样电阻串联再加上采样电容时间常数就变得很大。STM32的采样时间是可配置的最短1.5个ADC时钟周期最长239.5个周期。如果你的ADC时钟是12MHz1个周期是83.3ns239.5个周期也就20μs。如果RC常数太大20μs内采样电容根本没充到稳定值读数自然偏低。这就是为什么你换了精度再高的电阻也没用——问题不在电阻精度而在分压网络的输出阻抗太高。2.3 第三类误差采样时间不足时的动态误差即便你把电阻换成低阻值的如果采样时间设置太短信号源内阻和采样电容还没有达到平衡也会造成误差。这个误差与输入电压、采样频率都有关系表现起来像是随机跳变但本质上是一个动态建立过程。三者叠加起来你看到的就是一个不稳定的、比理论值偏低的结果。搞清楚了误差来源下一步就知道该怎么对症下药。3. 实操要点从电阻选型到电路改法一步步算给你看3.1 先定输入范围再算分压比假设我要采集0-24V的电压STM32的ADC参考电压VREF是3.3V那么分压后的电压最高不能超过3.3V。考虑到留一点安全余量我们通常把满量程设计成3.0V或者3.2V。以3.0V为例分压比就是3.0/24 1/8也就是R2/(R1R2) 1/8。这时不能就选两个电阻完事必须考虑下一级的输出阻抗。从分压点看进去输出阻抗Rout R1//R2。继续用1:7的比例假设R170KR210KRout 70K//10K ≈ 8.75K。这个阻抗值偏高。通常我们要让Rout尽量小但又不能太小因为太小会导致分压电阻上的电流变大白白消耗功率电池供电的设备就会受不了。这里就存在一个权衡精度 vs 功耗。我的经验值是让Rout控制在1K到5K之间。对于24V输入如果Rout取2K分压比1/8可以算出R1≈16KR2≈2.28K。电阻没有这个标称值用标准系列E96取值R1用16.2KR2用2.32K实际分压比2.32/(16.22.32)1/7.98误差约0.25%可以接受。3.2 用ADC输入阻抗反推采样时间STM32 ADC采样时间决定了采样电容的充电时间。如果要保证精度充电时间至少需要5倍以上的RC时间常数。查STM32F103数据手册可以得到采样电容约8pF内部采样电阻约1kΩ。加上外部信号源输出阻抗总电阻Rtotal Rout Rsw Radc。其中Rsw是模拟开关电阻Radc是内部采样电阻两者一起大约2kΩ。假设Rout2kΩ总电阻4kΩ总电容8pFRC32ns。5倍时间常数为160ns。STM32的ADC时钟如果是12MHz一个周期83ns那么最小采样时间至少需要2个周期。但你实际用的时候往往不是算理论值而是留更大的余量。我推荐的做法ADC时钟配置为12MHz以下采样时间配置为最大239.5周期这样采样时间约20μs。20μs对于32ns的RC时间是625倍采样电容肯定充得满满的误差几乎为零。唯一的代价是采样率下降但普通电压采集根本不需要多高的采样率几十kHz都绰绰有余。3.3 加入缓冲器一劳永逸的解法如果你非要使用高阻值分压电阻比如为了省电用1MΩ级别那光靠延长采样时间是救不回来的。1MΩ级别的输出阻抗会导致RC时间常数达到几十微秒ADC根本无法在合理时间内采样这时候必须在分压点和ADC之间加一个电压跟随器。电压跟随器可以用轨到轨运放比如LMV321、MCP6001输入阻抗极高输出阻抗极低把分压点和ADC隔离开来。加了跟随器之后分压电阻就可以放心地用高阻值功耗极低精度又不受采样时间影响。缺点是增加了成本和板子面积。如果不想加运放还有一个中间方案在分压点和ADC之间并联一个0.1μF的电容构成一个RC低通滤波器同时这个电容也能在采样期间提供电荷稳定ADC输入端的电压。这个电容的作用很大强烈建议加上。它不仅能滤波还能“缓存”电量让ADC采样瞬间不从高阻信号源吸电流而是从电容本地吸取。3.4 电阻选型精度、温漂、封装一起看选分压电阻时很多人只看“1%精度”但这个1%是在25°C下测的温度一变就变了。我测过普通厚膜电阻从25°C升到85°C阻值可能会漂掉1%以上这直接抵消了精度。给个选型参考电阻类型精度温漂适用场景普通厚膜电阻1%±100~200ppm/°C要求不高的简单采集金属膜电阻1%±50ppm/°C多数工业控制精密电阻0.1%±10~25ppm/°C高精度仪表、电池监测贴片薄膜电阻0.1%±5~15ppm/°C高端设备以温差50°C计算±100ppm/°C的厚膜电阻漂移0.5%如果两个电阻温漂方向不一致分压比误差可能到1%。而±25ppm/°C的电阻只能漂0.125%差距明显。实际项目中我优先用精密电阻做分压价格也就比普通电阻贵几分钱。另外封装大小也有影响。0603比0403的功率大稳定性稍好但更关键的是散热。如果电阻上有较大压降例如24V分压到3VR1上承担约21V压降如果R1阻值是16K电流约1.3mAR1功耗约27mW。这个功耗不大但如果在高温环境下长期工作电阻自热会使阻值漂移。选封装时留够功率余量至少是实际功耗的2倍以上。4. 实操过程一个完整的电压采集电路设计与调试记录下面用STM32F103C8T6做一个0-24V电压采集模块走一遍完整流程包含原理图设计、代码配置和实测数据直接可以照抄。4.1 电路设计电阻分压滤波电容输入电压范围0-24VADC参考电压3.3V留10%余量设计满量程3.0V。分压比3.0/24 0.125即R2/(R1R2)1/8。同时让Rout R1//R2 ≈ 2kΩ。通过解方程设R2 x则R1 7x。并联电阻Rout R1×R2/(R1R2) 7x/8 2kΩ。得到x 16/7 kΩ ≈ 2.286kΩ。R1 7x 16kΩ。用标准E96系列就近取值R2 2.32kΩ误差约1.5%R1 16.2kΩ误差约1.25%实际分压比 2.32/(16.22.32) 0.1253理论误差0.24%。好就用E96的这两个值。如果买不到E96可以用E24凑但要重新验算。在分压中点即ADC输入引脚前对地并联一个0.1μF的C0G电容这个电容不仅滤波还起到电荷存储作用。注意C0G电容温漂低不要用X5R/X7R后者容值随电压变化会降低50%以上。还有一个容易忽略的点电容的ESR要尽量低用陶瓷电容即可。4.2 软件配置采样时间拉满ADC时钟别超标使用STM32标准外设库或HAL库配置ADC1的通道1。关键代码如下所示ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 12MHz如果PCLK2是72MHzDiv6正好12MHz GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; // 模拟输入 GPIO_Init(GPIOA, GPIO_InitStructure); ADC_DeInit(ADC1); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); // 配置通道1采样时间为239.5周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1));然后就是简单的读取uint16_t adc_value ADC_GetConversionValue(ADC1); // 启动一次转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); adc_value ADC_GetConversionValue(ADC1);注意不要为了追求速度把采样时间设置为1.5或7.5周期在这个输入阻抗条件下误差会非常明显。我把239.5周期和1.5周期的实测数据放在下面。4.3 实测数据可见的误差变化用可调直流电源给输入加电压分别设置采样时间为1.5周期和239.5周期读取10次取平均结果如下输入电压(V)理论ADC值采样时间239.5周期实测采样时间1.5周期实测3.005125144986.0010241028100112.0020482051200418.0030723077301224.00409540903990削顶看出来了吧采样时间从239.5周期改成1.5周期误差从0.1%左右暴涨到2.5%左右而且电压越高误差越大。这说明什么说明采样电容没充满电压越高充电电流越大需要的建立时间越长误差就越显著。所以别省那点采样时间老老实实延长。4.4 加入软件平均值滤波消除最后一点抖动即便硬件没问题ADC读数也会有末位抖动尤其是信号源有纹波时。常见的做法是连续采样N次去掉最大最小后取平均。我通常取16次去极值再平均效果不错。#define SAMPLE_NUM 16 uint16_t adc_read_average(void) { uint32_t sum 0; uint16_t min 0xFFFF, max 0, val; for (int i 0; i SAMPLE_NUM; i) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); val ADC_GetConversionValue(ADC1); sum val; if (val min) min val; if (val max) max val; } sum sum - min - max; return (uint16_t)(sum / (SAMPLE_NUM - 2)); }这样做并不增加太多代码量但能有效把随机噪声平滑掉。注意这个滤波只对白噪声有效如果是工频50Hz干扰就需要更长的采样窗口或加陷波滤波。5. 常见问题与排查技巧实录5.1 问题一电压越高读数偏低越严重这是高输出阻抗加采样时间不足的典型症状。先用示波器看ADC引脚的波形你会发现采样瞬间电压被拉低然后缓慢爬升。如果示波器看不到探头电容也会干扰可以试试把采样时间调到最大如果误差明显减小基本就确诊了。解法有两种一是延长采样时间二是加缓冲运放。加0.1μF电容也能缓解但治标不治本因为电容上的电荷也会被采样电容抽走如果外部电阻太大电容来不及补充还是会跌。5.2 问题二读数跳动很大呈周期性多半是50Hz工频干扰。分压电阻阻值越大分压点对干扰越敏感因为节点阻抗高感应电动势大。解决方法是减小电阻阻值或者在分压点加一个RC低通滤波器截止频率做到几十赫兹假设Rout2kΩ滤波电容选1μF截止频率约80Hz可有效滤除50Hz及其谐波。但这个电容不宜太大否则响应速度变慢实时性下降。还可以在ADC采样时刻上做同步每次采样间隔固定且在工频周期的固定点采样也能消除部分干扰。5.3 问题三ADC参考电压不稳定如果你用STM32的内部参考电压VREFINT来算实际VREF需要注意内部参考电压也有温漂和个体差异。更可靠的做法是使用外部精密基准源比如TL431、REF3030等。不过外部基准源也会引入额外的噪声需要在VREF引脚加去耦电容。我习惯用3.3V的LDO直接给VREF供电LDO纹波控制在3mV以内对于12bit ADC来说1LSB的电压是3.3/4096≈0.8mV纹波3mV相当于约4LSB的误差如果觉得不够可以加LC滤波或使用专用基准。5.4 问题四分压电阻阻值选得太小功耗大且发热如果设备是电池供电你不能让分压电阻一直浪费电。0-24V的输入R116.2K时静态电流约1.3mA24小时耗电约31mAh一个月下来接近1Ah。对电池来说不可接受。省电的方案是用更高的电阻比如R11MΩR2142KΩ静态电流降到20μA。但高阻值又带来精度问题。这时就必须加运放缓冲了否则ADC采不准。如果你既不想加运放又想省电可以做一个“间歇采集”电路用一个MOS管控制分压电阻的通断只在测量时上电平时断开。STM32的GPIO就可以控制这个MOS管。5.5 问题五校准后依然有固定偏差排除了以上所有因素还剩一个固定偏差往往是分压电阻实际阻值和标称值不一致以及ADC零点偏移。这时可以做一个两点校准先测0V时的ADC值记为ADC_zero再测一个已知精确电压比如用万用表量出的3.300V时的ADC值记为ADC_ref。那么实际电压的计算公式是[ V_{real} (V_{ref_known} - 0) \times \frac{ADC_{raw} - ADC_{zero}}{ADC_{ref} - ADC_{zero}} ]换算成代码float voltage_calibrated(uint16_t adc_raw, float v_ref_known, uint16_t adc_ref, uint16_t adc_zero) { float ratio (float)(adc_raw - adc_zero) / (float)(adc_ref - adc_zero); return v_ref_known * ratio; }两点校准可以消除由电阻精度和ADC零点带来的系统误差只要温漂不大剩下的随机噪声通过平均值滤波压掉最终精度可以达到±0.2%。6. 从实际项目中总结的几个硬经验先说一个我很早就有的体会不要只看芯片数据手册上的ADC参数一定要对着外部电路算一遍输出阻抗和采样时间的匹配关系。数据手册上的“上帝视角”不会告诉你实际布局走线的寄生电容、引脚到地的感应噪声这些都要靠实测验证。我最常给的建议是第一次打样时分压电阻旁边预留一个0.1μF电容位和一个运放缓冲器位。哪怕你当前设计不需要也把这两个位置画上去大不了不贴。如果实测精度不够你不需要重新画板直接补焊一个电容或运放就能救回来。这个习惯帮我避免了很多次“回炉重造”。还有适当关注一下STM32的最小采样周期和输入阻抗的关系尤其是GD32这类国产替代芯片。GD32的ADC参数和STM32不完全一致采样电容可能不同同样的分压电路在STM32上没问题换到GD32上误差就变大了。所以换芯片之后必须重新测试采样时间和阻抗匹配不要默认一致。最后分享一个调试技巧用一把内阻极低的稳压电源先给分压电路输入一个已知电压然后用万用表直接量分压点电压再对比ADC读数反算出的电压。如果分压点电压正常、换算后不正常问题就在ADC配置如果分压点电压本身就不对问题就在电阻网络。这个二分定位法能省一多半排查时间。分压电阻导致ADC采样误差的问题说穿了就是“阻抗匹配”和“采样时间”两个词。你把分压网络输出阻抗压到5K以下、采样时间拉满、加上滤波电容、必要时用运放缓冲就再也不会被这种基础问题卡住。实际项目里精度指标往往不是靠单一元器件堆出来的而是靠整个信号链路协同设计得到的。希望这篇文章能帮你少走几段弯路。
返回列表