1. PT100的决定性优势:为什么我在测温项目里弃用NTC换铂电阻
1.1 从一次失败的NTC方案说起
做温度采集板时,头一回把PT100的信号链从惠斯通电桥一路做到单片机代码,踩的坑比想象中多。去年产线上一块三路测温板读数飘得厉害,最后发现不是PT100的问题,而是电桥参考电阻温漂惹的祸。这篇文章就把我当时如何选桥臂电阻、配仪表放大器、写单片机采样与校准代码的完整过程写出来,希望对打算用PT100做测温项目的朋友有帮助。
先说为什么换掉NTC。NTC热敏电阻价格便宜、灵敏度高,1Ω的阻值随温度变化能到几十欧,听起来很香。但它的致命伤有两个:一是阻值-温度曲线是强指数关系,想覆盖宽量程必须做多点标定;二是批次离散性大,同一个型号焊上去,20℃时阻值都可能差出好几个百分点。而且NTC的长期稳定性也一般,用久了阻值会漂。做工业级温度采集时,这三个特性叠加,基本可以劝退了。
PT100正好相反,它的核心价值在于标准性强、重复性好、长期稳定。铂电阻由IEC 60751标准约束,0℃时基准阻值100Ω,温度每升高1℃阻值大约增加0.385Ω。阻值变化很微弱,这既是它的劣势,也是它能保证精度的原因——纯金属铂的物理特性决定了它的漂移小、一致性好,不用像NTC那样逐颗标定。
1.2 三种测温传感器的选型对比
如果手头正在纠结用哪种温度传感器,我用实际项目经验给你列个对照表,帮助快速判断。
| 类型 | 灵敏度 | 线性度 | 互换性 | 典型精度 | 主要麻烦 |
|---|---|---|---|---|---|
| NTC热敏电阻 | 很高 | 差 | 差 | ±0.5~1℃ | 批次离散大,不适合宽量程 |
| 热电偶 | 电压信号弱 | 非线性 | 一般 | ±1~2℃ | 需要冷端补偿,参考端处理麻烦 |
| PT100 | 0.385Ω/℃ | 较好 | 好 | B级±0.3℃,A级±0.15℃ | 微弱信号,需要放大与调理电路 |
热电偶能干到上千摄氏度,这是PT100比不了的,但在-50℃到+300℃这个常见区间,PT100的稳定性和线性优势很明显。关键是热电偶还要做冷端补偿,那个补偿点本身就会引入误差,处理不好温度不准。PT100不需要任何补偿电路,测出来就是真实的电阻变化,后续靠查表或者多项式就能还原出温度。
1.3 精度等级选择:A级和B级怎么取舍
再补充一个选型细节。PT100探头会标A级、B级、1/3B级等精度等级,不是越贵越好,得看系统整体误差分配。
以0℃附近的B级为例,允许偏差是±0.3℃,A级是±0.15℃,1/3B级做到±0.1℃以内。我的习惯是:控制精度目标在±0.5℃以上的场合,B级完全够用;目标在±0.2℃左右,才上A级。真正决定最后精度的往往不是探头本身,而是后面的电桥参考电阻、运放失调、ADC参考电压和校准质量。我见过不少项目买了A级探头,结果参考电阻用普通贴片电阻,温度一升高读数跟着飘,白白浪费钱。
2. 惠斯通电桥的参数计算:从0℃零点到满量程增益
2.1 为什么偏偏要用电桥
PT100的阻值变化只有0.385Ω/℃,如果直接用电阻串联分压,把PT100和一个固定电阻串在一起取中点电压,电源电压一波动,读数就会跟着动。电桥的价值在于差分输出——把两个支路的电压相减,共模干扰被抵消掉,电源波动对两个支路的影响基本一致,所以差电压对电源变化不敏感。
另一个原因是PT100信号本身太微弱。0℃到100℃只有38.5Ω的变化,如果不做差分处理,直接单端测,信号里混进的噪声很难过滤。电桥把电阻变化变成毫伏级差分电压,再送到仪表放大器放大,信号质量会干净很多。
2.2 桥臂电阻取值与输出范围的计算
搭建最常用的四臂电桥,两个固定电阻R1和R2相等,取1kΩ或更大,目的是限制流过PT100的电流。上支路中点电压是稳定的分压值,下支路中点电压随PT100阻值变化。
我实际用的配置是R1=R2=1kΩ,参考电阻R3=120Ω,PT100接在R3对面那个桥臂。输出差分电压公式为:
Vout = VCC × (R2 / (R1 + R2) - Rpt / (R3 + Rpt))
当R1=R2,且VCC=3.3V时,第一个分式正好是0.5。代入不同温度下的阻值,可以得到下面这张表:
| 温度 | PT100阻值 | Vout(R3=120Ω) | Vout(R3=100Ω) |
|---|---|---|---|
| 0℃ | 100.00Ω | +150mV | 0mV |
| 50℃ | 119.40Ω | -9mV | -129mV |
| 100℃ | 138.50Ω | -118mV | -266mV |
这里有个关键选择:R3取120Ω时,0℃输出为正,避免运放输出靠近0V时的非线性区;R3取100Ω时0℃正好平衡,但输出会变成负电压,处理起来麻烦。我倾向用R3=120Ω,然后用一个偏置电压把整段信号抬到ADC能采的正电压范围,或者直接用带双极性输入的ADC。
2.3 增益怎么算:以0~100℃量程为例
假设测温范围0℃到100℃,用R3=120Ω,0℃时输出+150mV,100℃时输出-118mV,峰峰跨度约为268mV。如果ADC参考电压是3.3V,想让信号尽量吃满ADC量程,增益至少是3.3V/0.268V≈12倍。实际取10倍左右,留出余量防止放大后信号削顶。
用AD620这类仪表放大器时,增益公式是G=49.4kΩ/Rg+1。要得到10倍增益,Rg=49.4kΩ/(10-1)≈5.49kΩ,取标称5.6kΩ,实际增益约9.8倍。这是我在面包板上实测出来的经验值:选放大倍数时不要贴得太满,否则探头老化、温度过冲时信号容易超出ADC范围。
2.4 电桥设计的四个隐蔽深坑
这里把我的踩坑记录集中列一下,这四个问题几乎每个新手都会遇到。
参考电阻R3选了普通贴片电阻。普通电阻温漂几百ppm/℃,环境温度变化10℃就能让读数偏出0.5℃以上。必须用低温漂精密电阻,我推荐0.1%精度、25ppm/℃起步,想做好就上5ppm的金属箔电阻。
电桥激励和ADC参考不是同一个源。PT100测量的美妙之处在于可以做成比率测量,电桥电源和ADC参考都用同一个3.3V,电源波动会同时影响电桥输出和ADC参考,最终计算结果基本不受电源影响。如果电桥用3.3V、ADC参考用另外的基准,那么两个源的噪声就都会进入结果。
流过PT100的电流太大。电流越大,PT100自身发热越明显,这就是自热效应。我用R1=R2=1kΩ、3.3V供电时,PT100支路电流约2.7mA,悬空测试时温度读数会虚高0.3℃左右。后来把R1、R2换成2.2kΩ,电流降到1.3mA左右才压住了。稳妥的做法是把电流控制在0.5~1mA以内。
长导线电阻直接叠加进测量结果。如果PT100离主板有几十米,导线电阻可能有好几欧姆,这对0.385Ω/℃的PT100来说是非常大的误差。短距离可以用两线制,超过1米建议用三线制,更远就用四线制恒流源方案。
3. 硬件落地的几个关键判断:参考电阻、自热、走线与接地
3.1 元件清单与实测配置
下面这份清单是我实际使用过的组合,可以直接抄作业。
- PT100探头:A级或B级,明确测温范围和引线长度
- 精密参考电阻R3:120Ω,0.1%精度,25ppm/℃低温漂
- R1、R2:2.2kΩ,0.1%精度,普通金属膜即可
- 仪表放大器:AD620或INA128,增益电阻5.6kΩ
- 单片机:STC15F2K60S2,后续量产换过STC8系列
- ADC:方案A用STC内置10位ADC,方案B用ADS1220
- 电源:3.3V低纹波LDO,电桥、仪表放大器、ADC参考共用
- 去耦电容:每个运放电源脚放0.1μF陶瓷电容,靠近芯片引脚
面包板可以先验证逻辑,但我不建议在面包板上做高精度测试。PT100信号是毫伏级,面包板簧片接触电阻和长跳线天线效应都会让数据乱跳。我通常是面包板跑通代码框架,然后立刻画一块小板子,四层板或双层板都行,注意地平面完整。
3.2 供电、接地的具体做法
供电顺序是:外部电源进板,先经过LDO降到3.3V,再用电感或者磁珠分成模拟3.3V和数字3.3V。电桥和ADC的参考电压都从模拟3.3V取。模拟地和数字地在PCB上单点连接,连接点放在ADC芯片下方。这样数字开关噪声就没有机会在电桥输出回路里形成环路电流。
电桥到运放之间的走线要短,尽量走内层或者包地。如果要用飞线连接外部PT100探头,导线采用双绞线,信号地与屏蔽层单端接地。我在现场排过好几次故障,最后发现都是因为信号线和24V继电器输出线平行走了半米,噪声全耦合进来了。
3.3 自热效应和三线制接线
自热效应不太起眼,但最容易让实测温度偏高。PT100探头本体是有热阻的,如果内部电流产生的功率没法及时散掉,探头测到的温度就比环境高。公式很简单:P=I²×R。0.5mA时在100Ω上耗散0.025mW,温升可以忽略;2mA时耗散0.4mW,探头温升可能到0.1~0.5℃。所以设计目标就是把电流压到1mA以下,同时避免把电流给得越低越好,因为信号太弱也会被噪声淹没,需要平衡。
三线制接法的原理,本质上是用一根线把导线电阻抵消掉:一根线向PT100供电流,另外两根线分别接在PT100两端,分别测量电压和参考电阻。由于电流线和电压线分开,电流在导线上的压降不会进入差分测量路径。实际接线时,三根线要等长、同等材质,否则导线电阻差还是会带来误差。
4. 单片机里的转换算法:从ADC原始码到温度值的三种做法
4.1 ADC采样与数字滤波:先别急着换算
很多代码把每次ADC读到的原始值直接丢进温度公式,出来的数据能跳得你怀疑人生。原因很简单:运放放大了信号的同时也放大了噪声,ADC单次采样的结果包含了大量随机噪声。
我的做法是先做软件滤波。常用滑动平均或者中值平均。比如连续采32次,去掉最大值和最小值,再对剩下30个点求平均。这样能让10位ADC的有效分辨率稳定很多。实测下来,原始数据跳动±5个LSB,滤波后能压到±1个LSB以内。
代码可以这样写:
#define FILTER_N 32 unsigned int adc_filter(unsigned char channel) { unsigned char i; unsigned int max = 0, min = 4095; unsigned long sum = 0; for (i = 0; i < FILTER_N; i++) { unsigned int val = adc_read(channel); if (val > max) max = val; if (val < min) min = val; sum += val; } return (unsigned int)((sum - max - min) / (FILTER_N - 2)); }注意STC15内置ADC是10位,读回结果是0~1023。如果用16位ADC方案,把数据类型改成unsigned short甚至unsigned long,别在类型转换上栽跟头。
4.2 两点校准:消除运放失调和电阻容差
电桥的参考电阻有0.1%误差,仪表放大器有输入失调电压,ADC也有零偏。这些误差叠加起来,会让同一个温度点测出来的代码跟理论值差出不少。两点校准就是为了把这些固定偏差一次性消掉。
具体做法是准备两个标准温度点。我常用0℃冰水混合物和100℃沸水。等探头稳定后,记录两个ADC平均码值,分别记为code0和code100。之后每次采样得到的code,通过线性映射转成温度:
float code_to_temp(unsigned int code) { return (float)(code - code0) * 100.0f / (float)(code100 - code0); }这里的100.0f对应校准温差的100℃。如果你的标准点不是0和100,改成实际温度差就行。这种线性映射在0~100℃内最大误差大约0.3~0.4℃,因为PT100本身的非线性被忽略了。想要更高精度就得用下面的查表或多项式。
4.3 查表加线性插值:8位单片机的最优解
PT100的电阻-温度关系用10℃间隔存一张表,然后线性插值,这个做法在8位单片机上非常友好,不用浮点运算也能保持较高精度。
我用的表是从0℃到100℃,每10℃一个点,对应PT100标准电阻值。数组如下:
#define TABLE_N 11 const float rtd_table[TABLE_N] = { 100.00, 103.90, 107.79, 111.67, 115.54, 119.40, 123.24, 127.07, 130.89, 134.70, 138.50 };根据测得的电阻值R,在表里找到相邻区间,线性插值得到温度:
float rtd_to_temp_table(float R) { unsigned char i; float t_lo, t_hi, r_lo, r_hi; if (R <= rtd_table[0]) return 0.0f; if (R >= rtd_table[TABLE_N - 1]) return 10.0f * (TABLE_N - 1); for (i = 0; i < TABLE_N - 1; i++) { if (R >= rtd_table[i] && R <= rtd_table[i+1]) { r_lo = rtd_table[i]; r_hi = rtd_table[i+1]; t_lo = 10.0f * i; t_hi = 10.0f * (i+1); return t_lo + (R - r_lo) * (t_hi - t_lo) / (r_hi - r_lo); } } return -1.0f; }每10℃一段的线性插值,误差远小于PT100本身的B级偏差,足够大多数场景用。想要覆盖负温区间,把表头扩展成-50℃、-40℃这些负值点即可,逻辑不用改。
4.4 用Callendar-Van Dusen公式做全量程换算
查表法精度已经不差,但如果不想维护一张大表,又需要全量程连续输出,CVD公式是更好的选择。0℃以上时:
R(T) = R0 × (1 + A×T + B×T²)
其中R0=100Ω,A=3.9083×10⁻³,B=-5.775×10⁻⁷。这个二次方程可以直接解,单片机里写个求根函数:
#include <math.h> float rtd_res_to_temp_cvd(float R) { float a = 100.0f * (-5.775e-7f); float b = 100.0f * 3.9083e-3f; float c = 100.0f - R; return (-b - sqrtf(b*b - 4.0f*a*c)) / (2.0f*a); }这个函数在0~300℃区间误差可以做到0.01℃量级。要注意0℃以下时,CVD公式要加入一个额外的C项,公式形态会变,负温区间我更推荐直接用查表法。另外在STC15这类不带硬件浮点的单片机上运行平方根运算,单次计算大约几百微秒,对温度采样这种低速场景完全够用。
5. 附上两套可跑代码:STC内置ADC方案与ADS1220高精度方案
5.1 方案A:STC15内置10位ADC,低成本快速验证
如果只是做原型验证,STC15的10位ADC加运放方案是最快路径。STC15系列的ADC用ADC_CONTR寄存器控制,读取高位结果存ADC_RES,低位结果存ADC_RESL,拼成10位。
#include "STC15F2K60S2.h" #define ADC_POWER 0x80 #define ADC_START 0x08 #define ADC_FLAG 0x10 void adc_init(void) { P1ASF = 0x07; // 将P1.0~P1.2设置为模拟输入 ADC_CONTR = ADC_POWER | 0x60; // 开启ADC电源,选择时钟分频 delay_ms(1); } unsigned int adc_read(unsigned char channel) { ADC_CONTR = ADC_POWER | ADC_START | (channel & 0x07); while (!(ADC_CONTR & ADC_FLAG)); ADC_CONTR &= ~ADC_FLAG; return (unsigned int)(ADC_RES << 2) | ADC_RESL; }主循环里做的事情就是滤波、校准、查表,最后通过串口或者数码管输出。整个工程结构很清爽:
void main(void) { unsigned int code_val; float rtd_res; float temp; adc_init(); uart_init(); while (1) { code_val = adc_filter(0); // 多路选择,P1.0对应通道0 rtd_res = code_to_res(code_val); // 根据校准和增益换算电阻 temp = rtd_to_temp_table(rtd_res); // 查表插值 printf("Temp: %.1f C\\n", temp); delay_ms(500); } }这里code_to_res函数需要你把电桥输出电压、运放增益、ADC参考电压和校准点一起换算成电阻值。最简单的办法是用两个已知电阻做两点校准,反推系数k和b,然后rtd_res = k * code_val + b。
5.2 方案B:ADS1220实现0.1℃级别精度
如果项目要求0.1℃稳定度,我会直接上ADS1220。这是一颗24位Δ-Σ ADC,内置PGA和两个可编程恒流源,专门为RTD测量设计。使用比例测量接法,电流源流过PT100和一个精密参考电阻RREF,RREF上的电压同时作为ADC参考电压。这样一来,电源漂移、电流源漂移都被自动抵消了。
典型配置是:IDAC电流设为500μA,RREF取1.5kΩ,PGA增益16倍,差分输入接AIN0和AIN1。SPI读取转换结果的框架如下:
unsigned long ads1220_read(void) { unsigned long raw = 0; ADS1220_CS = 0; while (ADS1220_DRDY); // 等待转换就绪 raw = (unsigned long)spi_read_byte(); raw = (raw << 8) | spi_read_byte(); raw = (raw << 8) | spi_read_byte(); ADS1220_CS = 1; return raw; }把转换码换算成PT100电阻:
float ads1220_to_rtd(unsigned long raw) { float rref = 1500.0f; // 参考电阻1.5kΩ float gain = 16.0f; // PGA增益 return (float)raw * rref / (8388608.0f * gain); }ADS1220的寄存器配置必须对着数据手册来,重点是把恒流源选择到正确的引脚,参考电压选择为外部参考。不同板子的配置差异主要在IDAC电流和PGA增益,建议按实际探头引线长度和量程调整。
5.3 代码层面的几个工程陷阱
第一,STC内置ADC的参考电压其实就是VCC。如果你的3.3V LDO精度一般,那么绝对温度就会有偏差。办法是保留校准流程,或者把ADC参考切换到内部高精度带隙基准,具体看型号是否支持。
第二,很多STC系列单片机C语言代码跑着跑着就复位,有人怀疑是不是没有堆栈。这里说明一下,51单片机的C语言是有堆栈的,只是栈空间默认在IDATA区顶部,空间极小,通常只有几十字节。如果函数嵌套太深,或者定义了较大的局部数组,栈很容易溢出然后程序乱飞。我在代码里只用了两层函数调用,局部变量也尽量用全局数组复用,规避这个问题。
第三,校准参数一定要存到EEPROM或者Flash的专门区域,不能每次上电重新校准。我把code0、code100这些参数放在最后校准后存起来,主程序上电时先读参数再跑。参数异常时应输出错误码,别让系统拿一组错误系数算温度。
6. 实测排查记录:读数漂移、偏低偏高和校准经验
6.1 案例一:环境温度一变,读数跟着变
我调试一块板子时发现,早上校准好是25℃,到中午环境温度升到30℃之后,读数变成了27℃。排查了很久,问题出在参考电阻R3上。我最初用的是普通0805贴片电阻,温漂大约300ppm/℃。12Ω误差算下来,5℃环境变化轻松带来上欧姆的阻值变化,再叠加PT100的0.385Ω/℃,误差自然放大。
排查的方法是用万用表同时监测电桥输出和参考电阻,发现环境温度变化时参考电阻两端电压明显漂移。换成25ppm低温漂电阻后,读数稳定多了。
6.2 案例二:探头悬空与插入水中读数不一致
探头悬空时显示26℃,插入20℃的恒温水槽中却显示20.4℃,悬空状态虚高明显。这是自热效应加上散热条件不同造成的:空气中散热差,PT100自身发热无法散去,温度自然偏高。解决方式是降低激励电流,后来我把流过PT100的电流从2.7mA降到0.9mA,悬空和浸水之间的差距从0.6℃缩小到0.15℃。
凡是遇到探头读数偏高且在不同介质中偏差不同的情况,优先怀疑自热。
6.3 校准实操心得:冰水浴与电阻箱的选择
做两点校准时,0℃的冰水混合物是最廉价的基准。注意要用碎冰加少量水,充分搅拌后等温度稳定,再把探头密封好放进去,等读数不再变化了再记录。沸水校准则要留意当地大气压对沸点的影响,海拔不同,沸点有差异。想更省心,我建议买一台0.01%精度的电阻箱,直接模拟出标准电阻值,不需要准备热水冷水,校准时把PT100换成电阻箱拨到对应阻值就行。
校准顺序也要固定:先校零点,再校满度,再回到零点复核。如果两个零点读数不一致,说明斜率系数有问题,或者电路有严重非线性,不能直接取平均值糊弄过去。
6.4 最终实测效果
整套电路和代码调通之后,我做了一个72小时连续运行测试:恒温箱设定在85℃,每30秒记录一次串口输出的温度值。经过滑动滤波和两点校准后,最大偏差在±0.15℃以内,折算成阻值误差约0.06Ω。虽然还达不到三线制加高精度ADC的0.02℃水平,但对大多数温控和采集项目来说已经完全够用。
后来我又在多路测温项目中复用了这套代码,把电桥、运放、ADC参数封装成了独立模块。每次换量程只需要重新算一遍桥臂电阻和增益,其他逻辑几乎不用动。这也算是我从一堆折腾里沉淀下来的最大心得——测温系统想要稳定,靠的不是某一个高端器件,而是把电桥激励、参考电阻、零点校准这串链条上的每一环都想明白。