想用一块单片机同时管好设备内部的“体感温度”和远端风道的“环境温度”,这事听起来简单,真做起来会有不少细节。最近我完成了一个基于 PIC32MX795F512L 与 PJ85718DM 的双区域温度监测方案,主要用在嵌入式设备柜和 HVAC 送风回风系统里,把本地与远程两路温度一起纳入管理。如果你正在做温控、楼宇自控,或者只是想给现有设备加一个稳一点的上报功能,这篇内容应该能帮你省掉不少弯路。
这个方案不算复杂,但涉及的点很杂:传感器节点设计、主控最小系统、单总线时序、滤波校准、RS485 远程上报,每一环都有坑。下面我按从方案到硬件、再到固件和现场调试的顺序写,尽量把我踩过的坑和验证过的方法都列清楚。
1. 项目拆解:本地与远程温度监测的系统思路
1.1 本地与远程到底差在哪里
很多朋友一看到“本地与远程温度”,第一反应是“温度不都是一个传感器读出来的吗”。实际上这套系统里的“本地”和“远程”有两个完全不同层面的含义。
物理层面上,本地温度指传感器和主控在同一块板子或者同一个机柜内部,比如嵌入式控制器内部的功率芯片附近、电源模块旁边、密闭机箱里的空气温度。这个位置走线短、反馈快,但干扰源也近:MCU 自身发热、DC-DC 电感辐射、继电器吸合瞬间的尖峰,都会叠加到传感器信号上。远程温度则是把探头拉到风道、天花板、冷库或者几米外的设备间里,走线长、环境恶劣,还可能有变频器、风机电机在旁边制造共模干扰。
逻辑层面的差异同样重要。本地温度通常用于自我保护,比如机柜内温度超过 75°C 就降频或关断;远程温度则用于舒适性控制或能耗管理,比如新风温度、回风温度、房间温度。两者用途不同,采样周期、滤波时间常数、报警阈值都应该独立配置。把这些先想清楚,后面选传感器、画板子、写协议才不会乱。
1.2 为什么用 PIC32MX795F512L 搭配 PJ85718DM
选型的时候,我看过几类方案:用简单 8 位单片机加模拟热敏电阻、用一颗带多点采集的总线温度传感器,或者直接用带无线功能的模组。最后留下来的是 PIC32MX795F512L 加 PJ85718DM 这个组合,原因比较实际。
PJ85718DM 是数字式温度传感器,内部集成 ADC,直接输出数字量,一条数据总线上可以挂多个节点,非常适合在多处布点。它不像模拟热敏电阻那样需要每个通道分配一个 ADC 引脚,也不用担心长线传输时模拟信号衰减。对 HVAC 这种要拉线到风道、天花板、回风口的场景来说,接线这根线很省心。
PIC32MX795F512L 这边的优势是接口丰富度和计算余量。它有 512KB Flash、128KB RAM,主频可以跑到 100MHz 左右,还带着以太网 MAC、CAN、USB、多个 UART 和 SPI/I2C。这意味着本地采集可以留一个 GPIO 给温度总线,远程上送可以用 UART 接 RS485,将来想升级以太网上报也不用换主控。嵌入式系统最怕选型时算得太死,预留 30% 到 50% 的资源余量,后期会舒服很多。
用一个生活化的比喻来说:PJ85718DM 是分布在各个楼层的温度计,PIC32 是物业中心,一根电话线把所有温度计串起来,物业中心再通过对外专线把汇总数据递给上级管理平台。温度计只管精准感知,汇总、判断、上报这些统筹工作交给主控。
1.3 数据流、告警与远程上报的边界
整套系统的数据流是这样的:PJ85718DM 把物理温度转成数字量,通过单总线送到 PIC32MX795F512L;主控做滤波、阈值判断,驱动本地继电器或风扇;同时把处理后的温度值写入 Modbus 寄存器,等待远程主机通过 RS485 来读取。远程主机可能是触摸屏、组态上位机、PLC 或者楼宇管理系统(BMS)。
这里有一个很重要的边界:决策尽量放在主控本地,远程只负责监控和修改参数。这样即使和远程通讯中断,本地超温保护依然能工作。很多 HVAC 项目事故就是因为设备过度依赖上位机,上位机一卡,整个系统就失联了。所以我在实现时把报警、风扇联动、传感器断线检测都放在单片机里,Modbus 中断只会报状态,不会影响本地控制逻辑。
2. 硬件设计:传感器、主控与远程接口怎么搭
2.1 先搞定 PJ85718DM 传感器节点
先把传感器节点处理好,后面会少很多麻烦。PJ85718DM 这种数字温度传感器通常只需要三个引脚:电源、地、数据。如果总线上传感器数量不多、距离短,可以共用一组电源和地,数据线统一接在主控的一个 IO 上。数据线上必须接上拉电阻,常见做法是 4.7kΩ 左右。上拉太小会让总线负载变重,上拉太大则可能因为线缆分布电容导致波形上升沿太慢,读取时序出错。
供电方式上,短距离推荐直接用 3.3V 供电。如果现场总线长度超过一米,建议每个传感器节点独立接电源线和地线,不要只靠数据线供电,不然远端传感器在转换电流较大的瞬间会造成电压跌落,读数跳变甚至掉线。
现场布线的经验法则是:尽量走菊花链,也就是一根总线从主控出发,依次经过每一个传感器,而不是从主控接出好几条分叉。分叉拓扑在传感器数量多、线缆长的时候特别容易出现信号反射,表现就是偶尔读错数据、CRC 校验失败。如果工程上实在避免不了星形接法,每个分支线尽量控制在 20 厘米以内,并且把上拉电阻调到 2.2kΩ 到 3.3kΩ 之间增强驱动能力。
注意:传感器数据线不要和变频器输出线、继电器线走同一根线槽。如果必须平行走线,中间留出至少 5 到 10 厘米间距。温度总线虽然不是高速信号,但 HVAC 现场电机启停的干扰非常猛,有时候能直接把你总线上的数据打成乱码。
2.2 PIC32MX795F512L 最小系统与供电隔离
PIC32MX795F512L 是 3.3V 器件,电压范围一般可以接受 2.3V 到 3.6V 左右,但为了让 ADC、Flash 和内部稳压器稳定工作,我习惯把电源做到 3.3V ±1%。板子上主控电源引脚附近放 100nF 陶瓷电容,每个电源引脚至少一个,再加一颗 10uF 钽电容或者陶瓷电容做低频去耦。复位电路用经典的电阻电容复位即可,但要注意上电时间,MCU 的复位释放时间要大于电源爬坡时间,否则可能出现上电时序不稳定导致偶尔启动失败。
时钟部分,PIC32MX 系列可以使用外部晶振加 PLL。我用了 8MHz 晶振,通过配置 PLL 倍频到 80MHz 或 100MHz。这个主频下做单总线时序、Modbus CRC 运算都绰绰有余。需要提醒的是,晶振引脚周围不要走开关电源的电感或脉冲电流路径,否则时钟抖动会让 UART 的波特率误差变大,RS485 传输偶尔出现乱码。
供电和隔离是嵌入式设备柜里的重点。如果控制器外壳里同时有继电器、开关电源和单片机,地线电位在继电器动作瞬间可能跳动很大。RS485 远程总线一般建议做电气隔离:隔离型 RS485 收发器加隔离 DC-DC,或者用数字隔离器把 UART 信号隔开。温度传感器总线如果只在本机柜内,可以不隔离,但传感器端的地和主控地必须可靠连接,不能靠外壳接地勉强凑合。
做过几个现场项目之后,我的结论是:电源和地处理好,能解决一半以上的“偶发故障”。很多温度读数飞跳,追到根上不是传感器不行,而是主控电源纹波太大或者地线环路把电机噪声引入了传感器。
2.3 远程通信接口的选型与现场布线
远程温度上报我优先选了 RS485 半双工接口,因为 HVAC 现场最常见、兼容性最好,很多楼宇自控系统的网关都直接支持 RS485 Modbus。RS485 是差分信号,抗共模干扰能力强,理论传输距离到 1200 米,实际工程控制在 800 米以内更稳妥。
接线时有几个点容易翻车。第一,A/B 两个信号线不要接反,接反的直接后果是整个总线上所有设备都通信失败。第二,总线两端要接 120Ω 终端电阻,而不是每个设备都接。很多设备板上会默认带一个终端电阻,如果每个板子都短接,等效阻抗会低于 120Ω,导致信号反射和通讯距离缩短。第三,屏蔽双绞线屏蔽层单端接地,不要两端都接大地,否则可能形成地环路,低频干扰反而更严重。
如果把远程上报扩展到以太网,PIC32MX795F512L 自带 MAC,可以外接以太网 PHY 芯片和网络变压器,走 RMII 接口。这样主控可以通过 Modbus TCP 或者 MQTT 上报温度。对 HVAC 这种长期运行设备,我建议网络流量尽量小,每 5 到 10 秒上报一次足够,没必要追求秒级刷新。
3. 固件实现:单总线驱动、滤波和 Modbus 上报
3.1 单总线时序与底层驱动
写单总线驱动前,一定要把时序图和数据手册放在手边。这类数字温度传感器的通信协议通常基于单总线,核心动作只有三个:复位、写位、读位。复位时主机拉低总线至少 480 微秒,然后释放,传感器会在 15 到 60 微秒内拉低总线作为存在脉冲。写位时,主机先拉低总线,如果要写 0,就保持低电平超过 60 微秒;要写 1,则只保持短暂的低电平后立即释放,让上拉电阻把总线拉高。读位时,主机拉低总线一小段时间后释放,然后在 15 微秒内采样总线电平。
这些时间窗口只有几十微秒,而且对延时有严格要求。我一开始偷懒用了普通空循环做延时,结果不同编译优化级别下,读到的温度完全不一样。后来老老实实用定时器做了微秒级延时函数,或者用带 volatile 变量的循环来避免被优化掉。
下面是一段简化版驱动,逻辑上可以直接参考,但延时参数必须按实际传感器手册调整:
#include <stdint.h> #define OW_LAT LATBbits.LATB8 #define OW_TRIS TRISBbits.TRISB8 #define OW_PORT PORTBbits.RB8 #define CMD_SKIP_ROM 0xCC #define CMD_CONVERT 0x44 #define CMD_READ_SCRATCH 0xBE // 延时函数依赖主频,移植时请重新标定 static void ow_delay_us(int us) { volatile int i; while (us-- > 0) { for (i = 0; i < 10; i++); } } static uint8_t ow_reset(void) { uint8_t presence; OW_TRIS = 0; // 输出模式 OW_LAT = 0; // 拉低 ow_delay_us(480); OW_LAT = 1; // 释放 OW_TRIS = 1; // 输入模式 ow_delay_us(70); presence = OW_PORT; // 读取存在脉冲 ow_delay_us(410); return (presence == 0) ? 1 : 0; } static void ow_write_bit(uint8_t bit) { OW_TRIS = 0; OW_LAT = 0; ow_delay_us(8); if (bit) { OW_LAT = 1; ow_delay_us(60); } else { ow_delay_us(60); } OW_LAT = 1; OW_TRIS = 1; ow_delay_us(2); } static uint8_t ow_read_bit(void) { uint8_t bit; OW_TRIS = 0; OW_LAT = 0; ow_delay_us(2); OW_LAT = 1; OW_TRIS = 1; ow_delay_us(6); bit = OW_PORT; ow_delay_us(55); return bit; } static void ow_write_byte(uint8_t byte) { for (uint8_t i = 0; i < 8; i++) { ow_write_bit((byte >> i) & 0x01); } } static uint8_t ow_read_byte(void) { uint8_t byte = 0; for (uint8_t i = 0; i < 8; i++) { if (ow_read_bit()) { byte |= (1 << i); } } return byte; } float read_local_temperature(void) { uint8_t scratch[9]; int16_t raw; float temp; ow_reset(); ow_write_byte(CMD_SKIP_ROM); ow_write_byte(CMD_CONVERT); ow_delay_us(750); // 等待转换完成 ow_reset(); ow_write_byte(CMD_SKIP_ROM); ow_write_byte(CMD_READ_SCRATCH); for (uint8_t i = 0; i < 9; i++) { scratch[i] = ow_read_byte(); } raw = (scratch[1] << 8) | scratch[0]; temp = raw * 0.0625f; return temp; }代码里我用了一颗传感器时的快速读取方式。多传感器场景不能直接跳过 ROM,需要先枚举总线上的设备 ID。单总线类数字温度传感器一般都会内置唯一序列号,固件在初始化时做一次总线搜索,把所有 ID 存到数组里,之后轮询每个 ID 读取温度。第一次写总线搜索算法会觉得麻烦,但写完之后一劳永逸,后续插拔传感器都能自动识别。
3.2 多点温度采集与滤波策略
HVAC 现场常见需求是挂 2 到 4 个传感器,分别是本地机柜温度、送风温度、回风温度、室外温度。多点采集时,我建议先发一次全局转换命令,然后根据传感器最大转换时间等待,再逐个读取。这样比挨个发转换、挨个等待要快得多。
采集到原始温度后,不要直接把读数拿去报警或上报,因为传感器本身会有随机噪声,HVAC 现场还有风机引起的温度扰动。我用一阶低通滤波,公式很简单:
filtered = filtered * 0.8f + current * 0.2f;如果采样周期是 1 秒,这个滤波相当于时间常数约 5 秒,既不会掩盖设备柜突然过热的趋势,又能把风道里风速变化引起的毛刺修平滑。如果采样周期是 500ms,可以把系数改成 0.9 和 0.1。想更平滑也可以改成二阶滤波,但对温度控制来说没必要,响应太慢反而会造成报警滞后。
滤波参数放在配置区,现场可以通过 Modbus 写寄存器调整。默认给一套稳妥的值,运维人员后续可以根据实际环境微调。这个设计在项目验收时特别加分,因为现场调试人员往往想自己调灵敏度。
除滤波外,我还会做传感器断线检测。单总线设备长时间无应答、CRC 校验连续失败三次,就标记该通道温度无效,同时把故障标志写到 Modbus 寄存器。不要用某个特殊温度值来表示故障,比如“-55°C”,否则上位机容易把故障和真实低温混淆。
3.3 Modbus RTU 从机与寄存器设计
远程上报我用了 Modbus RTU 从站协议,功能码只开放 04 读输入寄存器,最多加一个 03 读保持寄存器保存配置参数。对温度监测来说,功能码越少越安全,别把不必要的历史数据区暴露给总线。
寄存器设计上尽量简单:
| 寄存器地址 | 说明 | 数据格式 |
|---|---|---|
| 40001 | 本地温度 | 0.1°C,有符号 |
| 40002 | 远程温度 | 0.1°C,有符号 |
| 40003 | 报警状态 | bit0 本地超温,bit1 远程超温 |
| 40004 | 传感器故障标志 | bit0 本地传感器断线 |
实际代码里,Modbus 通信最重要的是 CRC16 校验。少写了 CRC,或者高低字节顺序写反,都会导致主站一直报超时。
uint16_t modbus_crc16(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 & 0x0001) { crc = (crc >> 1) ^ 0xA001; } else { crc >>= 1; } } } return crc; }接收帧我用状态机实现:第一步匹配从机地址,第二步等待功能码,第三步累计数据长度,第四步校验 CRC。校验通过后解析数据段,校验失败直接丢弃。不要在中断里做完整解析,可以把接收到的字节缓存起来,在主循环里统一处理。中断里做太多计算,UART 接收会有概率丢字节。
通信参数我默认配置为 9600bps、8 数据位、1 停止位、无校验。这个配置在楼宇自控领域最兼容。如果现场对实时性要求高,可以改到 19200bps,但总线长度超过 500 米时不建议再往上升,速率越高,RS485 对线缆质量和终端匹配越敏感。
4. 调试记录:现场问题、排查与标定
4.1 高频问题速查表
这套系统现场调试过程中,我遇到最多的问题集中在表格里这几种。如果你也碰到类似现象,可以直接对照排查。
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| 读不到任何传感器 | 总线上拉电阻缺失、传感器供电异常、接线断路 | 先量传感器两端电压,再量数据线和 GND 之间有没有上拉到 3.3V |
| 传感器序列号时有时无 | 数据线接触不良、总线星形分叉太长 | 检查端子压接,尽量改成菊花链 |
| 读数一直是上电默认值 85°C 左右 | 只读了暂存器,没发转换命令,或转换等待时间不够 | 确认固件先发 CONVERT 命令,并等待足够时间 |
| 数据经常 CRC 错误 | 线缆过长、上拉电阻不合适、干扰串入 | 缩短总线、换屏蔽线、调整上拉电阻,必要时用隔离方案 |
| 多个传感器读数互相串 | 跳过 ROM 命令时总线冲突,或多个传感器共用一个地址 | 枚举并区分每个传感器 ROM ID |
| Modbus 主站无响应 | 从机地址不对、波特率不匹配、A/B 接反、终端电阻问题 | 用串口调试助手直连从机,确认设备地址和参数 |
调试的时候,我习惯先用一个独立串口把单片机收到的原始单总线数据打印出来,确认传感器链路正常,再测 Modbus 报文。这样能快速把问题分隔开:是采集层有问题,还是协议层有问题。如果不分层排查,经常会在一个其实没坏的传感器上浪费半天。
4.2 温度传感器的两点标定
数字温度传感器出厂的绝对精度通常够用,但如果项目要求误差在 ±0.5°C 以内,或者现场环境导致传感器有固定偏差,我会做两点标定。方法很简单:准备两个稳定温度源,比如冰水混合物大约 0°C,以及一个稳定的温水浴大约 40°C 到 60°C。用参考温度计记录实际温度,同时读出传感器的原始值,然后计算斜率修正和偏移修正。
假设参考温度和传感器读数的关系是线性:
T_actual = K * T_sensor + B先测两点得到两组值,然后计算:
K = (T2_actual - T1_actual) / (T2_sensor - T1_sensor) B = T1_actual - K * T1_sensor把 K 和 B 存入 Flash 或 EEPROM,每次采集后代入。需要提醒的是,校准温度源越接近传感器实际工作范围越好,不要在 0°C 校准完直接拿到 80°C 环境用,线性误差会让高温段的偏差反而变大。
传感器自热是读数偏高的一个隐藏原因。PJ85718DM 这类传感器本体在持续工作时会有轻微温升,如果传感器贴在发热元件或密闭不透风的位置,即使做了软件校准,长期读数也会跟着环境热往上涨。我在安装时尽量让传感器探头与被测物体接触良好,同时避免传感器和功率电阻、大电流走线靠太近。
4.3 长期运行稳定性的几个细节
嵌入式设备一旦投运,可能是两年三年不关机。针对长期运行,我在固件里加了几个保险措施。
单总线操作必须带超时。复位、读位、写位这些操作如果不加超时判断,一旦总线被外接故障设备拉死,主控会卡在单总线驱动里出不来。一个 100ms 超时保护加进去,能保证最坏情况下系统还能重新初始化总线。
看门狗要喂,但不能盲喂。UART 接收中断里喂狗会导致主循环卡死时设备看起来还挺正常,故障迟迟暴露不了。我习惯在主循环末尾喂狗,并且给每个周期性任务加执行时间统计,超过预期的任务要打日志。这样看门狗复位的记录才能帮你定位问题。
现场还有一个非常容易踩的坑:手持编程器或者调试器连接上之后,总线时序被调试中断打断,温度读取偶尔失败。这不是代码问题,是调试器占用 CPU 造成的。出现这种情况时,先拔掉调试器再测试,别在烧录器挂着的时候判断产品稳定性。
5. 从测温到控制:组网、PID 与 HVAC 联动
5.1 多站点组网与云端上报
单台设备测温意义有限,真正有价值的是一套系统里几十台设备同时上报温度。用 RS485 组网时,每个从机设置独立地址,上位机按地址轮询。轮询周期不要太短,几十台设备每秒轮询一遍会占满总线,我一般设置 500ms 到 1s 轮询一次实时温度、5s 同步一次配置即可。
如果需要上云,RS485 可以先接一个网关,把 Modbus RTU 转成 MQTT 或 Modbus TCP。网关只做协议转换,不过度处理数据。这样单片机端的固件不用跟着平台换,将来换云平台只需要改网关配置。组网之后,每台设备唯一的标识建议由硬件拨码和 MAC 地址共同构成,避免现场更换设备后云端出现重复 ID。
上报数据时,把本地温度和远程温度分开作为两个字段。例如 HVAC 场景里,本地温度对应“机组电气室温度”,远程温度对应“送风管温度”,两者在云端的意义完全不同。不要笼统地合并成“温度1、温度2”,早期我吃过这个亏,运维人员看曲线根本分不清哪条是送风温度,后来把所有字段的物理含义写清楚了才好用。
5.2 HVAC 联动控制的控制逻辑切入点
温度监测最终要服务于控制。在 HVAC 应用里,最常见的需求是:当送风温度超过设定值,加大风机转速或打开冷水阀;当回风温度过低,减小阀门开度防止过冷。PIC32MX795F512L 有 PWM 输出和丰富定时器资源,可以直接输出 0-10V 或 PWM 信号控制模拟设备,也可以通过继电器做分步启停。
做联动控制时,控制策略有一个常见误区:直接把两个温度平均后作为反馈。实际上本地温度和远程温度的动态特性完全不同。本地机柜温度对风机动作响应很快,远程风道温度响应慢得多,两者加权平均会让控制环震荡。我通常的做法是:远程温度作为主反馈,决定制冷或加热的基准量;本地温度作为保护反馈,只在超过安全阈值时叠加修正或直接限制输出。通俗说就是“远处管舒适,本地管安全”。
如果要做 PID,采样周期 1 秒就够,比例项和积分项可以先用经验值粗糙调定。温度变化慢,积分时间不用太短,否则容易积分饱和。现场调 PID 时,先把 P 调大看震荡,然后减半,再慢慢加 I,这是笨办法但很可靠。
6. 最后想分享的几条现场经验
6.1 把传感器捏在手里,先验证链路
调试的时候我特别推荐一个土办法:把传感器捏在手指间,看着温度曲线有没有立刻往上升。如果读数纹丝不动,大概率是时序或接线问题,不是传感器坏了。如果数据跳来跳去,再查滤波和干扰。这套方式能最快把“采集链路”和“业务逻辑”隔离开。很多时候我在现场折腾半天,最后发现是传感器没接牢、接触电阻时大时小,手一捏倒是碰巧压紧了,问题反而不见了。
6.2 精度是否够用,要看监测对象
做温度监测,不需要盲目追求高精度。HVAC 应用的舒适性控制,±0.5°C 的重复性比绝对精度重要;设备机柜过热保护,更看重趋势变化和响应速度,偏差一两度也不致命。真正重要的是系统稳定、数据连续、故障能及时暴露。所以我在这个项目里没有堆高精度器件,而是把更多精力花在线缆、隔离、滤波、看门狗和断线检测上,这些才是长期运行里最影响体验的部分。
项目做完回头看,硬件和固件都不复杂,但每一步都藏着行业经验的取舍。希望这篇内容能帮正在做类似温度监测项目的你,少踩几个我已经踩过的坑。