做暖通空调(HVAC)项目的温度采集模块时,我一直在找一个既能把 PT100/PT1000 这类传感器信号测准、又能适应工业现场恶劣环境的方案。最后定下来的是ADS122U04IPWR(TI 的 24 位 Δ-Σ ADC,带 UART 接口)搭配PIC18LF45K50(Microchip 的 8 位 USB 单片机)。这套组合在风机盘管供回水温差、风道温度、冷却水温度这些典型 HVAC 测点上跑得很稳,温度分辨率可以做到 0.01℃ 级别,系统成本却比 PLC 模拟量模块低一个数量级。如果你也在做类似的工业测温节点,这篇文章会把选型理由、硬件连接、固件状态机、标定方法和现场踩坑经验全部摊开讲。
1. 为什么是 ADS122U04IPWR 和 PIC18LF45K50 这个组合
1.1 需求倒推选型
先说需求:HVAC 系统里要测的温度点很分散,风机盘管附近一个探头、风道里一个探头、冷机水管上又一个探头。每个探头到控制柜的距离少说几米,多则二三十米。传统做法是每个探头都走 4~20mA 模拟量回控制柜,再由 PLC 的模拟量模块去读,一块 8 路模拟量输入模块就要上千块。我的目标是一个低成本的 MCU 节点直接采集传感器信号,算好温度后通过总线发给主控,这样整个系统线缆、成本都能降下来。
顺着这个思路选型,传感器信号数字化这步是核心。K 型热电偶精度不够,NTC 热敏电阻在工业场合老化问题明显,最后锁定 PT100/PT1000 铂电阻。PT100 在 0℃ 时只有 100Ω,温度变化 1℃ 阻值只变化约 0.385Ω,要把这么小的变化量可靠解析出来,普通的 12 位 MCU 内置 ADC 基本是开玩笑的,必须上高分辨率 Δ-Σ ADC。
ADS122U04IPWR 的 24 位分辨率、内置可编程增益放大器(PGA)、双路 IDAC 恒流源,这些正好是 RTD 测量的标准配置。但真正让我下决心的,是它罕见的 UART 接口。
1.2 UART 接口的 ADC 好在哪
绝大多数精密 ADC 都是 SPI 或 I2C 接口,这在板级通信没问题,可一旦传感器探头离 MCU 几米远,SPI 的时钟线和数据线在长距离传输时容易出时序问题,I2C 的抗干扰也一般。ADS122U04 的 UART 接口意味着只用两根线(TX/RX)就能和 MCU 通信,而且可以在 ADC 端和 MCU 端各加一个 RS-485 收发器,把单端 UART 转成差分信号,二十米线缆完全没问题。
从系统架构上看,这相当于把“高精度 ADC”和“主控逻辑”从物理上解耦了。探头侧只放传感器和 ADC,采集到的原始数据以数字形式传回,模拟信号在探头端就完成了数字化,沿途线缆上的电磁干扰不会像模拟信号一样直接叠加到测量结果里。这和“传感器就地数字化”的工业测控思路完全一致。
1.3 PIC18LF45K50 在这个方案里的分工
很多人看到 8 位单片机第一反应是“老、弱”,但在这个项目里 PIC18LF45K50 的任务非常明确:跑一个 UART 状态机、做滤波和线性化、再通过 USB 或 RS-485 把温度值上报。这些都是轻量级任务,8 位机完全够用,反而一个 32 位 MCU 或者带系统的主控放在探头上才是杀鸡用牛刀。
选 LF 版本(低电压)是因为整板 3.3V 供电,ADS122U04 的模拟电源也是 3.3V,电平直接兼容,不用做电平转换。PIC18LF45K50 内部带 24MHz 振荡器,可通过 PLL 跑到 48MHz,跑浮点滤波算法不至于太吃力。它还带 USB 2.0 FS 模块,维护时插根 Type-C 线就能当虚拟串口用,调试体验比一堆杜邦线舒服多了。关键参数对比如下:
| 项目 | ADS122U04IPWR | PIC18LF45K50 |
|---|---|---|
| 核心功能 | 24 位 Δ-Σ ADC,内置 PGA/IDAC | 8 位 MCU,48MHz,USB 2.0 FS |
| 接口 | UART(最高 200kbps) | 2 路 EUSART,MSSP(SPI/I2C) |
| 模拟特性 | PGA 增益 1~128,双 IDAC,内部温度传感器 | 12 位 ADC(本项目未用) |
| 供电 | 2.3V~5.5V | 2.0V~3.6V(LF) |
| 工作温度 | -40℃~+125℃ | -40℃~+85℃/105℃ |
| 封装 | TSSOP-16 | TQFP-44 |
2. 传感器接口与硬件连接的几个成败细节
2.1 比例测量才是 RTD 精度的根基
先说 RTD 测温的基本原理:铂电阻阻值随温度变化,PT100 在 0℃ 时 100Ω,到 100℃ 时约 138.5Ω。测量温度本质上就是测量阻值。工程上一般不用恒压源供电然后测电压,因为导线电阻、供电电压漂移都会引入误差。我的做法是恒流源激励加比例测量。
所谓比例测量,就是让同一个 IDAC 恒流源电流同时流过 RTD 和一个精密参考电阻 RREF。ADC 用 RREF 两端的电压作为参考电压,同时测量 RTD 两端的电压。ADC 输出的数字码值等于 (VR TD / VREF) × 满量程,因为 VRTD 和 VREF 都是同一个电流乘上对应电阻,电流的实际大小被约掉了,电源电压波动、基准漂移、恒流源温漂统统不影响精度。
用生活类比解释:这就好比用一把尺子量物体长度,统一使用同一刻度的尺子,尺子的实际长短变了,物体和尺子的比例关系不变,读数照样准。这一点是 ADS122U04 测温方案能远超普通电阻分压法的根因。
2.2 IDAC 电流和参考电阻的搭配计算
这块需要静下心算一算,因为几个参数是相互拉扯的。
第一是自热效应。电流流过 RTD 会发热,导致探头自身温度高于被测对象。PT100 在一个普通探头里的自热系数大约 0.4℃/mW(贴片式)到 2.5℃/mW(玻璃封装)。激励电流取 1mA 时,在 100Ω 上功率为 1mA² × 100Ω = 0.1mW,引起的自热约 0.04~0.25℃,还能接受。如果取 2mA,功率翻四倍,误差就比较难看了。我实际用的是 1mA。
第二是参考电阻取值。IDAC 电流流过 RREF 产生基准电压,这个电压需要落在 ADC 的外部参考输入范围内。我的板子 AVDD = 3.3V,参考电压最好在 1.5V~2.5V 之间,太低了 PGA 输出容易顶到量程,太高了余量不足。电流 1mA 时,RREF 取 1.5kΩ 得到 1.5V 参考电压,在合理区间。
第三是 PGA 增益。温度测量范围计划做到 0~150℃(HVAC 里够用了)。PT100 在 150℃ 时约 158Ω,1mA 激励下 VRTD 只有 158mV。如果 PGA 增益取 8,PGA 输出电压约 1.26V,离参考电压 1.5V 还有 16% 余量,不会削顶。0℃ 时 VRTD×增益 = 100mV×8 = 800mV,占满量程的 53%,整个测量范围内没有满量程浪费,分辨率也足够。如果你要测到 200℃ 以上,PT100 阻值到 175Ω 以上,增益 8 就不太够了,需要把 RREF 调小到 1.2kΩ 或者增益降到 4,总之满量程要留 10~20% 余量。
2.3 四线制连接和外围电路
我画的是四线制 PT100 接法,四根线的分工是:两根线走激励电流,两根线走电压检测。电压检测线上的电流几乎为零,所以检测线上的导线电阻不产生压降误差。ADC 的差分输入端直接接检测线两端,测到的是 RTD 本身的电压,不受线缆电阻影响。
ADS122U04 的 IDAC1 引脚接 RTD 激励线(红色),RTD 另一端接 IDAC2 返回路径或者直接接模拟地。RREF 串在 RTD 回路的低边,跨接到 REFP0/REFN0 引脚。AIN1/AIN2 跨接 RTD 的检测线。这样接线后,ADC 的码值和 RTD 阻值之间就是严格的线性比例关系。外围还需要在电源引脚放 10μF 钽电容和 0.1μF 陶瓷电容的组合,参考引脚附近放 0.1μF 去耦电容。
2.4 布局布线里最容易忽略的雷
这块我栽过跟头。第一次打板时,RTD 的模拟信号线和 UART 数字线平行走了 3 厘米,结果电源一开,读数在 0.3℃ 上下抖动。后来把模拟区挪到板子一角,UART 走线从另一侧出,地平面完整铺铜,抖动立刻降到 0.02℃ 以内。
工业现场还要注意,从探头到 ADC 板子的四根 RTD 线要用屏蔽绞线,屏蔽层单端接地(接到控制柜外壳),千万不要两端都接,否则屏蔽层变成地环路反而引入工频干扰。HVAC 现场经常有变频器、水泵电机在旁边,这些设备产生的共模干扰如果不屏蔽,24 位 ADC 也扛不住。
3. 固件设计:从 UART 命令到温度数据的完整链路
3.1 命令帧结构
ADS122U04 的 UART 配置为 8 数据位、无校验、1 停止位,命令字节定义在数据手册的命令表里。我这边用宏封装,禁止在业务代码里裸写魔法数:
#define ADS_CMD_START 0x08 #define ADS_CMD_STOP 0x0A #define ADS_CMD_RDATA 0x12 #define ADS_CMD_RREG 0x22 #define ADS_CMD_WREG 0x42 #define ADS_CMD_RESET 0x06RREG/WREG 是寄存器读写命令,后面还要跟寄存器地址和数据长度。RDATA 是读取转换结果,一次读 3 个字节,24 位二进制补码。START 启动连续转换,STOP 停止。整个通信模型不复杂,但必须用一个状态机管理,因为 ADC 响应是异步的。
3.2 上电初始化序列
上电后第一件事不是急着配寄存器,而是先复位 ADC,确保它退出未知状态。然后按顺序写入四个配置寄存器:REG0 配置通道选择(AIN1/AIN2 差分输入)和 PGA 增益(=8);REG1 配置数据速率(20SPS,内置 50/60Hz 抑制)和滤波模式(低延迟 FIR);REG2 配置 IDAC 电流(1mA);REG3 配置参考源(外部 REFP0/REFN0)、IDAC 路由和断线检测。
初始化代码骨架如下:
void ads_init(void) { ads_send_cmd(ADS_CMD_RESET); delay_ms(5); ads_write_reg(0x00, 0x10); // MUX=AIN1-AIN2, PGA=8, 按数据手册位域组合 ads_write_reg(0x01, 0x14); // 20SPS, 低延迟滤波 ads_write_reg(0x02, 0x78); // IDAC 电流 1mA ads_write_reg(0x03, 0x02); // 外部参考, IDAC1 输出至 RTD, 断线检测使能 ads_send_cmd(ADS_CMD_START); }注意,这里给的寄存器值是某一套具体拓扑下调通的组合,不同 MUX 接法、不同增益要求下位域组合完全不同。正确的做法是打开 TI 官方数据手册,对着位定义表一行行核对。我有一次图省事直接复制参考代码的配置字节,结果通道切换错了,读数偏了十几度,排查了一晚上。
3.3 连续转换与数据读取状态机
ADS122U04 启动连续转换后,会周期性刷新转换结果。读取流程是:MCU 发 RDATA 命令,ADC 把当前转换结果的 3 个字节返回。但实际过程中 UART 可能因为干扰丢字节,所以我的状态机把“发RDATA-等3字节-校验”做成一个完整超时重试机制:
uint8_t ads_read_data(int32_t *raw) { uint32_t timeout = 100; uint8_t buf[3]; ads_send_cmd(ADS_CMD_RDATA); while (timeout--) { if (uart_available()) { buf[0] = uart_read(); timeout = 100; } else { continue; } if (uart_available()) { buf[1] = uart_read(); } else { return ADS_ERR_FRAME; } if (uart_available()) { buf[2] = uart_read(); } else { return ADS_ERR_FRAME; } *raw = ((int32_t)buf[0] << 16) | ((uint16_t)buf[1] << 8) | buf[2]; // 24 位有符号数符号扩展 if (*raw & 0x800000) { *raw |= 0xFF000000; } return ADS_OK; } return ADS_ERR_TIMEOUT; }这里有一个特别容易忽视的细节:24 位 ADC 结果是有符号数。ADS122U04 的输出范围是二进制补码形式,如果用 int32 直接接收,必须做符号扩展,否则负温度(冰点以下)时读到的码值会被当成 1670 万以上的大正数,换算出来的温度能飙到几百上千摄氏度。我做冷冻水温度测试时第一次就中了这个招。
3.4 PIC18 侧 UART 配置要点
PIC18LF45K50 的 EUSART 模块配置不复杂,但波特率寄存器要算准。我的系统时钟用内部振荡器 PLL 到 48MHz,SPBRG 的设定值等于 (FOSC / (16×Baud)) - 1。9600 波特率下 SPBRG = (48MHz / (16×9600)) - 1 = 311,即 0x0137,需要写到 SPBRGH:SPBRGL 两个寄存器。如果觉得手算麻烦,Microchip 的 MCC 代码生成器可以直接生成,但我还是建议自己算一遍,这样换时钟频率或者改波特率时心里有数。
4. 从 ADC 码值到真实温度:线性化与标定
4.1 比例测量的码值转换公式
拿到 24 位原始码值后,第一步是反推出 RTD 的阻值。因为采用了比例测量,码值和阻值的关系非常简单:
R_RTD = (ADC_Code / 0x7FFFFF) × RREF
以 0x7FFFFF(8388607)为满量程,RREF = 1500Ω,那么码值 4194303(半量程)对应 R_RTD = 750Ω。这一步是纯线性换算,不引入任何额外误差。实际代码里如果不想做浮点除法,可以先把 ADC_Code 左移或者用定点数处理,PIC18 的整数运算效率高得多。我用的是定点数表示法:定义一个 1/256Ω 为单位的小数,全程用 uint32 运算,最后再转成整数部分。
4.2 PT100 的非线性:Callendar-Van Dusen 方程
PT100 在 0~850℃ 范围内,阻值和温度的关系用 Callendar-Van Dusen 方程描述:
R(T)=R0×(1+A×T+B×T²)
其中 R0=100Ω,A=3.9083e-3,B=-5.775e-7。这个二次方程本身已经比“阻值线性换算温度”精确得多。如果测量范围到 0℃ 以下,还需要在低温区加入 C 项:R(T)=R0×[1+A×T+B×T²+C×(T-100)×T³]。
PIC18LF45K50 没有硬件浮点单元,软件浮点也能算,但每次线性化要解一次二次方程,还要做浮点乘法和开方,运算量太大,转换一次温度要花好几毫秒,放到整个扫描循环里很拖节奏。我的做法是查表+线性插值:预先在 0~150℃ 范围内按 0.5℃ 间隔生成 RTD 阻值表(实际上 PT100 是查表得到的 R→T,生成方式是用标准 RTD 表或者方程正算),存到 Flash 里,运行时用二分查找定位区间,再做线性插值。
0.5℃ 间隔的线性插值误差在 0.002℃ 以内,比 ADC 的热噪声还低,这个精度已经远超过 HVAC 控制的需求。如果你连 Fl ash 空间都不想浪费,1℃ 间隔的插值误差大约 0.01℃,也够用了。查表插值不仅快,还避免了浮点库对单片机 Flash 和 RAM 的占用,是一石二鸟。
4.3 两点标定的实操流程
理论计算做得再好,硬件总有偏差:RREF 电阻的实际精度、PGA 失调电压、RTD 本身 0℃ 时的偏差,都会叠加进去。所以必须做标定,标定其实很简单。
我的标定在恒温槽里进行,如果没有恒温槽,用冰水混合物(0℃)和沸水(100℃)也能做粗标,但沸水要注意当地海拔修正。采到的两个原始码值记为 C0 和 C100,对应的标准温度是 T0 和 T100。那么校正公式是:
T_cal = T_raw × gain + offset
其中 gain = (T100 - T0) / (T_raw100 - T_raw0),offset = T0 - T_raw0 × gain。这两个系数算好后写入 PIC18 的数据 EEPROM,掉电不丢失。之后每次上电,读取标定系数。
标定系数一定要存好。我的经验是存两份,一份在主 EEPROM 区,一份在备份区,每次上电校验校验和,主区校验失败自动加载备份区。工业设备没有这种容错机制,现场参数一旦被误写或者 Flash 位翻转,后面的运维排查非常痛苦。标定完成后,我会在固定温度下测 24 小时,看读数漂移,正常应该在 ±0.05℃ 以内。
4.4 滤波策略:不是滤得越狠越好
24 位 ADC 的噪声水平大约是几十个 LSB,换算成温度大约 0.01~0.02℃(取决于增益和基准)。ADS122U04 的 20SPS 模式内置数字滤波器,对 50/60Hz 工频干扰有不错的抑制。我在它后面又加了一个 16 次滑动平均,实测峰峰值从 0.02℃ 降到 0.005℃ 左右,非常可观。
但滑动窗口不能无限加长。HVAC 里风道温度变化本来就慢,16 次平均(采样周期相当于 0.8 秒)完全不影响控制响应。但如果你要测的是快速变化的冷却水温,平均窗口太长会导致温度读数滞后,这时候就要权衡:要么提高数据速率到 100SPS 以上,要么减短平均窗口。我的建议是:响应速度优先的场景用 4 次平均,噪声优先的场景用 16 次平均,具体看控制回路的带宽。
5. 现场调试踩过的坑
5.1 自热效应:1mA 电流也有大误差
前面理论计算过,1mA 激励在 100Ω 上有 0.1mW 的热功率,理论上误差不大。但在实测中我发现,如果把 PT100 探头放在 24AWG 细线、玻璃封装的工业探头里,自热可以达到 0.2℃ 以上。原因很简单:探头内部的导热环境远比理论模型差,热量散不出去。
排查方法很简单:给 RTD 供两种不同的 IDAC 电流(比如 250μA 和 1mA),分别读温度。如果两次读数差超过 0.1℃,说明自热效应不可忽略。处理手段有三种:一是降低 IDAC 电流到 500μA,但 ADC 输入信号幅值减半,噪声会相对变大;二是把探头换成导热胶填充的金属套管,改善散热;三是在标定时把自热偏移一并补偿进去。实际工程里我通常是三管齐下。
5.2 工频干扰:屏蔽层接地方式不对就是白接
现场有一路风道温度读数在 0.1℃ 左右周期性跳动,频率正好 50Hz。示波器一测,RTD 线上的共模噪声有 30mV 级别(已经远大于 ADC 分辨率能接受的水平)。找了半天原因,发现屏蔽层在探头端和控制柜端都接地了,形成地环路。把探头端断开、只保留控制柜端单点接地后,干扰从 30mV 降到 1mV 以下,读数恢复正常。
这个问题在实验室永远发现不了,因为实验室没有变频器、没有大功率电机,地电位是干干净净的。所以做工业测温项目,从画原理图和出线缆图纸那一步就应该定好屏蔽策略:屏蔽层单端接地,接到系统的数字地,另一端悬空处理。
5.3 UART 长线的隐性故障
ADC 板和控制板之间有时需要走几米长的 UART 线。一开始我直接用普通杜邦线飞线,结果数据偶尔出现乱码,温度值会跳变一个很大的值。排查后发现是信号完整性问题:UART 总线在几米长的线缆上反射严重,边沿过冲导致误码。
解决方案是加 RS-485 差分收发器。ADC 板上的 UART 出来接一个 MAX3485 之类的芯片,转成差分信号走双绞线到控制板,控制板上再转回 UART 给 PIC18LF45K50。改完之后 20 米线缆跑 9600 波特率稳如泰山。如果你的系统只需板级短距离,就不用上 485,但哪怕 30 厘米的排线,也不建议用长飞线直连 UART。
5.4 断线检测:ADC 读到满码不是温度
RTD 断线是工业现场最常见的故障场景。PT100 传感器断线后,ADC 输入端浮空,24 位转换结果会跑到正满量程附近(0x7FFFFF 附近)或者负满量程。如果不做处理,主控会看到读数几分钟内直飙 1000℃ 然后报警停机,现场维护人员完全不知道是探头坏了。
处理思路是在配置寄存器里把断线检测功能打开(REG3 的 bit 位),让 ADC 内部周期性检查传感器连续性,断线时返回标志。同时在 MCU 端加一个数值门限判断:转换码值落在满量程正负 5% 以外的区间时,直接判为传感器故障,进入故障安全输出模式。注意不要把门限设得太紧,因为超量程的真实温度也可能产生接近满幅的值,但 RTD 应用里量程是事先确定的,不会真的接近满幅,这个判断是可靠的。
6. HVAC 场景里的进阶玩法
6.1 高精度温差测量的关键
风机盘管的供回水温差是 HVAC 能效控制的核心参数。温差 5℃ 的测量精度比绝对温度 50℃ 的精度更重要,因为控制逻辑是用温差算换热量。我的做法是两路独立的 ADS122U04 分别测供水和回水,然后 MCU 做减法。
这里有个容易入的坑:两块 ADC 板各自的参考电阻 RREF 是不完全相同的(即使标称 1.5kΩ±0.1%),它们的基准差异会直接体现在最后相减的结果里。解决办法:要么选用温度系数小于 5ppm/℃ 的高精密参考电阻,要么对两块板的增益系数单独标定,甚至在板子上共用一个大电流参考电阻,让两路 ADC 都用同一个外部基准,这样温差计算时基准偏差直接抵消。我用的是共用参考电阻的方案,效果最干净。
6.2 与主控通信:Modbus RTU 是最省心的路径
PIC18LF45K50 算完温度后,需要通过总线发给上位机或者楼宇自控网关。HVAC 行业最通用的通信协议是 Modbus RTU,跑在 RS-485 物理层上。我在 PIC18 上实现了简单的 Modbus 从站:03 功能码读保持寄存器(温度值),05 功能码控制报警输出,设备地址用板载拨码开关配置。
温度数据用整数形式上报,分辨率为 0.01℃,也就是把 -50.00℃ 映射为 -5000,50.00℃ 映射为 5000,放入两个保持寄存器(高 16 位 + 低 16 位)。这样上层组态软件直接读寄存器就能显示,不用做浮点转换。PIC18 的 UART1 用于和 ADC 通信,UART2 作为 Modbus 从站口,逻辑上完全分开,互不干扰。
6.3 故障安全与系统联动
换热站或者空调机组控制里,温度测点的故障不能简单地读一个错误数值上报,更危险的是一路信号跳变引发连锁控制动作。我的处理是:
- 故障状态输出一个明确标志位(温度无效),而不是输出一个极端温度值
- RTD 断线、ADC 通信超时、标定系数校验失败,三种情况分别对应三种故障码
- 故障持续超过 30 秒后,把最后有效温度保持住,并同时上报“保持值”状态
- 主控收到故障码后进入安全模式:关闭电动阀、保留风机最小转速或者按预设策略执行
这套逻辑在项目调试阶段就帮我抓住过问题:一次是现场接线端子进水导致 RTD 与外壳短路,另一次是变频器启停瞬间 UART 误码率飙升。没有故障帧设计的话,这两个问题会让整个空调系统莫名其妙地乱跳。
最后的一点实际体会
这套 ADS122U04IPWR + PIC18LF45K50 的组合,我在两套 HVAC 项目的控制柜里跑了快一年。回过头看,选型正确很重要,但更关键的是把每个环节的物理原理吃透——比例测量为什么能消除电流源漂移,查表插值为什么比浮点运算更合适,屏蔽层为什么要单端接地。这些东西手册上可能只有一两句话,但在现场就是决定系统能不能稳定运行的分水岭。
如果你正准备抄这套方案,我最想强调的只有一条:第一版硬件回来后先别急着写满功能,就做一件事,把 ADC 的原始码值通过串口打出来,确认码值稳定、能随温度单调变化了,再往上叠滤波、线性化、通信协议。地基打稳了,后面基本不会出大幺蛾子。