如果你做过 HVAC 或者机房环境监控,大概率会碰到这样的需求:本地要实时显示回风温度和盘管温度,同时还得把几十米外的几个独立区域温度汇总到一个面板上统一查看。我做过一个类似的冷站监控项目,最早用的是 NTC 加独立变送器方案,后来又尝试过用主控直接采模拟信号,折腾几次之后,稳定跑下来的组合是:PJ85718DM 数字温度传感器配合 PIC18F87K22 这颗 8 位 MCU。这篇文章就把这套测温系统的完整做法拆开讲一讲,覆盖器件选型、硬件连接、固件逻辑和远程上报,适合正在做嵌入式环境监控、HVAC 控制盒或者设备状态监测的工程师参考。
标题里“本地与远程”这两个词,很多同事一开始理解得比较浅,以为只是传感器放在近处和远处的区别。真正落地之后你会发现,这背后其实是两套完全不同的链路:本地传感器要解决的是多点采集、时序可靠的问题,远程上报要解决的是远距离传输、抗干扰和协议解析的问题。下面就从需求开始,一层层往下拆。
1. 需求拆解:嵌入式与 HVAC 场景里的“本地/远程温度”到底指什么
1.1 两种“远程”的温度:物理布点 vs 系统架构
先厘清概念。在 HVAC 项目里,本地温度通常指控制器所在位置的测量点,比如空调箱的回风口、冷冻水的供回水管、电控柜内部的空气温度。这些点离主板近,一般一两米以内,直接走 I²C 或 SPI 都行。远程温度则是指分布在几十米甚至上百米外的测量点,比如不同楼层的房间温控器、室外冷凝器附近的空气温度、车间远端的热源区域。
但嵌入式系统里还存在第二种“远程”:从 MCU 角度来说,传感器无论接在哪条总线上,都只是物理拓扑上的远;真正的“远程监控”,是把采集到的温度数据通过 RS-485、CAN 或者以太网上传给上位机,让不在现场的人也能看到趋势。所以这份需求实际上包含两层:一是传感器层面的本地与远程布局,二是系统层面的本地控制与远程监控。
1.2 从典型 HVAC 现场提炼出的硬性指标
我那个项目是给一个工厂车间的空调系统做集中控制,现场条件不算极端,但很典型。拆解下来,硬性指标大概是这几条:
| 指标项 | 要求 | 说明 |
|---|---|---|
| 测温范围 | -20℃ ~ +85℃ | 夏季车间温度可升到 45℃ 以上,冬季冷库附近会到零下 |
| 精度 | ±0.5℃ 以内 | 用于回风温度 PID 调节,差太多会振荡 |
| 采集点数 | 本地 6 点 + 远程 4 点 | 10 路通道 |
| 实时性 | 全通道刷新周期 ≤ 2 秒 | 温度本身变化慢,2 秒足够 |
| 通信距离 | 远程最远 120 米 | 采用 RS-485 链路 |
| 通信协议 | Modbus RTU | 方便对接已有的上位机 |
| 供电 | 24V DC/3.3V 稳压 | HVAC 控制柜常见的供电条件 |
| 异常处理 | 断线检测、超温报警、复位恢复 | 无人值守场景 |
这里要注意的是“精度 ±0.5℃”这个指标。如果是用 NTC 加 10 位 ADC,在全温度范围内做到这个精度很吃力,需要逐点校准;而数字温度传感器本身已经把 ADC、基准和线性校准做在芯片内部,压力小得多。这也是我后来换掉 NTC 方案的根本原因。
2. 器件选型背后的取舍:PJ85718DM 与 PIC18F87K22 的组合逻辑
2.1 PJ85718DM:一颗 I²C 数字温度传感器能解决什么
PJ85718DM 是一颗 I²C 接口的数字温度传感器,封装是典型的 8 引脚 SOIC,工作电压 2.7V 到 5.5V,测温范围覆盖 -55℃ 到 +125℃,12 位分辨率下最小刻度 0.0625℃,典型精度能做到 ±0.5℃。它的 I²C 从机地址由 A0、A1、A2 三个引脚的电平决定,默认地址是 0x48,最多一条总线上可以挂 8 个芯片。
真正让它在 HVAC 场景里好用的,是 ALERT 输出引脚。芯片可以配置成窗口比较器模式,当温度超出上下限时,不需要 MCU 轮询,ALERT 引脚自己拉低,可以直接接 MCU 的外部中断或者 PLC 的数字输入。对于需要在温度越限瞬间触发联锁控制的场景,这个特性比纯软件轮询可靠得多。
它内部还有一个 12 位 Σ-Δ ADC,转换时间长一些,大概几十毫秒,但换来的是极低的噪声。用手持万用表的温度档和它对比,稳定下来后末位数字基本不动。相比之下,如果自己用 MCU 内部 ADC 采 NTC,1℃ 以内的波动很常见。
2.2 PIC18F87K22:8 位 MCU 的底气来自哪里
PIC18F87K22 是 8 位 MCU 里相当能打的一颗。80MHz 主频意味着单周期指令执行速度快,温度采集这种任务对它来说很轻松;128KB Flash 和 3.9KB RAM 足够放下协议栈、滤波缓冲区和掉电保护数据;更重要的是它外设很全,两个 MSSP 模块可以独立跑 I²C/SPI,两个 EUSART 可以跑 RS-485,还有一个 12 位 ADC 和 ECAN 模块,后续想扩展 CAN 总线出口也不用换主芯片。
选它还有个很现实的原因:功耗和发热。HVAC 控制柜常年通电,控制板就装在电柜里,如果 MCU 本身功耗大、发热高,反而会影响附近温度传感器的读数。这颗芯片正常工作电流在十几毫安级别,发热可以忽略,对测温系统来说误差源又少了一个。
2.3 为什么不用 NTC 或 PT100
我不是说 NTC 和 PT100 不能用,但在“多点、统一监测、远程上传”这个需求下,它们的综合成本不占优。NTC 方案省钱,但每个通道都要做分压电阻、滤波电容、ADC 校准,6 个点就要校准 6 次,批量生产时校准工时非常痛;PT100 精度高,但需要恒流源或高精度电桥,配套变送器本身就要几十块,远距离传输还得 4-20mA 变送器。
数字温度传感器把校准做好了,直接用 I²C 读出来就是工程单位,省掉了一大堆前端调理电路。这条链路在系统可靠性上的收益,比省那几块钱的 BOM 成本更有价值。
3. 硬件搭建:I²C 传感器巡检链路的连接、供电与抗干扰设计
3.1 地址规划:一条 I²C 总线上挂 8 路传感器
PJ85718DM 的地址引脚 A0、A1、A2 可以接 GND 或 VCC,组合出 8 个地址:0x48 到 0x4F。我做的是 6 路本地温度,分别配了地址 0x48~0x4D,远程节点板上的 4 路用另一颗从机 MCU 采集后打包上传,所以本地总线没有挂满。
设计上要注意:地址引脚不能悬空。悬空的话电平不稳定,上电后传感器可能随机地址,导致初始化时找不到设备。我在第一版原理图里偷懒把 A0~A2 直接没接,结果一批板子有 30% 出现“设备丢失”,查了半天才发现是地址漂移。后来老老实实用 10kΩ 电阻把每个地址引脚拉低或拉高,问题彻底消失。
I²C 总线上拉电阻也要选对。PJ85718DM 在 100kHz 模式下,建议上拉电阻 4.7kΩ 到 10kΩ;如果线比较长、电容大,阻值取小一点,但不能小于 2.2kΩ,否则灌电流过大,低电平可能被拉不到阈值。我这边本地线缆总长约 3 米,用 4.7kΩ 比较稳。
3.2 传感器供电与去耦:最容易翻车的环节
HVAC 控制柜里的 24V DC 是从开关电源来的,纹波不小。我最初直接用一个 AMS1117-3.3 线性稳压给传感器供电,结果发现温度读数会出现周期性跳动,尤其是湿度大的时候更明显。查了一圈,问题是传感器电源引脚上的纹波叠加到了内部基准上,导致转换结果抖动。
解决办法很直接:传感器供电单独走一组 LC 滤波,10μH 电感加 100μF 电容,再在每个传感器电源引脚旁边放一个 0.1μF 陶瓷电容。这样改完以后,读数的稳定性肉眼可见地提升。另外,I²C 的 SDA/SCL 走线尽量短且远离 220V 交流线和继电器输出线。继电器闭合瞬间的拉弧,在示波器上能看到明显的尖峰耦合到 I²C 线上。
| 场景 | 线缆长度 | 上拉电阻 | 去耦电容 | 注意事项 |
|---|---|---|---|---|
| 板内短距离 | < 30cm | 10kΩ | 0.1μF | 基本无压力 |
| 板间走线 | 1 ~ 3m | 4.7kΩ | 0.1μF + 10μF 电解 | SDA/SCL 远离功率线 |
| 柜内长距离 | 5m 以上 | 2.2kΩ | LC 滤波 | 建议改用 RS-485 子板 |
3.3 工业现场的隔离策略:本地 I²C、远程 RS-485 的混合拓扑
这就是我在 1.1 里说的“两层远程”在硬件上的落地。本地 6 路传感器在控制板上直接 I²C 读取,简单高效;远程 4 个点在 120 米外,如果直接拉 I²C 线,信号完整性基本没戏,所以我做了一块远程采集从机板,面板上用 PJ85718DM 采 4 路温度,然后主从机之间用 RS-485 通信。
有人会问,为什么不直接每个远程点挂一个传感器到 RS-485 上?也可以,但每颗 PJ85718DM 本身没有 RS-485 接口,得给每个点配一颗 MCU 或者专门的协议转换芯片,成本反而比集中一块从机板更高。而且现场条件恶劣,多一个设备点就多一个故障点。集中到一块从机板,放在远程区域的接线盒里,供电、防雷、接地都方便处理。
主控板这边,RS-485 收发器选带失效保护的型号,A/B 线之间并联 120Ω 终端电阻。通信线采用双绞屏蔽线,屏蔽层单端接地。如果你在电柜里布线,务必走独立的线槽,别跟变频器输出线绑在一起。
4. 固件实现:温度采集、滤波、报警与自诊断的完整逻辑
4.1 MSSP 主模式初始化与单次温度读取
PIC18F87K22 上电后,先把 MSSP 模块配置成 I²C 主模式。在 XC8 环境下,我习惯把这些初始化都放到一个函数里:
void i2c1_init_master(void) { // 100kHz @ 64MHz SSP1ADD = 159; SSP1CON1 = 0x28; // SSPEN=1, I2C Master mode SSP1CON2 = 0x00; SSP1STAT = 0x80; // Slew rate control enabled }读传感器温度的基本流程是:启动条件 -> 发送从机地址写位 -> 发送命令字节(比如 0x00 表示读温度寄存器)-> 重新启动 -> 发送从机地址读位 -> 读高字节 -> 读低字节 -> 停止条件。MCU 连续读两个字节后,把数据拼成一个 16 位值。
uint16_t read_temp_reg(uint8_t slave_addr) { uint16_t raw; uint8_t msb, lsb; i2c_start(); i2c_write((slave_addr << 1) | 0); // 写命令 i2c_write(0x00); // 温度寄存器地址 i2c_restart(); i2c_write((slave_addr << 1) | 1); // 读模式 msb = i2c_read(1); // 主设备发送 ACK lsb = i2c_read(0); // 最后一个字节发 NACK i2c_stop(); raw = (uint16_t)((msb << 8) | lsb); return raw; }4.2 数据换算、负数处理与温度格式
PJ85718DM 的 12 位温度数据是左对齐的,高字节的高 8 位是整数部分,低字节的高 4 位是小数部分。温度值的 LSB 是 0.0625℃,也就是 16 分之一度。换算公式是:温度 = (msb << 4) | (lsb >> 4),然后除以 16。
负数的情况要小心。温度低于 0℃ 时,12 位数据用二进制补码表示,比如 -1℃ 对应 0xFFF0。如果直接把 16 位数当成无符号整数除以 16,会得到 4095,这就错了。统一做法是先转成有符号 16 位整数,再右移 4 位,除以 16。
int16_t raw_signed = (int16_t)raw; int16_t temp_x16 = raw_signed >> 4; // 放大 16 倍 float temp_c = (float)temp_x16 / 16.0f;我在实际代码里基本不用浮点,直接保留整数部分和小数部分,通过一个联合体拆开。比如 25.0625℃ 显示成 250625,发给上位机时再按 0.01℃ 分辨率换算,省掉浮点库占用。
4.3 滑动滤波与带迟滞的报警逻辑
温度信号本身变化慢,但 HVAC 现场的风机、压缩机启停会引起局部气流波动,导致读数上下跳 0.2℃ 左右。我加了一个 8 次滑动平均滤波,队列里存最近 8 次采样值,每次取平均。这个成本很低,却让后续 PID 控制平滑了很多。
报警逻辑要加迟滞,不然会在阈值附近频繁触发。比如超温报警设为 60℃,恢复值要设成 58℃,这样温度在 59℃ 附近波动时,不会一会儿报警一会儿恢复。PJ85718DM 的 ALERT 引脚在窗口比较模式下也有类似机制,固件里再用软件迟滞做一层保护,双重保障。
#define TEMP_ALERT_HIGH 60.0f #define TEMP_ALERT_LOW 58.0f这个“高报低复”的策略在 HVAC 联锁控制里很关键,否则继电器反复吸合,触点寿命会快速消耗。
4.4 传感器失联与总线超时处理
多点测温系统最怕的不是温度不准,而是传感器丢了之后系统不知道。PJ85718DM 如果从机不响应,I²C 总线上表现为 NACK,但如果传感器损坏导致 SDA 被拉死,主控会一直等不到释放。解决办法是给 I²C 操作加超时保护。
我在代码里用一个循环计数,超过 N 次仍检测不到 ACK 就放弃本次通信,执行 9 个时钟周期的总线恢复,释放可能被从机锁住的总线,再标记该通道为故障。这样任何一个传感器掉线,系统都能继续读其他通道,同时把故障状态通过 Modbus 上报给上位机,而不是整条总线“一挂全挂”。
实时性方面,要保证全通道刷新周期在 2 秒内。温度传感器转换时间几十毫秒,10 个通道顺序读取也就几百毫秒,完全够用。不要为了省电把采样间隔拉太长,否则 PID 控制的滞后会明显变差。
5. 远程汇聚:基于 RS-485/Modbus 的远程温度上报方案
5.1 从机端聚合:把 I²C 多点温度打包成一份数据表
远程从机板的固件逻辑很清晰:每 500ms 读一次板上的 4 路传感器,做同样的滑动平均滤波,把结果存到一份结构体里。然后等待主机通过 Modbus 命令来读。
这里有个很关键的工程细节:从机板上的 MCU 我选的也是 PIC18 系列,但如果你用其他 MCU 更顺手,完全没问题。重要的是从机固件要负责自己本地传感器的健康状态,一个传感器失联,不要把整包数据作废,而是把那个通道的温度字段填成一个约定的异常值,比如 0x7FFF,让主机知道这个点失效了。
5.2 Modbus RTU 寄存器映射与 CRC 校验
Modbus RTU 是 HVAC 行业上位机最通用的协议,基本上所有组态软件都支持。我规划的寄存器表如下:
| 寄存器地址 | 内容 | 数据类型 | 范围 |
|---|---|---|---|
| 0x0000 | 本地通道 1 温度 | int16 | 0.01℃/bit |
| 0x0001 | 本地通道 2 温度 | int16 | 0.01℃/bit |
| ... | ... | ... | ... |
| 0x0006 | 远程通道 1 温度 | int16 | 0.01℃/bit |
| 0x0010 | 系统状态字 | uint16 | bit0~bit9 对应各通道故障 |
CRC16 是 Modbus RTU 的标配,标准多项式 0xA001,查表法或者按位法都行。我直接贴按位计算的实现,因为存储空间够,省事又直观:
uint16_t modbus_crc(uint8_t *buf, uint16_t len) { uint16_t crc = 0xFFFF; for (uint16_t i = 0; i < len; i++) { crc ^= buf[i]; for (uint8_t j = 0; j < 8; j++) { if (crc & 1) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } } return crc; }收发时序上,主机发送读保持寄存器命令,从机在接收到完整帧且 CRC 校验通过后,延迟几十微秒再回复。如果校验失败,从机必须静默,不能回错误帧,否则主机会认为通信链路有冲突。
5.3 主机轮询周期与故障重连策略
主机轮询从机的时间间隔我设置为 1 秒一次,每个从机帧长不到 20 字节,115200bps 下传输时间几乎可以忽略。1 秒的轮询周期足够让上位机画出平滑的趋势曲线。
RS-485 通信最容易出的问题是接线顺序和终端电阻。多台设备菊花链连接,终端电阻只接在链路两端,中间设备不能接。另外,A/B 线极性不能接反,很多现场的“通信不稳定”最后查出来就是有一台设备的 A/B 接反了。碰到连续通信超时,固件里要做重连策略:连续 3 次读超时,标记从机离线;再每隔 5 秒尝试重连一次,不让上位机被错误数据刷屏,也不让总线被无限重试占满。
6. 实测数据与高频问题处理:校准、电压跌落与总线锁死排查
6.1 校准前的误差分布与软件补偿
PJ85718DM 出厂精度 ±0.5℃,但实际装到系统里,误差还包含传感器自身发热、探头外壳热阻、线缆压降带来的参考电压偏差。我实测下来,把传感器直接裸露在空气中,和水银温度计对比,25℃ 附近误差在 ±0.2℃ 内;但套上防水探头之后,由于探头外壳的热阻,温度响应变慢,稳定值也会偏低 0.3℃ 左右。
针对这个,我做了两点处理:一是软件里给每个通道加一个可配置的偏置量,通过 Modbus 寄存器在线校准;二是温度响应慢的探头,采样周期保持 1 秒以上连续读取,别用单次读数做控制跳变。这个偏置参数在批量生产时每台设备可以单独标定,效率比硬调电阻高得多。
6.2 SDA 被拉死的总线锁死:现象与恢复
调试过程中印象最深的一个问题:远程从机板运行一段时间后,主控板上 I²C 总线完全不响应,示波器看 SDA 一直是低电平,像是被某个传感器拉死了。原因基本可以锁定为总线竞争,传感器复位或者电源毛刺导致它误进入异常状态。
后续在固件里加了总线恢复机制,每次通信超时,就对 SCL 连续翻转 9 个时钟周期,让总线释放。这招在绝大多数 I²C 从机锁死场景下都有效,可以理解为“喂一条走廊里的每个人一口水,让全体恢复正常状态”。同时我在每个传感器的电源脚加了 0.1μF 电容,减少复位时的电源毛刺后,锁死概率降到了个位数级别。
6.3 压缩机启停引发的电压跌落与复位
HVAC 现场最恶劣的电噪声来自大型压缩机启动瞬间。有一次调试,压缩机一启动,控制器就重启,看门狗误触发。测量 24V 电源发现,启动瞬间电压跌落超过 5V,维持几十毫秒。这直接导致控制板上的 3.3V 稳压输出跟着跌落,MCU 电压低于复位阈值。
解决组合拳:电源输入端加大电容储能,TVS 管吸收浪涌;MCU 的欠压复位模块使能,设定合适的跌落阈值;看门狗超时时间调到比电源波动时间更长,避免瞬间跌落后又恢复时看门狗提前复位。改完以后,压缩机随便启停,控制器再也没有无故重启过。
6.4 报警误触发的最后排查:传感器探头安装位置
最后一个高频问题不是硬件故障,而是安装位置。回风温度传感器如果装在空调回风口正下方,风机抽风时形成一个局部低温区,读到的温度比实际房间平均温度低 2℃ 还多。送风温度传感器如果太靠近加热盘管,辐射热会让读数偏高。
遇到误报警,我现在的习惯是先问安装位置,再查代码。把探头的安装位置调整到气流的代表区域,避开直接辐射源,并保证探头插入深度足够,很多“报警异常”根本不需要改程序。这点对任何做 HVAC 温度采集的同行来说都非常重要——传感器测的是探头位置的温度,不是空间平均值,安装的人必须明白这一点。