STM32恒流源法电阻测量仪设计与实现
做嵌入式这么多年测电阻这件事我一直觉得没表面上那么简单。手里虽然有万用表但每次测低阻值电阻或者在线测量时接触电阻、导线压降带来的误差能让人抓狂。后来项目里需要做一个自动化的电阻测量模块干脆自己用STM32搭了一个恒流源法电阻测量仪顺带把显示也换成了0.96寸OLED效果还不错。这篇就把整个设计与实现过程拆开讲清楚从恒流源原理、硬件电路、软件代码到实际测试数据全都有适合正在做相关课设、毕业设计或者想自己做一个万用表模块的朋友参考。1. 方案如何选型与实际硬件设计逻辑很多人拿到“测量电阻”这个需求第一反应是用电阻分压加ADC去读。这个方案本身没有错但一旦涉及宽量程测量问题就很明显。分压法需要根据待测电阻的预估范围反复调整分压电阻否则小电阻上分到的电压太小ADC的分辨率根本不够看大电阻又可能超过量程。恒流源法就好比用一个已知大小的水流去冲一个未知宽度的河道根据水位算河道截面思路更直接测量值跟待测电阻成线性关系不用去解分压方程。1.1 恒流源法测量的基本原理恒流源法的核心公式只有一个Rx Ux / Iset。其中Iset是流过待测电阻的恒定电流Ux是待测电阻两端的电压通过STM32的ADC采集得到。只要恒流源足够稳定ADC的采样足够准整个系统的精度就完全取决于这两个环节。这个方案最大的好处是线性度好计算简单而且电流恒定后电源电压波动和导线电阻对测量结果的影响被大幅削弱。举个例子如果我设定1mA的恒流电流当接入一个10kΩ电阻时电阻两端电压是10V这个电压可以直接进ADC接入100Ω电阻时电压只有0.1V虽然小但因为ADC是12位的也还能读到一定的分辨率。如果想覆盖更大的范围就必须分档切换电流。1.2 量程设计与电流档位划分在设计量程时我参考了数字万用表常见的量程划分思路。核心原则是保证每个档位下待测电阻两端的电压都落在ADC的合适采集区间内既不能超过ADC参考电压也不能小到接近噪声底。以STM32F103的ADC为例参考电压设定为3.3V12位分辨率理论最小分辨电压大约是0.8mV。为了留出余量我把每个档位的满量程电压控制在2V左右。低阻档恒流10mA适合测1Ω到200Ω满量程约2V中阻档恒流100uA适合测200Ω到20kΩ满量程约2V高阻档恒流10uA适合测2kΩ到200kΩ满量程约2V超高阻档恒流1uA适合测20kΩ到2MΩ满量程约2V这样分档的好处是待测电阻两端电压始终落在0.1V到2V这个区间内ADC的有效分辨率利用得很充分又不会过压。切换方式我用的是继电器或者模拟开关实际制作中如果对切换速度要求不高用继电器更稳妥接触电阻小。2. 硬件电路设计与关键器件选型硬件电路是整个仪器的基础恒流源建不出来后面程序写得再好也没用。我在设计时花了不少时间在恒流源电路的选型上试过LM334、试过三极管分立方案最后发现运放加基准源的方案精度和温度稳定性综合表现最好。2.1 恒流源电路结构与运放选型电路上我选用了运算放大器深度负反馈结构配合精密基准源产生恒定电流。核心思路是用基准源产生一个稳定的参考电压输入到运放的同相端运放输出驱动调整管在采样电阻上形成压降运放的反馈端检测采样电阻电压最终使得采样电阻上的电压等于参考电压从而电流等于参考电压除以采样电阻。这里运放的选型非常关键。普通LM358也能工作但失调电压和温漂都不算小。我实际用下来OP07或者OPA2277这类精密运放效果更好。具体电路参数上如果基准源输出2.5V采样电阻用250Ω那么恒流就是10mA采样电阻换成25kΩ恒流就变成100uA。切换采样电阻就能切换档位这也是我最终采用的档位切换方式。补充一点运放输出端到调整管基极之间要加限流电阻防止上电瞬间电流过大。调整管我用的是一只小功率MOS管压降小带载能力强选三极管的话最好用达林顿结构不然小电流档位容易压不住。2.2 ADC采样电路与输入保护STM32内置的是逐次逼近型ADC输入通道的等效输入阻抗不算高。恒流源输出端直接接ADC引脚会存在两个问题一是恒流源输出电压被ADC内部采样电容分压导致采样值偏低二是如果待测电阻很大输出阻抗高信号源驱动ADC采样电容的能力不足采样结果会明显跳动。我的处理办法是加一级电压跟随器。用另一个运放做跟随把恒流源输出的电压缓冲后再送入ADC。跟随器输入阻抗极高基本不会从恒流源吸取电流采样值也更稳定。输入保护也很重要不能忽略。万一待测电阻开路或者误接高压恒流源输出端会瞬间被拉得很高直接烧ADC引脚。我在ADC输入端对地并联了双向TVS管同时串了一颗1kΩ电阻限流实测下来误接过12V电压也没有损坏芯片。2.3 OLED显示模块与整机结构显示部分用的是最常见的0.96寸I2C接口OLEDSSD1306驱动4个引脚VCC、GND、SCL、SDA。这个模块批量使用时经常遇到点不亮的问题但大部分情况不是模块坏了而是地址不对或者上拉电阻缺失。我的板上SCL和SDA都加了4.7kΩ上拉电阻OLED供电用3.3V和STM32同电压避免电平不匹配。整机结构上我做的是一个小型PCB电源入口加了LDO稳压到3.3VMCU用STM32F103C8T6最小系统预留了SWD下载口和一个串口调试口。这样调试的时候方便打印内部数据量产或长期运行时通过OLED看结果就行。3. 软件程序设计与关键模块实现硬件只算完成了一半软件才是把这个仪器真正“盘活”的部分。我基于STM32的HAL库开发代码结构分了三层底层是OLED驱动和ADC读取中间是滤波与电阻计算顶层是量程切换和显示逻辑。工程在Keil5里编译烧录用ST-Link非常常规。3.1 系统软件主流程主程序的逻辑相对直白主循环里跑一个状态机上电初始化配置时钟、GPIO、I2C、ADC、UARTOLED显示开机画面进入自动测量循环先默认中阻档读取一次电阻值根据结果自动切换到最优档位再重新测量数据处理软件滤波、去零漂、线性修正OLED刷新显示当前档位、采样电压、计算电阻值我实际把一次完整的测量周期控制在300ms以内其中ADC采样的时间占了大头。如果以后需要更快的刷新率可以把采样次数减少或者提高主频。3.2 ADC采样与软件滤波算法ADC配置上我开了DMA扫描多个通道一次触发连续采集32次然后软件做中位值平均滤波。中位值平均滤波的原理是先把采集到的数据排序去掉最大值和最小值再对剩下的数据求平均。这个方法对付随机脉冲干扰和周期性噪声都有效果比单纯滑动平均更抗尖峰。下面是关键的ADC读取与滤波代码实测跑起来效果很好#define ADC_SAMPLE_NUM 32 uint32_t adc_raw_buf[ADC_SAMPLE_NUM]; // 排序函数用于中位值平均滤波 void sort_array(uint32_t *arr, uint32_t len) { for (uint32_t i 0; i len - 1; i) { for (uint32_t j 0; j len - 1 - i; j) { if (arr[j] arr[j 1]) { uint32_t temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; } } } } uint32_t get_adc_filtered(ADC_HandleTypeDef *hadc, uint32_t channel) { ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel channel; sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(hadc, sConfig); for (uint32_t i 0; i ADC_SAMPLE_NUM; i) { HAL_ADC_Start(hadc); HAL_ADC_PollForConversion(hadc, 10); adc_raw_buf[i] HAL_ADC_GetValue(hadc); HAL_ADC_Stop(hadc); } sort_array(adc_raw_buf, ADC_SAMPLE_NUM); uint64_t sum 0; for (uint32_t i 2; i ADC_SAMPLE_NUM - 2; i) { sum adc_raw_buf[i]; } return (uint32_t)(sum / (ADC_SAMPLE_NUM - 4)); }这里采样时间我配置成了239.5个周期也就是最长的采样时间。因为恒流源输出经过跟随器之后输出阻抗虽然低了但依然存在高频噪声延长采样时间相当于一个内置的硬件低通滤波对读数稳定帮助很大。3.3 OLED显示驱动与界面设计OLED驱动直接基于HAL库的I2C接口写。初始化SSD1306的时候注意要先发软件复位命令再关闭显示、清屏、设置显存地址模式最后开启显示。现在网上很多现成的HAL库驱动代码但不少都缺少关键延时导致复位不彻底我建议写驱动的时候先对照SSD1306数据手册走一遍命令时序别直接抄。显示界面分了三个区域顶部显示当前量程和电流档位中间一行大字显示电阻值底部显示电阻两端的实测电压。OLED本身刷新速度不快所以动态动画别做太多我做了个简单的开机动画就收手了。字库方面如果只是显示数字和字母用ASCII字库就够了取模软件生成数组就行。如果要显示汉字就需要准备16x16的GB2312字库。我的界面上用了“测量”、“档位”等中文所以提前在工程里塞了一份常用汉字字模。3.4 自动量程切换与电阻计算电阻计算不是直接用采样值除以电流那么简单。实际系统中存在零漂和增益误差我采用了两点校准法把计算流程拆成两步先电压校准再电阻计算。电压校准是在没有接负载时记录ADC的零漂值然后在已知电压下记录换算系数。这样最终计算电压时用(ADC值 - 零点偏移)乘以系数把跟随器和ADC通道的零点误差一起消掉。电阻计算则直接除以当前恒流值同时做了温度补偿修正。自动量程切换我采用“先粗测再精测”的策略。第一轮先固定用中阻档(100uA)测量根据得到的大致数值判断目标量程第二轮切换量程后再正式采样。这样避免低阻档直接测大电阻导致输出电压超过ADC参考电压也让最终读数更稳。切换量程到重新采样之间加入100ms延时留时间让恒流源稳定下来这点尤其重要不然刚切换完瞬间去读ADC采到的还都是瞬态值。4. 实测数据与误差分析代码写完硬件打样回来最期待的就是实测。我准备了一组标称值的精密电阻从10Ω到1MΩ用这个仪器测了一遍又跟四位半万用表对照了一下结果还是很有参考价值的。4.1 实测精度数据记录以下是典型档位下的实测记录标称电阻设定电流实测电压实测电阻实际相对误差10Ω10mA0.1012V10.12Ω1.20%100Ω10mA1.0045V100.45Ω0.45%1kΩ100uA0.9950V995.0Ω0.50%10kΩ100uA0.9942V994.2Ω0.58%100kΩ10uA0.9985V99.85kΩ0.15%1MΩ1uA0.9978V997.8kΩ0.22%低阻档10Ω测量误差偏大主要原因是接触电阻和PCB走线电阻的叠加。10mA电流流过10Ω电阻时如果有0.1Ω接触电阻就会带来1%的误差这在低阻测量中占比很可观。这也解释了为什么商品万用表测低阻时要用四线开尔文接法。4.2 误差来源与标定方法分析从数据看中高阻档位的误差普遍在0.5%以内这个精度对于DIY仪器来说已经算不错了。误差的主要来源有三个恒流源的电流精度、ADC参考电压的精度、以及开关接触电阻。恒流源电流精度取决于基准源电压精度和采样电阻精度。我用的采样电阻是0.1%精密电阻基准源是TL431温漂性能一般室温下勉强够用如果对精度要求更高建议换REF3030这类低温漂基准源。ADC参考电压直接影响电压换算精度。STM32的参考电压其实是内部VDD经过LDO出来的虽然稳定度还行但绝对精度并不保证。我实测它的实际值在3.28V左右和我们配置时假设的3.3V有偏差所以两点校准时我以实际测量值为准而不是用理论值。标定方法上我用的是一个简单线性模型最终电压 (ADC原始值 - 零点偏移) × 电压系数。零点偏移在开机时自动测量电压系数在出厂时写入Flash。这样即使换了一片不同供电电压的STM32也只需要重新标定一次系数不需要改代码。5. 常见问题与调试技巧汇总这套仪器做完之后我在反复调试中踩了不少坑尤其是OLED显示和ADC采样这两个方面。这里挑几个典型案例分享一下帮大家少走弯路。5.1 OLED点不亮与花屏的排查实录0.96寸OLED批量点不亮这个问题网上讨论很多我实际碰到的原因也五花八门。最常见的是SCL、SDA接反了其次就是I2C地址不对。SSD1306默认地址一般是0x3C但某些模块是0x3D跑不通的时候先用I2C扫描程序扫一下地址比肉眼翻手册快得多。还有一次遇到的现象是OLED上电后满屏雪花点看起来像花屏。后来排查发现是OLED的VCC接到了5V上而I2C信号是3.3V虽然模块大多自带稳压但显存初始化要求先断电再上电否则会进入不可控状态。解决方法是程序中上电后延时200ms再初始化OLED并且把VCC改成3.3V供电。另外一个值得注意的细节OLED的I2C总线需要上拉电阻很多模块板上没有。如果直接用杜邦线连接STM32和OLED容易被干扰或者根本不工作我建议在靠近STM32的一侧加装4.7kΩ到10kΩ的上拉电阻到3.3V。5.2 ADC采样数值跳动与恒流源噪声处理ADC读数来回跳是高频出现的问题。我最初以为是滤波不够后来用示波器一测发现恒流源输出端有一大堆几百kHz的振荡。问题出在运放的反馈补偿电容没加电路自激了。在运放反馈电阻上并联一个几十pF的小电容振荡立刻消失。还有一个容易忽视的地方是地线处理。恒流源电流从采样电阻回来走的路径如果和ADC参考地靠得太近地上压降就会被ADC当作信号采进去。我把电源地、模拟地、数字地做了单点汇接模拟电路集中在板子的一个角落信号线尽量短粗采样出来的数据稳定了很多。5.3 排查思路速查表我把调试过程中常用的几个排查点整理成了速查表方便上电异常时按顺序检查现象优先检查项备选方案OLED完全无显示I2C地址、接线顺序上拉电阻、OLED供电电压OLED花屏或乱码复位时序、延时不足重新上电时序、检查显存访问方式ADC采样明显偏高/偏低校准系数、参考电压实际值重新做两点标定ADC数值乱跳运放自激、采样时间加补偿电容、延长ADC采样周期高阻档读数偏低输入阻抗不足、跟随器输入偏置电流换JFET输入运放量程切换后读数异常切换后稳定时间不足增加延时到100ms以上还有一个隐藏很深的坑如果程序里切换了ADC通道但通道配置没有生效就立刻开始转换会采到上一次通道的残留值。这里建议切换通道后丢弃前两次转换结果再进入正式的采样循环。6. 实际使用中的一些心得与细节建议最后再补充几个我个人用了很久之后才总结出来的细节适合后续想继续优化这版设计的朋友参考。关于电流档位我目前用的是四档但对于小阻值测量精度要求更高的场景可以考虑增加一档更大的电流比如100mA专门测1Ω以下的微小电阻。不过这时候必须用四线开尔文接法不然线阻误差完全掩盖了真实电阻值。关于ADC精度如果觉得12位不够用有两个低成本改进方向一是用外部基准源芯片比如REF3030提供更精确的3.0V参考电压稳定性比内部参考好很多二是在软件里用过采样技术对同一个电压采样多次累加取平均等效提高分辨率。后者对恒流源的稳定性要求会更高因为信号本身要足够稳累加才有意义。我在这个项目里用了中位值平均滤波效果介于两者之间性价比比较高。OLED显示部分如果想做交互可以加上两个按键用于手动切换档位并支持长按进入校准菜单。我后来的版本就在代码里预留了这个入口校准的时候不用改源码直接通过OLED菜单操作非常方便。这也是这个项目后续最值得扩展的地方有了按键菜单后整套设备就更接近一个独立仪表了。