之前给某楼宇自控的小改造项目做温度监测,控制柜里要我盯一路“本地温度”,风管和冷冻水管上还要再采集两路“远程温度”。手头正好有 PJ85718DM 这种数字温度传感芯片,再加上一颗 PIC18F86J55 做主控,我就照着“多点采集 + 主控轮询 + 数据输出”的思路搭了一套嵌入式 HVAC 温度监测系统。
这套方案说不上豪华,但它把温度测量里最常遇到的问题都走了一遍:传感芯片怎么接、I2C 怎么跑、远程探头长了怎么抗干扰、温度读回来怎么校准。如果你正在做嵌入式温控器、HVAC 控制面板,或者只是想把手里的温度数据稳定地送到屏幕上和上位机里,这篇内容可以直接拿来当参考。
1. 方案选型:为什么要用“温度芯片 + MCU”而不是现成温控器
1.1 先把需求拆清楚:本地温度和远程温度分别是什么
很多人看到“本地温度”和“远程温度”会下意识认为一个是单片机自己测,另一个是网络上的数据。但在嵌入式 HVAC 场景里,这两个词的含义更朴素。
“本地温度”指的是主控板所在位置的温度,通常是控制柜内部空气温度、PCB 附近的环境温度,甚至是 MCU 自身附近的热点温度。它的作用是保护电气设备。比如变频器柜、空调控制柜,如果柜内温度超过 60℃,电子元件寿命会明显下降,这时候需要在程序里做高温报警或者联动风扇。
“远程温度”则是离开主控板几米甚至几十米位置上的被测点温度。HVAC 系统里最常见的远程测点包括:
- 送风管温度,用来判断空调吹出去的空气是否达到设定值;
- 回风管温度,用来做 PID 调节反馈;
- 冷冻水供水/回水温度,用来判断冷机和水泵工作状态;
- 室内平均温度,用来做房间温控。
这些测点离控制器距离不等,短的两三米,长的可能穿墙走线到十几米。所以远程温度在设计上和本地温度最大的区别在于:传输距离、线缆防护、以及故障隔离。远程传感器如果不小心短路或者进水,不能把整块主控板拉死。
1.2 选型思路:接口、精度、成本与可维护性
本地温度我选了一颗板载的数字温度传感芯片,也就是标题里的 PJ85718DM 这类器件。选择它的主要原因是接口简单、一致性好、不需要校准 NTC 的 B 值参数。数字温度芯片内部的 ADC 和参考电压都已经做好了,MCU 通过 I2C 或 SPI 直接读寄存器,返回的就是温度对应的数字量。相比热电偶要接冷端补偿和放大器,相比 NTC 要做查表和非线性校正,数字传感器的开发工作量小很多。
远程温度我也尽量用同一型号的传感芯片,只是把它们放在探头外壳里,通过线缆引出去。这样做的好处是软件不用维护两套换算公式,硬件也只要把地址引脚 A0/A1 设置成不同电平,就能在一条 I2C 总线上区分多个传感器。
PIC18F86J55 作为主控,我看重的是它有足够的 I/O 和通信外设,能够同时管理 I2C 总线、串口、液晶屏和继电器。这类 PIC18 系列 MCU 在工业温控器里很常见,抗干扰能力、供货稳定性和开发工具链都比较成熟。虽然它的主频不算高、Flash 也不算大,但做温度监测这种任务完全够用。
提示:如果你的项目对成本更敏感,远程温度也可以换成 NTC + 恒流源 + ADC 采集。但那套方案需要每路一个采样电阻,还要做线阻补偿,批量生产时的校准工作量明显更高。数字温度芯片虽然单价稍贵,但省下来的调试工时往往更值钱。
1.3 系统架构和数据流
整块系统的信息流可以拆成四个环节:采集、换算、决策、输出。
采集环节由 PJ85718DM 完成。本地一颗直接焊在主控板边缘,远程一到两颗放到管道或风管探头里,三颗芯片通过 I2C 连接到 PIC18F86J55。芯片的地址通过 A0/A1 引脚配置,本地传感器给一个地址,远程传感器给另一个地址,主控轮询时按地址区分。
换算环节由固件完成。传感器读出来的原始寄存器值是 16 位数字量,需要按照数据手册的精度位右移,再乘以分辨率,得到带小数的摄氏温度。这一步很多人容易做错,尤其要注意高位字节和低位字节的顺序。
决策环节由 MCU 完成。程序里设置两个温度阈值,比如本地温度超过 55℃ 就开风扇,远程回风温度超过设定值就联动冷阀。为了避免温度抖动导致继电器频繁吸合,判断时还要加入滞回。
输出环节包括三部分:一路 LCD 显示当前温度,一路串口输出调试信息,一路继电器输出控制风扇或者阀门。如果现场需要接入楼宇自控系统,还可以把温度数据通过 Modbus RTU 协议发送到上位机。数据流从传感器到 MCU,再从 MCU 到显示和执行机构,整个链路在逻辑上非常清晰。
2. 硬件搭建:从传感器探头到主控板
2.1 温度传感器的基本接法
以 I2C 接口为例,PJ85718DM 这类芯片通常有这些引脚:VDD、GND、SDA、SCL、A0、A1、ALERT。
供电方面,传感器一般支持 2.7V 到 5.5V,可以直接用主控板的 3.3V 或者 5V。但无论用哪一路电源,我强烈建议在 VDD 和 GND 之间加一颗 100nF 陶瓷电容,并且尽量靠近芯片引脚放置。数字传感器内部的 ADC 在转换瞬间需要一点电流,如果电源被拉出纹波,温度读数最后一位就会跳。
SDA 和 SCL 必须接上拉电阻。我通常选 4.7kΩ,如果远程线比较长,可能改成 2.2kΩ。上拉电阻越小,总线上升沿越陡,抗干扰越强,但功耗也会变大,而且接多颗设备时驱动能力需要重新计算。实测下来,一条总线上挂三颗传感器、线长两米以内,4.7kΩ 基本没问题。
A0 和 A1 是地址设置引脚。把 A0 接 GND、A1 接 VDD,就能得到不同的 I2C 地址。本地传感器用 0x48,远程传感器用 0x49,这样在程序里通过地址就能判断数据来自哪一路。
ALERT 引脚是报警输出,可以接 MCU 的外部中断。不过我在做现在这个项目时没有用它,而是让主控定时轮询读取温度,因为轮询本身已经能让数据保持足够的实时性。如果你追求快响应,可以把传感器配置成比较器模式,温度越限时 ALERT 拉低触发中断。
2.2 主控资源分配与引脚规划
PIC18F86J55 的资源分配,我的建议是先把通信和电源规划好,再考虑显示和控制。
我用 SCL 和 SDA 接本地传感器的 I2C 总线;如果远程传感器距离不超过两三米,也可以直接挂在同一条总线上。不过为了隔离故障,我更喜欢把远程 I2C 总线独立分配到另一组引脚,用软件分别初始化。这样远程线缆一旦短路,不会影响本地温度读取。
具体引脚规划大致如下:
| 功能 | 引脚 | 说明 |
|---|---|---|
| 本地传感器 SCL | 复用 MSSP 模块的时钟引脚 | 加 4.7kΩ 上拉到 VDD |
| 本地传感器 SDA | 复用 MSSP 模块的数据引脚 | 加 4.7kΩ 上拉到 VDD |
| 远程传感器 SCL/SDA | 另一组软件 I2C 引脚 | 靠近端子加 TVS 二极管 |
| LCD 数据/控制线 | 普通 GPIO | 8 位并口或者 4 位并口 |
| 继电器输出 | 普通 GPIO 加三极管 | 需要续流二极管 |
| 串口 TX/RX | 对应串口外设引脚 | 现场调试用 |
GPIO 分配时要注意引脚是否有第二功能冲突。比如某些引脚默认是编程调试接口,如果被占用了,下载器可能连不上。我在开发初期吃过这个亏,把烧录引脚接了 LCD,结果程序烧进去之后第二次就下载失败了。后来查了数据手册的重映射表才改过来。
2.3 电源、去耦与长线防护
HVAC 控制柜里的电源环境比较脏,变频器、接触器、压缩机启动都会在母线上产生干扰。所以我不推荐直接从高压驱动回路里取电给 MCU 供电。较好的做法是用一路隔离 DC-DC 或一个小功率开关电源,把控制板的供电和动力回路从物理上分开。
传感器的供电我建议单独用一颗 3.3V LDO。实测下来,如果让传感器和继电器共用 5V,继电器吸合瞬间的电压跌落会导致温度寄存器读数抖动。LDO 的作用不只是稳压,更重要的是隔离负载波动。加上足够的输出电容,读数稳定很多。
远程线缆侧还要考虑静电和雷击浪涌。温度探头从风管或水管引出来,线缆可能会经过金属桥架、穿墙套管,现场电磁环境复杂。我在远程端子入口处加了双向 TVS 二极管,钳位电压选 5V 左右,把浪涌限制在 MCU 可承受范围内。对于更恶劣的环境,建议再串 1kΩ 电阻。
注意:远程 I2C 线如果超过三米,不要急着加长。I2C 本身是为板级通信设计的,总线电容大了以后时序容易失效。超过三米我一般改方案,要么用 I2C 总线扩展芯片,要么干脆把远程传感器接到另一块小板上,小板之间用 RS485 或者 Modbus 通信。方案变化不大,但稳定性提升非常明显。
2.4 远程探头安装与线缆选择
远程温度探头的安装直接影响测量准确性。传感器的感温部分一定要和被测介质充分接触。测风管温度时,探头要伸到管道中心位置附近,不能贴着管壁。管壁温度受外界环境影响很大,探头贴着管壁测到的其实是管道外壳的温度,而不是风温。正常情况下,探头插入深度至少要超过管道直径的三分之一。
测水管温度时,如果只是把探头贴在水管表面,需要用导热硅脂涂在接触面,再用保温棉把探头和管道一起包住。如果不包保温棉,探头受到环境温度影响,读数会飘。如果水管有保温层,最好把保温层切开一点,让探头直接接触管壁,再重新密封。
线缆方面,我选的是屏蔽双绞线,屏蔽层单端接地,接到主控板的地。双绞线可以抵消磁场干扰,屏蔽层可以抵挡电场干扰。千万不要两端都接地,否则屏蔽层里会形成地环路电流,反而把干扰引进来。端子处要用带锁扣的接线端子,避免现场震动导致松动。
3. 固件实现:轮询、换算、滤波和控制输出
3.1 I2C 底层驱动与初始化
PIC18F86J55 的 MSSP 外设在主模式下的初始化并不复杂。这里给出一段参考写法,细节以具体数据手册为准:
void i2c_init(void) { // 设置主模式,禁止 SMBus,使能 I2C SSPCON1 = 0x28; // SSPEN=1, 主模式 SSPCON2 = 0x00; SSPADD = 39; // 假设 Fosc=32MHz,目标 SCL=100kHz SSPSTAT = 0x80; // 禁止斜率控制,标准速度 }SCL 频率的确定方式要记牢:对于标准速度 I2C,SSPADD = Fosc/(4*SCL) - 1。我在实际项目里常用 100kHz 而不是 400kHz,因为远程线缆的动态电容和现场干扰会限制速率,慢一点更稳。如果你需要快速采集多个点,可以把读取间隔缩小,但没必要一味提高时钟频率。
起始、停止、读写字节的函数是所有 I2C 通信的基础:
void i2c_start(void) { SSPCON2bits.SEN = 1; while (SSPCON2bits.SEN); } void i2c_stop(void) { SSPCON2bits.PEN = 1; while (SSPCON2bits.PEN); } void i2c_write_byte(unsigned char data) { SSPBUF = data; while (!SSPSTATbits.BF); if (SSPCON2bits.ACKSTAT) { // 从机无应答 } } unsigned char i2c_read_byte(unsigned char ack) { SSPCON2bits.RCEN = 1; while (!SSPSTATbits.BF); unsigned char data = SSPBUF; SSPCON2bits.ACKDT = ack ? 0 : 1; SSPCON2bits.ACKEN = 1; while (SSPCON2bits.ACKEN); return data; }这套代码我调试时没有做抽象层,直接读写寄存器,简单可靠。如果你用代码配置器生成 I2C 驱动,原理也差不多,只要最终能正确控制起始、停止和应答即可。
3.2 温度寄存器读取与换算
读取 PJ85718DM 温度的流程是:先向传感器发送一个寄存器地址,告知要读取温度寄存器,然后发起重新起始或直接读操作,连续读两个字节。假设温度寄存器地址是 0x00,数据高位在前:
unsigned char temp_h, temp_l; int raw_temp; i2c_start(); i2c_write_byte(0x90); // 0x48 写地址 i2c_write_byte(0x00); // 温度寄存器地址 i2c_stop(); i2c_start(); i2c_write_byte(0x91); // 0x48 读地址 temp_h = i2c_read_byte(1); // 读高字节,发送 ACK temp_l = i2c_read_byte(0); // 读低字节,发送 NAK i2c_stop(); raw_temp = (temp_h << 8) | temp_l;换算的步骤要看传感器的分辨率配置。常见的数字温度传感器支持 9 到 12 位分辨率,默认 12 位时,温度数据的低 4 位是小数部分。把原始值右移 4 位后,乘以 0.0625,就能得到带小数的摄氏度。写成代码就是:
float temperature = (raw_temp >> 4) * 0.0625;这里最容易犯的错是忘记右移,直接把 16 位原始值当作温度。还有一个容易犯的错是高低字节顺序读反。我排查过不少类似问题,最后都是一步步打印原始寄存器值才发现的。建议在调试初期就把原始值和换算后的温度一起通过串口打印出来,这样能快速判断是通信问题还是换算问题。
3.3 滤波、限幅和故障重试
温度数据天然带有随机噪声。HVAC 环境里的噪声来源很多,比如传感器附近的风机转动、温度控制阀频繁动作、电磁干扰耦合到线缆上。如果不对读数做滤波就直接控制继电器,很容易出现“排气扇没几分钟就频繁启停”的效果。
我用了两层滤波。第一层是限幅滤波,也就是判断当前采样值和上一次有效值的差值,如果差值超过 2℃,就认为是异常跳变,丢掉本次数据,重新再读一次。第二层是滑动平均,保留最近 4 次有效样本,输出平均值。
#define FILTER_SIZE 4 float buffer[FILTER_SIZE]; unsigned char index = 0; float last_valid = 0.0f; float filter_temperature(float sample) { if (sample > last_valid + 2.0f || sample < last_valid - 2.0f) { return last_valid; } buffer[index] = sample; index = (index + 1) % FILTER_SIZE; float sum = 0.0f; for (unsigned char i = 0; i < FILTER_SIZE; i++) { sum += buffer[i]; } last_valid = sum / FILTER_SIZE; return last_valid; }滤波算法不宜太重,否则系统对真实温度变化的响应会太迟钝。举个例子,HVAC 冷启动时,回风温度可能在一两分钟内下降好几度,如果滑动窗口太长,控制器实测温度会远远落后于真实值,导致冷阀过冲。4 到 8 次的窗口长度是一个比较合理的平衡点。
故障重试逻辑也要写清楚。I2C 通信偶尔会失败,可能是远程线缆暂时受干扰,也可能是传感器上电时序还没稳定。我在轮询函数里对每颗传感器连续读取三次,三次都失败才设置故障标志。故障时保留上一次有效温度,不让故障值参与滤波和控制,同时通过串口输出报警。
3.4 数据出去:LCD、串口和继电器逻辑
数据显示我用了字符型液晶屏,第一行显示本地温度,第二行显示远程温度。刷新频率不用太高,2 到 5 秒刷新一次就够,因为人眼对环境温度变化的感知本来就慢。LCD 刷新太快反而会在屏幕上留下闪烁感,还会占用 MCU 的轮询时间。
串口输出用于现场调试。每 200ms 输出一行数据,格式类似:
LOCAL: 25.31C REMOTE: 24.87C LOCAL_SENSOR_OK REMOTE_SENSOR_OK调试时把波特率设为 115200,用串口助手直接看。等系统稳定后,再把调试信息和正式通信分开。如果你要接上位机或者楼宇自控系统,串口数据可以改成 Modbus RTU 协议。
继电器控制我用了一组简单的阈值逻辑,并加了滞回区间:
float switch_on_th = 50.0f; float switch_off_th = 45.0f; void update_fan(float local_temp) { if (local_temp >= switch_on_th) { relay_on(); } else if (local_temp <= switch_off_th) { relay_off(); } }这里需要强调滞回区间。如果没有滞回,温度在 49 到 50℃ 之间波动时,继电器的开和关会以秒为单位反复切换,继电器触点寿命很快就没了。滞回的区间大小一般取 3 到 5℃。如果是控制冷阀,为了避免频繁调节,还可以把阈值换成 PID 输出加执行器死区,原理是一样的。
4. 调试实录:那些容易踩的坑
4.1 读数全是 0xFF 或不停跳变怎么查
第一次上电调试,我总是先读原始寄存器值,再看换算结果。如果读回来的是 0xFF 或者 0xFFFF,基本上可以判定通信链路有问题,而不是温度本身坏了。
排查顺序我总结如下:
- 查供电。传感器的 VDD 和 GND 是否接反,电压是否在允许范围内。
- 查上拉。SDA/SCL 是否有 4.7kΩ 左右的上拉电阻,到 VDD 的上拉是否真的连接。
- 查地址。A0/A1 配置是否正确,读地址和写地址是否和硬件引脚一致。
- 查时序。用示波器或者逻辑分析仪抓 SCL 和 SDA,看起始条件、地址字节、应答位是否完整。
- 查线路。远程线缆是否存在虚假接触,端子是否松动。
读数跳变的排查方向不太一样。如果是数据最后一位跳,那是ADC 噪声或电源纹波问题,检查去耦电容和 LDO。如果是偶发性大范围跳变,比如从 25℃ 跳到 80℃ 再跳回来,那多半是通信被干扰,重点检查线缆屏蔽层、TVS 管和是否用了双绞线。如果是稳定偏大或偏小,那属于校准问题,放到后面单独说。
4.2 远程线缆带来的干扰和时序问题
远程线在 1 米以内时,I2C 几乎不会出问题。一旦超过 3 米,问题就来了。我遇到过的情况是传感器返回的数据偶尔正确偶尔错误,用示波器看波形发现高电平已经被总线电容拉成了斜坡,SCL 上升沿太缓,传感器判断不了时钟。
解决办法有几个,优先级从低到高:
- 降低 I2C 频率,从 400kHz 降到 100kHz,甚至 30kHz;
- 把上拉电阻从 4.7kΩ 改为 2.2kΩ,增强上拉能力;
- 使用屏蔽双绞线,并单端接地;
- 线缆两端加 TVS 和 ESD 防护;
- 如果线长超过 10 米,直接放弃 I2C,改成 RS485 远程模块。
我这里最推荐的是第四种,方案成熟,隔离也方便。远程模块和主控之间用 Modbus RTU 协议,主控只负责读取寄存器,传感器和主控的电气回路完全隔离开,故障波及范围很小。温度监测项目的可靠性和最终成本,很大程度取决于这里是否偷懒。
4.3 两点校准:到底怎么校才准
数字温度芯片出厂前虽然做过校准,但在现场安装后,系统级误差不一定小。误差来源包括传感器自热、探头外壳热阻、安装位置和长线导致的电源压降。要求高的现场,我都会做一次两点校准。
标准做法是先校低温点,再校高温点。低温点用冰水混合物,让探头插入冰水混合物中,等到读数稳定,记录原始温度值。高温点用恒温水槽,设定 50℃ 或 60℃,等稳定后记录第二组数据。这里最关键的是等温度充分稳定,不能探头刚放进去就急着读数,否则校准点本身就不准。
假设冰点实测是 0.8℃,高温点实测是 51.3℃,真实值是 0℃ 和 50℃。校正公式写成:
float calibrated_temp = (raw_temp - 0.8f) * (50.0f - 0.0f) / (51.3f - 0.8f) + 0.0f;这个公式本质上是把实测的“两点直线”映射到真实温度范围。校准后,在冰点和高温点之间,线性误差可以控制在正负 0.3℃ 以内。要注意的是,数字传感器的非线性在宽量程下仍然存在,所以两点校准只能保证中间区域的误差不会太离谱,不要把校准范围拉到超出实际工作温度区间。
4.4 现场试运行和散热布局修正
系统做完后,我在现场跑了一个星期,发现本地温度读数一直比控制柜内温度高 2℃ 左右。问题出在传感器位置,我把 PJ85718DM 放在了主控板靠近继电器和电源变压器的一侧。发热器件把热空气抬升,传感器放在热风路径上,读到的并不是平均柜温。
修改办法很简单,把传感器移到主控板边缘,远离发热元件。如果结构允许,最好让传感器探头露出 PCB 一两厘米,甚至放到配电柜侧面的散热孔处,这样测到的才是真正需要保护的柜内环境温度。
远程探头也要看安装位置。风管温度探头如果安装在直管段和弯头附近,弯头会改变气流组织,导致局部温度不均。我把探头挪到了直管段的中间位置,读数稳定不少。水管探头则要注意保温棉的密封,保温棉一旦受潮,导热系数会变化,读数会缓慢漂移。这些细节写在需求文档里,现场施工时往往没人关注,做完对比数据你才会发现差别很大。
5. 从原型往产品化靠:成本、可靠性和扩展思路
5.1 从单板调试到批量生产的几点注意
原型跑通只是第一步,往产品化方向走的时候,有几个点我会重新检查一遍:
- 晶振和电容的负载匹配。晶振两端电容数值不要照抄参考设计,要按实际晶振规格计算并联电容值;
- PCB 走线。SDA/SCL 不要走太长、不要和继电器驱动线平行走很长距离,最好用地线隔开;
- I2C 地址冲突。如果以后要扩展更多远程节点,要预留地址选择跳线,避免两款产品固化同一个地址;
- 传感器老化。批量采购的传感器可能存在批次差异,建议按批次抽样做高温和低温测试;
- 校准工装。如果产品需要现场精度,最好做一套简易恒温槽,出厂前快速校准。
这些点单独看都很小,但产品一旦到几十台以上,任何一项没做好,返工成本都会放大很多倍。
5.2 远程节点扩展:从 I2C 到 RS485/Modbus
如果只有三五颗传感器,主控直接轮询 I2C 就能胜任。但商业楼宇里,一台控制器可能同时管理多个楼层,或者一个空调机房里有几十个温度测点。这时候远程节点应该独立出来做 RS485 总线的小模块。
模块方案可以是“另一颗 PIC18 系列 MCU + 同样一颗 PJ85718DM”,物理上做成一个防潮小盒子,安装在温度测点附近。模块与模块之间用 RS485 连接,主控作为 Modbus 主机,轮询每个模块的保持寄存器。这样做有几个好处:传感器线短,受干扰少;模块之间隔离,故障点清楚;后续加测点只需要增加模块地址,不用改主控代码。
Modbus 协议本身不复杂,实现时只需要把温度换算结果放到保持寄存器里,再用 CRC 校验负责数据完整性。主控的串口中断服务程序会被请求打断,但温度轮询本来就不需要毫秒级实时性,所以整体负载很轻。
5.3 还可以往哪些方向升级
这套系统的底子打好了,后续扩展空间很大:
- 加无线节点。远程传感器如果无法走线,可以在探头后面加无线数传模块,但要注意电池供电和休眠策略,温度采集不需要频繁唤醒;
- 加历史记录。在 MCU 上挂一颗小容量 Flash 或者 SD 卡,定时保存温度曲线,方便后期分析设备运行状态;
- 加入 PID 控制。有了稳定的测温基础,下一步就可以把本地和远程温度作为 PID 输入,直接控制风机盘管或电动比例积分阀;
- 接入云平台。通过串口接网关,把温度数据上传到服务器,实现远程监控和预警。
从我个人的习惯来说,做温度监测项目,关键不是把代码写得多花哨,而是稳定。温度本身变化慢,系统最怕的不是大数据量,而是偶尔一个错误读数导致风机乱转或者冷阀乱动。把测温链路做稳,数据好看了,后续任何控制逻辑都容易写。
最后再分享一个小技巧:现场调试时,随身带一个已校准的温度表或者一支好的温度计,和系统的读数做对比。不要只看屏上的数字顺不顺眼,要在冰水、室温、热水三个点分别比对一次。这个习惯帮我省了很多现场返工的时间。温度监测这种事,刚开始觉得简单,真正把误差和干扰处理明白,才算入门。