1. 项目概述与方案选型思路
做嵌入式这几年,板子上随时会用到各种标称值的电阻电容,但手头没有LCR表的时候,只能靠万用表大概估一下。后来我用STM32搭配一块最经典的NE555,做了个高精度测量仪,专门负责电容电阻双测量。整套硬件成本很低,却能把电阻从1欧到几兆欧、电容从几十皮法到几百微法的范围测出来,配合软件校准后误差能稳定在1%左右。这篇文章我会把原理、电路、代码、标定和误差分析全部摊开来说,适合想做实用仪器、又想深入理解RC测量原理的朋友参考。
1.1 为什么选NE555+STM32而不是直接上ADC
也许你第一反应是用ADC。直接测电阻确实可以:把电阻和一个基准电阻分压,用STM32的ADC读电压,再用算法反推。但分压法有个天生的问题,量程宽了之后,输出电压变化非常不均匀。测量1欧和1兆欧,需要几组分压和增益切换,不然要么小电阻被分辨率吃掉,要么大电阻进入饱和区;电容测量更麻烦,ADC只能看到电压变化的过程,还需要精确的时间基准来拟合充放电曲线,对实时性要求高。
NE555在这种场景里其实扮演的是一个“时间转换器”:它把被测电阻、电容的大小,转换成一段高电平脉宽。STM32只需要做最擅长的事——用定时器把这段脉宽精确测出来。晶振有多准,时间测量就有多准,省掉了一堆模拟前端。这也解释了为什么老掉牙的NE555到今天还能在新设计里出现:它做“RC到时间”的转换非常可靠。
1.2 单稳态测量原理
NE555有三种典型工作模式,这里用的是单稳态。平时3脚输出低电平,2脚收到一个负脉冲后,3脚跳高,同时内部放电管断开,标准电容或被测电容开始通过基准电阻充电;当电容电压充到阈值引脚检测到2/3VCC时,输出跳低,放电管重新导通把电放掉。
这个输出高电平的持续时间对应充电时间,公式是T=1.1RC。R是充电回路电阻,C是充电电容。测量电阻挡,就串入一个固定基准电容测未知电阻;测量电容挡,就串入一个固定基准电阻测未知电容。用一个挡位切换开关,把固定元件和待测元件互换,就能实现双测量,逻辑非常清晰。
关于为什么用单稳态而不是常见的无稳态振荡器测频率,这里说下我的考虑。无稳态模式下NE555连续输出方波,测频率也能算出RC,但频率测量对占空比不敏感,而且窄脉冲高、宽脉冲低的问题会让量程覆盖不够。单稳态把信息集中在一次脉宽里,每次测量时间短,适合做成手持仪器。
1.3 系统组成与工作流程
整体硬件分成三个部分:NE555单稳态测量前端、STM32测量与控制核心、OLED显示和人机交互。测量时,STM32先通过I/O口给NE555的2脚一个约10us的负脉冲,等输出脚跳高,立刻启动输入捕获计时;等到输出脚跳低,捕获结束,计数得到的脉宽就能换算出电阻或电容。
为了覆盖宽量程,我会在基准元件侧预留几个挡位,例如基准电容取10nF、100nF、1uF,基准电阻取1k、10k、100k,可以用CD4066模拟开关切换,也可以用继电器。实际使用时,如果当前挡位算出来的数值超出量程,软件会自动切到下一挡,重新触发测量。
2. 硬件设计与关键电路解析
2.1 NE555单稳态电路细节
关键电路连接方案:5V供电,2脚通过一个10k电阻上拉到VCC,2脚直接引到MCU触发引脚;6脚和7脚接在一起,通过基准电阻R到VCC,通过被测电容C到地。3脚输出接STM32捕获引脚。我用的是LM555CN,国产NE555也没有问题,内部放电管的导通时间都在几百纳秒级别,不影响整体精度。
触发脉冲宽度有讲究。NE555手册规定触发负脉冲的宽度理论上要小于输出高电平时间,否则可能锁存输出。我实际用10us宽度,对大部分被测元件都成立,因为最小脉宽出现在最大量程时也有几百微秒。如果自制时触发脚直接接地持续太久,输出会被“钉”在高电平,这是新手最常见的坑。
关于充电回路的走线,有一个容易忽略的点:7脚放电管导通时,内部实际上是接到地的,如果不小心把7脚和6脚的走线分开太远、中间有长线,寄生的电感可能会让波形出现振铃,影响阈值判断。尽量让R、C、供电退耦靠近NE555放,面包板上实验可以,做成PCB之后读数会更稳定。
2.2 挡位切换与基准元件选择
挡位切换我用的是CD4066,四个独立模拟开关,一个芯片解决四路切换。导通电阻大概几十到一两百欧,在毫秒级的RC时间里影响很小,但在测量小电阻时必须做短接校准,把导通电阻从结果里扣掉。如果追求极致低阻测量,建议用继电器或者双路开关并联,导通电阻可以降到毫欧级别。
基准元件的选择直接决定最终精度。基准电容我优先选CBB聚丙烯电容,温度系数稳定、损耗小,10nF、100nF各备几颗;基准电阻选1%精度金属膜电阻,如果手头有0.1%的更好。实测10nF的国产CBB电容,实际容值可能偏了2%左右,所以后面讲到标定时,我会强调先测量并校准基准值。
供电方面要留意,NE555用5V,STM32一般3.3V,两者之间做电平匹配。我把NE555输出通过一个1k电阻直接接到STM32引脚,引脚内部的保护二极管会限制高电平到3.3V多一点,实测没问题。如果你不放心,也可以外接一个3.3V稳压管或分压电阻,总之别直接硬接5V高电平给MCU引脚,长时间高压有可能损坏引脚。
2.3 STM32最小系统与接线
核心板我用了一个STM32F103C8T6的小板,8M晶振、复位电路、BOOT0拉低,都是标准配置。这里要注意,如果GPIO上电默认状态不确定,触发的负脉冲和NE555的初始状态配合不好,所以我会在代码里先把触发引脚配置成推挽输出并置高,初始化完成后再开始第一轮测量。
LCD用0.96寸I2C OLED,SCL和SDA分别接到PB6、PB7;定时器捕获我用PA0,对应TIM2_CH1。还有一个按键用来切换测量模式(电阻/电容/自动),接在PA4上做外部中断。整个系统一共没多少连线,但建议做成一块PCB,一是减少寄生电容,二是探头可以用排针直接插,方便测试。
3. 固件实现:定时器捕获与数值换算
3.1 初始化与定时器配置
我采用STM32标准外设库写的工程,Keil5环境下编译,用ST-Link下载。先初始化系统时钟到72MHz,然后配置GPIO和TIM2。定时器设置成输入捕获模式,分频系数设为35,这样计数频率是72MHz/36=2MHz,每个计数对应0.5微秒,能分辨大约0.5微秒的时间差,对应电容挡的小电容,能做到几十皮法级别的分辨率。
配置代码:
void TIM2_Cap_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler = 35; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x0F; TIM_ICInit(TIM2, &TIM_ICInitStructure); TIM_Cmd(TIM2, ENABLE); TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE); }滤波参数0x0F是为了滤掉输出波形上的毛刺,但对正常的几百微秒脉宽没有影响,实测读数稳定很多。如果你用的是HAL库,逻辑完全一样,只是API名字换一下,关键还是分频系数和捕获极性。
3.2 完整测量流程与滤波
触发和测量在主循环里做。先通过PA1输出一个10us的负脉冲,随后在中断里记录上升沿和下降沿的计数值,两者相减得到脉宽。因为TIM2是16位的,最大计数值65535对应约32.7毫秒,而单稳态的最大脉宽通常不超过几十毫秒,所以直接用32位变量拼接处理,避免计数回绕导致测负。
每次测量我会连续测9次,去掉一个最高和一个最低,剩下7次取平均值,再做一次滑动平均。这样处理之后,电源纹波或接触抖动引起的偶然跳变就被压掉了。中值滤波对突然的尖峰特别有效,滑动平均能找到真实趋势,两者配合之后,读数小数点后两位基本不跳。
这里有一个细节:触发脉冲输出之后,NE555输出要经过一个上升沿才能被捕获到。如果代码在发出负脉冲后立刻去读捕获寄存器,有可能读到上一次的残留值。我在主循环里会加一个标志位,只有捕获中断真正置位之后才进入计算逻辑,否则继续等待。
3.3 数值换算与自动换挡
换算很简单,都是前面那条T=1.1RC公式。测量电阻时,R_x = pulseTime_us / (1.1 * C_ref_uF);测量电容时,C_x = pulseTime_us / (1.1 * R_ref_kOhm)。代码里把时间转为微秒再计算,显示时自动选择单位:
float pulse_us, Rx, Cx; if (mode == MODE_RESISTOR) { Rx = pulse_us / (1.1f * C_ref_uF); show_resistance(Rx); } else if (mode == MODE_CAPACITOR) { Cx = pulse_us / (1.1f * R_ref_kOhm); show_capacitance(Cx); }自动换挡逻辑是这样的:当前挡位算出来的值如果超出本挡最佳量程,就通过CD4066控制引脚切到下一挡,再触发一次测量;连切三次还是超范围,就显示超出量程。自动换挡虽然会带来一点延迟,但换来的是从几十皮法到几百微法都能自动找到合适的挡位,体验比手动按键好很多。
4. 标定流程与误差分析
4.1 误差来源逐项拆解
我先把误差来源列一下。
第一,NE555本身的阈值精准度。理想单稳态公式里的1.1是个近似系数,实际芯片内部比较器的分压电阻有一定离散性,大概在1%到3%之间,这是所有NE555方案的固有偏差。
第二,基准电容、基准电阻自身的精度。普通的CBB电容标称5%,金属膜电阻标称1%,它们直接把误差传给结果。这也是我建议基准电阻至少选1%精度,基准电容尽量选CBB或涤纶的原因。
第三,定时器时基误差。STM32使用8M晶振,晶振本身的精度一般20ppm,几乎可以忽略,但如果直接使用内部RC振荡器,误差可能会到1%以上。所以晶振一定不要省。
第四,PCB寄生电容。测量小电容时影响非常明显,哪怕是两块焊盘之间的寄生电容都能让10nF以下挡位的读数偏离几个百分点。
第五,CD4066的导通电阻在低阻挡会有零点几欧的贡献,对小电阻测试影响大。把这些叠在一起,普通DIY如果不做标定,误差大概率在3%到5%,这个精度做定性判断够用,但做元件配对筛选就很勉强。想达到1%量级,必须校准。
4.2 软件标定的具体做法
我的标定思路很直接:准备几个已知高精度的电阻和电容,分别测量,然后用实际值反推修正系数。比如测10k电阻时,标称值10000欧,实际测量值是9850欧,说明这一挡存在大约1.5%的整体系统偏差。我把它折算成一个比例系数,存到Flash里,之后每次计算都乘上这个系数。
电阻挡和电容挡分别标定,因为两者的误差源不同。对同一挡位,我至少选两个点做标定:一个接近量程下限,一个接近量程上限。如果两个点的修正系数一致,就说明系统线性度好,用一个系数就行;如果系数不一致,我会用线性插值修正。实测下来,NE555单稳态的线性度其实相当不错,大部分挡位一个系数就够了。
提示:标定之前先给设备通电预热5分钟,让芯片温度稳定下来。NE555的阈值电压会随温度略有变化,热稳定后再标定,比一开机就标定准确得多。
4.3 实测数据与误差对照
下面是我用这套设备测出来的一组数据,基准电阻是10k的金属膜电阻,基准电容是100nF的CBB电容,标定之后得到的对比:
| 待测元件标称值 | 实测脉宽(us) | 计算值 | 万用表/示波器参考值 | 偏差 |
|---|---|---|---|---|
| 10k电阻 | 1123 | 10.21k | 10.03k | 1.8% |
| 100k电阻 | 11045 | 100.4k | 99.8k | 0.6% |
| 330k电阻 | 36520 | 332.0k | 331.5k | 0.15% |
| 10nF电容 | 116 | 10.55nF | 10.2nF | 3.4% |
| 100nF电容 | 1098 | 99.8nF | 99.5nF | 0.3% |
| 1uF电容 | 11020 | 1.002uF | 995nF | 0.7% |
小电容挡偏差大,主要原因就是PCB和引脚上的寄生电容并联到了基准电容上,导致计算值偏大。解决方法是测量小电容时,先把测试夹悬空测一次基准值作为“零容”,再用待测电容减去这个零容值,能明显改善。比如10nF那组,零容值是十几pF,减掉之后偏差就降到1%以内了。
5. 常见问题与排查技巧实录
5.1 读数跳动、偏差大怎么查
很多朋友做出来第一版,最常见的现象就是读数不停跳。按照下面的顺序排查,基本能解决:先用示波器看NE555输出波形,如果波形边缘有毛刺,检查5V供电纹波和退耦电容,NE555的VCC旁边加一个100nF的陶瓷电容必不可少;如果波形正常但读数跳,多半是接触不良,测试夹或杜邦线松动;再不行就检查STM32的输入捕获引脚是否被其他外设复用,PA0经常被调试接口之类占用。
| 故障现象 | 可能原因 | 处理办法 |
|---|---|---|
| 读数随机跳动 | 电源纹波、滤波不够 | 增加100nF退耦、加中值滤波 |
| 误差偏大且固定 | 需要标定 | 做单点或两点标定 |
| 小电容测不准 | 寄生电容干扰 | 用零容测量法扣除 |
| 大电阻没反应 | 超时时间太短 | 拉长超时周期到100ms |
| 高电平烧不进MCU | 电平不匹配 | 串电阻或3.3V稳压 |
5.2 测量范围异常与换挡失效
我遇到过最奇怪的问题,切换挡位后数值完全不对。查了半天发现是CD4066的两个通道引脚顺序接反了,导致该通的通道没通,不该通的反而通了。模拟开关不是机械开关,导通方向没有区别,但引脚分布容易看错,接线前一定要对着数据手册核对一遍。
另一个常见情况是大电容测量时,STM32已经切换到下一挡,但上一挡的电容还在充电,放电管没有完全把电荷放完。单稳态触发后,7脚放电路径导通,理论上是放电很快,但有些电解电容的残余电压需要更长时间泄放。我后来在每次测量结束后,加了一个20ms的放电等待,问题就消失了。
5.3 我的一点实操心得
做完这台仪器,我最大的体会是:精度不是靠堆元件,而是靠标定和系统设计。NE555的边缘精度其实很普通,但它胜在线性度高、重复性好,只要把基准元件和软件修正搞定,就能让一台元器件成本不到20块的小工具,干出接近桌面LCR表的活。
如果你也想做,建议从万用表能测准的范围起步,先做10k电阻和100nF电容这两挡,把波形、读数、误差全部调通之后,再加量程和自动换挡。别一上来就追求几十皮法和几兆欧,范围拉得越大,要处理的坑就越多。做成之后,平时选元件、配对电阻、估算电容,真的比靠色环和丝印靠谱太多。