搞嵌入式的都知道,测温度这件事看着简单,可真放到 HVAC(暖通空调)控制系统里,情况就完全变了:控制板自身发热要管,风管里的送风温度要管,回风温度要管,表冷器盘管表面温度也要管。不同位置、不同用途的温度,对应的处理逻辑完全不同——有的用来闭环调速,有的用来做防冻保护,有的只用来判断设备会不会过热。标题里的 PJ85718DM 配 PIC18F86J16,就是一套非常典型的低成本测温与控制组合:前者把本地和远程两路温度测量做进同一颗芯片,后者负责读数、滤波、校准、上报,并把温度换算成风机和压缩机的动作。这篇文章就按实际做项目的顺序,把硬件怎么接、固件怎么写、标定怎么做、控制怎么闭环,以及我最常遇到的坑,一次讲透。想把这套方案用在空调箱控制器、变频风机、冷链监控或者嵌入式设备过热保护上的朋友,可以直接照着走。
1. 为什么HVAC系统要分开测“本地温度”和“远程温度”
1.1 两种温度各有各的用途
我先说一个很多人一开始没想明白的点:HVAC 系统里的“本地温度”和“远程温度”,根本就是两个用途完全不同的信号,不能混着用。
本地温度指的是传感器芯片所在地——也就是主控板所在位置的温度。嵌入式控制板的发热大户是 MCU、功率管、继电器和电源模块,这些器件长期工作在高温下,寿命和可靠性都会明显下降。所以本地温度通常承担两个角色:一是给主控板做自保护,比如温度超过 85°C 就降频、降低 PWM 占空比、甚至直接停机;二是给风机调速提供一个参考,让机柜内部保持合理的散热风量。
远程温度则是真正代表“被控对象”的温度。做法是在传感器芯片外部接一颗二极管连接的小三极管,把这个三极管通过双绞线送到几米甚至十几米外的风管、表冷器翅片或者室外阴凉处。因为远端只有一颗无源三极管,没有芯片、没有电源、没有复杂电路,所以环境再恶劣也不怕:高温、高湿、灰尘、冷凝水,都不会直接打在控制器上。
很多人以为远程测温只是“把探头放远一点”,其实它真正的价值是:让所有有源电路都留在安全的控制柜里,而把最脆弱、最需要靠近被控对象的探头做成廉价可更换的无源器件。这样维护成本低,可靠性也高。
1.2 单芯片双通道比堆传感器划算得多
如果你只想测一个点,随便一颗数字温度传感器都能搞定。但 HVAC 系统通常至少需要 3~4 个温度点,这时方案选择就很关键。
最笨的办法是每个测温点放一颗数字温度传感器,比如常见的贴片温度芯片,每颗都要占 I2C 地址、要配去耦电容、要占 PCB 面积,而且每个点都要从控制板拉一条电源线过去。远端一旦进水短路,可能把整条 I2C 总线都拖死。
用 PJ85718DM 这类本地+远程双通道芯片就简单得多:主控板上只放一颗芯片,需要测的远端位置各接一个三极管。价格上相当于用一颗芯片的钱换掉多个独立传感器,PCB 面积也省下不少。我做过一个空调箱控制器,原来板子上挤了 4 颗温度传感器,改用双通道方案后板面清爽了很多,BOM 成本也降了一截。
这里我用一张表总结两种方案的差异,方便你选型时直接参考:
| 方案 | 每点成本 | PCB 占用 | 远端失效风险 |
|---|---|---|---|
| 每点放独立数字温度传感器 | 高(芯片+电容+布线) | 大 | 高(有源电路暴露在远端) |
| 本地+远程双通道芯片 | 低(芯片+三极管) | 小 | 低(远端只有无源三极管) |
1.3 远程测温的原理:为什么不是热敏电阻
聊到这里,你得先搞清楚远程温度到底是怎么测出来的,否则后面接线、标定很容易瞎猜。
PJ85718DM 这类芯片的远程测温通道,测量对象根本不是热敏电阻,而是“二极管连接”的三极管 PN 结。芯片内部会先后给这个 PN 结注入两档不同的电流 I1 和 I2,然后分别测出两次的管压降 V1 和 V2。根据半导体物理,V1 和 V2 的差值和温度有近似理想的线性关系:
ΔV = V1 - V2 = (k·T/q) · ln(I1/I2)
这里 k 是玻尔兹曼常数,q 是电子电荷,ln(I1/I2) 是两档电流比的自然对数。这个式子里,绝对管压降、反向饱和电流这些受工艺影响很大的项都被消掉了,所以用 ΔV 推算温度精度可以做到很高。
这也解释了为什么远端要专门用“把基极和集电极短接的三极管”,而不是随便拿个热敏电阻串上去。热敏电阻是阻值随温度变化,工作原理完全不同,芯片根本不认。我在现场见过好几次有人把热敏电阻接到远程通道上,读数乱跳,还以为芯片坏了。
2. 硬件连接关键点:PJ85718DM与PIC18F86J16的引脚级对接
2.1 供电与电平:3.3V 系统就是省事
PJ85718DM 这类数字测温芯片通常的工作电压是 3V~3.6V,PIC18F86J16 的 J 系列同样是 3.3V 供电的主控,两个器件逻辑电平天然匹配。这意味着 SCL、SDA、ALERT 这些信号线可以直接连,不需要任何电平转换芯片。
我做项目时给传感器供电用的是和 MCU 同一路 3.3V,但传感器电源脚旁边一定放两颗电容:一颗 100nF 的陶瓷电容负责高频去耦,一颗 1uF 的钽电容或者多层陶瓷电容负责中低频稳定。电容位置尽量贴着 VDD 引脚,不然高速 I2C 通信时电源噪声容易串进测量结果。
有一点必须提醒:J 系列 PIC18F86J16 不是 5V 器件,设计时千万别顺手给它供 5V,也不要让 5V 信号直接怼进它的引脚。如果你的系统里还有其他 5V 器件,务必检查信号线上有没有电平冲突。
2.2 引脚对接表
PIC18F86J16 有多个 MSSP 串行模块可用,我这里用 MSSP2 举例。具体用哪一组引脚,以你手里的 TQFP 封装引脚图为准,但接法思路是一样的。
| 信号 | PJ85718DM | PIC18F86J16 | 说明 |
|---|---|---|---|
| VDD | VDD | 3.3V 电源 | 100nF + 1uF 去耦 |
| GND | GND | GND | 单点接地,避免地环路 |
| SCL | SCL | MSSP2 SCL 引脚 | 接 4.7kΩ 上拉到 3.3V |
| SDA | SDA | MSSP2 SDA 引脚 | 接 4.7kΩ 上拉到 3.3V |
| A0/A1/A2 | 地址选择 | GND 或 VDD | 决定 I2C 从机地址 |
| ALERT | 告警输出 | RB0(外部中断) | 开漏输出,需外部上拉 |
| D+ / D- | 远程二极管正负 | 不接 MCU | 双绞线接远端三极管 |
上拉电阻的取值我一般用 4.7kΩ,I2C 速率跑 100kHz 或 400kHz 都稳定。如果总线上挂了多颗器件,上拉电阻可能要调到 2.2kΩ,具体看总线电容,最好用示波器看过波形再定。
2.3 远程二极管的接线规范:别把它当普通信号线
远程测温看起来只是接一个小三极管,但这里恰恰是故障率最高的地方。接法上有几条硬规矩:
第一,三极管必须是“二极管连接”:NPN 管的基极和集电极短接在一起,短接点接到 D+,发射极接到 D-。接反了或者漏接了,读数会偏得离谱。
第二,D+ 和 D- 必须用双绞线或者紧贴在一起的平行走线,不能一根线走很远、另一根线绕个大圈回来。因为 ΔV 测量对毫伏级噪声敏感,两根线如果不对称,耦合进来的差模噪声会直接叠加到信号上。
第三,远端三极管旁边并联一颗 100pF~1nF 的小电容,滤掉高频干扰。风机变频器、压缩机启动瞬间都是很强的干扰源,不加这颗电容,读取值时不时会跳一下。
第四,测温距离超过 3 米时,建议用双绞屏蔽线,屏蔽层在控制板侧单端接地,千万不要两端都接地。两端接地容易形成地环路,反而把噪声引进来。
3. 固件流程拆解:I2C读取、数值滤波与Modbus上报
3.1 初始化与寄存器布局
固件侧的第一步是把 PIC18F86J16 的 MSSP 配置成 I2C 主模式。PJ85718DM 的默认 I2C 从机地址常见是 0x48(7 位地址),实际地址由 A0/A1/A2 引脚的电平决定。我习惯在初始化代码里把这个地址定义成宏,方便以后一板多机时改。
这类双通道测温芯片的寄存器布局大同小异,我按手头器件的典型布局做个参考表,具体地址以你手里的数据手册为准:
| 寄存器 | 功能 | 说明 |
|---|---|---|
| 0x00 | 本地温度寄存器 | 12 位补码,高位在前 |
| 0x01 | 远程温度高字节 | 补码 |
| 0x10 | 远程温度低字节 | 低 4 位有效 |
| 0x02 | 状态寄存器 | 含远程开路/短路标志 |
| 0x03 | 配置寄存器 | 转换模式、分辨率 |
| 0x04/0x05 | 温度偏移寄存器 | 校正值,0.0625°C/LSB |
初始化时我一般把分辨率设成 12 位,连续转换模式。轮询模式在电池供电的低功耗设备里有用,但在 HVAC 控制器这种 220V 供电环境里,连续转换反而省心,不用担心漏读报警。
3.2 读取与换算:一个完整的 I2C 读函数
下面这段是从传感器读取 16 位温度寄存器原始值的核心代码。I2C 底层的 Start、Stop、写字节、读字节函数用 PIC 的 MSSP 外设实现,这里不展开,直接看读取逻辑:
#define PJ85718DM_ADDR 0x48 // 7-bit地址 uint16_t temp_reg_read(uint8_t reg) { uint16_t val; i2c_start(); i2c_write((PJ85718DM_ADDR << 1) | 0); // 写方向 i2c_write(reg); // 目标寄存器 i2c_start(); // repeated start i2c_write((PJ85718DM_ADDR << 1) | 1); // 读方向 val = (uint16_t)i2c_read(ACK) << 8; // 高字节 val |= (uint16_t)i2c_read(NACK); // 低字节 i2c_stop(); return val; }拿到原始值之后要换算成摄氏度。以 12 位分辨率的常见格式为例,温度数据存放在 16 位寄存器的高 12 位,每个 LSB 代表 0.0625°C。换算时先把高 4 位做符号扩展,再整体右移 4 位:
int16_t raw = (int16_t)temp_reg_read(0x00); // 本地温度原始值 int16_t t12 = raw >> 4; // 符号扩展后的12位温度码 float temp_c = (float)t12 * 0.0625f; // 转换成摄氏度这里最容易犯的错是忘记符号扩展。温度低于 0°C 时寄存器是补码表示,直接拿 unsigned 转 float 会得到几百度的离谱值。所以读取时一定要用 int16_t,并且先右移再做乘法。
3.3 滤波:滑动平均和报警值要分开处理
HVAC 现场的温度信号不会像实验室里那么干净,风机启停、压缩机吸合都会在读数上砸出毛刺。我建议对用于控制闭环的滤波值做滑动平均,窗口一般取 8~16 次即可,太长会让系统反应变慢,太短又压不住毛刺。
#define FILTER_WINDOW 8 static int16_t buf[FILTER_WINDOW]; static uint8_t idx; static int32_t sum; int16_t moving_average(int16_t sample) { sum -= buf[idx]; buf[idx] = sample; sum += sample; idx = (idx + 1) % FILTER_WINDOW; return (int16_t)(sum / FILTER_WINDOW); }一个关键经验:报警判断不要用滤波后的值,要用原始值或者 2~3 次的快速均值。因为真正的过热、传感器开路是突发的,滑动平均会把这种突变拉平,导致报警延迟。我习惯温度显示和控制用滤波值,报警和故障判断走另一条快速通道。
3.4 Modbus RTU 从机实现要点
HVAC 设备几乎都要把数据汇到上位机或者楼宇自控系统,PIC18F86J16 的 UART 外设配一颗常规的 RS485 半双工收发器,跑 Modbus RTU 从机协议就够了。我的寄存器规划是:40001 存本地温度(单位 0.01°C),40002 存远程温度,40003 存状态字,40004 存设定点,40005 存滞回参数。
Modbus RTU 里最容易出问题的就是 CRC16。下面是标准的 Modbus CRC16 实现,查表法和逐位法都可以,我贴一个逐位版本,逻辑清晰也好移植:
uint16_t modbus_crc16(uint8_t *data, uint8_t len) { uint16_t crc = 0xFFFF; while (len--) { crc ^= *data++; for (uint8_t i = 0; i < 8; i++) { crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : (crc >> 1); } } return crc; // 低字节在前发送 }另一个高频坑是 RS485 收发切换。发送完后必须等发送移位寄存器真正送出最后一个字节,再把方向脚拉回接收态。我见过太多人发完立刻切方向,最后一个字节丢半个,上位机 CRC 永远报错。PIC 的 EUSART 模块有发送完成标志位,发送前设置好,发送后轮询这个标志再切方向,问题就没了。
3.5 报警脚怎么用
PJ85718DM 的 ALERT 脚是开漏输出,平时被外部上拉拉高,温度越限时拉低。我把它接到 PIC18F86J16 的 RB0(外部中断引脚),配下降沿触发。
中断服务程序里只做一件事:置一个全局报警标志。真正的 I2C 读取、状态解析全部放主循环里做。原因很简单,I2C 时序对中断打断很敏感,如果在 ISR 里边发边收,中途被更高优先级的中断打断,总线时序就乱了,轻则读到错值,重则整条总线卡死。
4. 精度与校准:远端二极管测温的偏差来源与补偿方案
4.1 影响精度的五个因素
远端二极管测温的理论精度很高,但那是理想情况。落到实际 PCB 和现场环境,偏差主要来自五个地方:
引线电阻。PJ85718DM 给二极管注入的是电流,电流流过 D+、D- 之间的串联电阻会产生附加压降,芯片把这个压降也算进 ΔV 里,导致读数偏高。线越长、越细,这个偏差越大,我实测 10 米 0.2mm² 导线大约能带来 1~2°C 的正偏差。
三极管的理想因子。ΔV 公式里的 ln(I1/I2) 就是两档电流比的对数,所以理想因子 n 越接近 1,测温越接近理想。不同厂家、不同批次的小三极管 n 值有细微差别,芯片通常有校正寄存器,按实际用管型号去设。
噪声。远端引线就是一根天线,变频器、继电器、开关电源的高频噪声如果耦合进 ΔV,读数就会像心电图一样抖。应对办法就是前面说的双绞线加小电容。
漏电流。高温高湿环境下 PCB 表面和线缆绝缘层会有微量漏电流,相当于给二极管并联了一个电阻,影响低频段的测量。所以远程通道周边走线要尽量干净,线缆选绝缘好的材料。
自热。传感器自身会消耗一点功率,如果把它贴着发热器件放,本地温度会明显偏高。远端三极管因为工作在微安级电流,自热可以忽略,本地通道才需要留心。
4.2 校准流程:冰水、恒温箱、两点法
我一般不做单点校准,而是做两点线性校准。流程是这样的:
第一步,把远端三极管和一根高精度参考温度计绑在一起,放进冰水混合物里,等读数稳定后记录显示值和参考值的偏差。这一步给出 0°C 附近的偏差点。
第二步,把同样的一组探头放进恒温箱,设到 85°C(没有恒温箱就用沸水替代,但要注意当地气压对沸点的影响),再记一个偏差点。
第三步,用这两个点算出线性补偿系数,把补偿值写进芯片的偏移寄存器,或者直接存在 PIC 的内部 EEPROM 里。注意,补偿值对本地温度和远程温度是分开设置的,别搞混。
第四步,回到 25°C 附近复测,确认补偿后的误差落在可接受范围。产线上如果要做批量标定,就在这个流程前面加一个通电老化步骤,让板子稳定半小时再测,结果更可靠。
4.3 现场标定的一个细节
还有一件事经常被忽略:校准点的环境必须和实际使用环境“热接触”一致。比如远程三极管装在风管里测回风温度,校准的时候就要让它和参考温度计处在同一个热质量里,不能一个贴风口、一个拿在手里。
另外,我在项目里发现,出厂时校准得再准,到了现场还是会因为安装位置变化产生偏差。所以固件里最好留一个远程偏移量寄存器或者 Modbus 可写的补偿寄存器,让现场调试的人微调,不用重新编译烧录。这个设计虽然只多了几十行代码,但售后体验完全不一样。
5. HVAC闭环控制落地:从温度值到风机与压缩机的动作
5.1 传感器位置怎么选:每个温度点都对应一个动作
HVAC 控制不是把温度读出来显示一下就完事,关键是把每个温度点对应到明确的控制动作上。我按位置和用途整理了一张表,是我做空调箱控制器时的分法:
| 测温点 | 传感器通道 | 控制用途 |
|---|---|---|
| 主控板内部 | 本地 | 设备过热保护、内部风机调速 |
| 回风管 | 远程 | 主控闭环:决定风机/压缩机启停 |
| 送风管 | 远程 | 盘管能力监测、风量异常判断 |
| 表冷器表面 | 远程 | 防冻保护、防止盘管结冰 |
回风温度是空调系统最常用来做主控对象的位置,因为它代表整个空间的平均温度。送风温度则反映盘管当前的冷却或加热能力,两个值配合,可以判断系统是正常运行还是已经“力不从心”。
5.2 滞回控制:避免继电器和压缩机频繁启停
最常见的控制逻辑就是滞回(hysteresis)控制。设定一个目标温度,比如 22°C,然后用回风温度做反馈,控制风机或者压缩机。没有滞回的话,温度在设定点附近微幅波动就会让继电器不停地吸合断开,继电器寿命和电源稳定性都会出问题。滞回区间一般取 ±0.5°C 到 ±1°C。
#define SET_POINT 22.0f #define HYST 1.0f static uint8_t fan_on = 0; void hvac_control_tick(float return_temp) { if (!fan_on && return_temp > SET_POINT + HYST) fan_on = 1; // 升温越过上限,启动 else if (fan_on && return_temp < SET_POINT - HYST) fan_on = 0; // 降温越过下限,停止 }这套逻辑看起来简单,但现场调试时有个细节:滞回区间不能设得太小。很多人觉得 ±0.5°C 已经不小了,但实际 HVAC 系统热惯性大,温度波动也大,区间太小照样会频繁动作。我给自己的经验值是回风控温用 ±1°C,送风保护用 ±0.5°C,既兼顾舒适度又保护设备。
5.3 安全联动:防冻和过热是两套独立逻辑
HVAC 的远程温度里,防冻保护优先级最高。表冷器盘管在冬季如果水温过低或者风量不足,盘管里的水可能结冰,冻裂换热器是实打实的设备事故。所以盘管表面温度一旦低于某个阈值,不管设定点是什么,系统都必须立刻进入防冻模式:关风机、开热水阀或者切换再循环模式。
本地温度则承担反向保护。我写过这样一段逻辑:本地温度超过 85°C 时,强制把输出 PWM 降到 50%;超过 95°C 时直接停机并锁存故障。锁存的目的是防止设备在故障状态下反复重启,必须等人工复位或者温度降下来之后才能恢复。这个锁存标志我存在 PIC 内部 EEPROM 里,断电重启也不会丢失。
另外,远程通道的开路/短路检测一定要做。三极管引线断掉或者短路时,温度读数会跳到一个极端值,如果不识别这个状态直接拿去控制,后果很严重。芯片的状态寄存器一般会置位开路/短路标志位,固件里读到这个标志,就把对应通道的测量值标记为无效,控制逻辑走安全默认值,同时上报故障码。
6. 实测数据与常见故障排查记录
6.1 一组有代表性的实测数据
我在某个模拟项目里搭了一套测试环境,用 5 米和 15 米两种双绞线长度做了对比,记录校准前后的远程温度读数偏差。环境温度约 28°C,三极管选普通小信号 NPN,加 100pF 滤波电容。
| 线长 | 校准前偏差 | 校准后偏差 | 风机启动瞬间波动 |
|---|---|---|---|
| 1 米 | +0.15°C | ±0.1°C | ±0.05°C |
| 5 米 | +0.5°C | ±0.2°C | ±0.15°C |
| 15 米 | +1.2°C | ±0.3°C | ±0.4°C(加电容前) |
从数据能直接看出来,引线电阻带来的偏差是随长度线性增加的,用两点校准可以压下去。但校准抵消不掉噪声,噪声只能靠布线、屏蔽和滤波来解决。所以我的结论一向是:校准解决系统性偏差,布线解决随机性波动,两者不能互相替代。
6.2 高频踩坑记录:现象、根因、处理
做这类项目,我把遇到过的故障整理成了一份排查清单,每次现场出问题就按这张表查,效率很高:
| 现象 | 根因 | 处理方式 |
|---|---|---|
| I2C 读回全是 0xFF | SCL/SDA 没有上拉电阻 | 加上 4.7kΩ 上拉到 3.3V |
| 远程温度读数乱跳 | 三极管接反或 D+/D- 挨着 PWM 线 | 检查二极管连接方向,线束远离强干扰源 |
| 温度缓慢漂高 | 传感器紧贴电源模块自热 | 调整布局,芯片远离发热源 |
| Modbus 偶发 CRC 错误 | 收发方向切换过早 | 等发送完成标志位再切换 DE |
| ALERT 报警不触发 | 开漏输出缺上拉电阻 | 给 ALERT 加 10kΩ 上拉 |
| 低温读数异常偏高 | PCB 漏电流大 | 清洗板面,检查绝缘处理 |
这里面最让我记忆深刻的是第一个坑。第一次打板回来,I2C 怎么读都是 0xFF,我查了半天以为是芯片焊坏了,后来发现是原理图里漏画了上拉电阻。I2C 总线的上拉不是可选项,是必须项,这点新手特别容易漏。
6.3 我的几点经验
做了几轮这类项目之后,我最大的感触是:测温系统的难点从来不在“读出温度”这一步,而在温度读数背后那一整条链路——硬件布局、机电干扰、标定补偿、故障识别、控制策略,每个环节都可能让数据失真。PJ85718DM 配 PIC18F86J16 这套组合的价值,就是用一个成熟的双通道测量方案把前面最难的模拟部分简化掉,让开发者把精力放在固件逻辑和系统可靠性上。
最后分享两个我自己的习惯:第一版 PCB 上就把校准点和测试点都留出来,不要等整机装好了再想办法;另外一个通道不够用时,可以通过 A0/A1/A2 地址线在总线上挂多颗 PJ85718DM,每颗都能同时提供本地和远程温度,扩展成多路测温系统非常顺手。这套路数做冷链监控、配电柜温度监测、甚至小型机房环境监控,都是一样的打法。