做嵌入式和 HVAC 温控项目,免不了跟温度采集打交道。前段时间我基于 PJ85718DM 数字温度传感器和 PIC32MX795F512L 单片机做了一套本地与远程双路温度监测系统,目前用在制冷机组控制柜和远端风管测点上,运行了两周没出过幺蛾子。这篇把方案、硬件连接、底层驱动、Modbus 轮询和踩过的坑一次性讲清楚。如果你正在做暖通控制板、机房温度监控,或者想用一个带以太网/USB 的 MCU 实现多传感器温度采集,这篇应该能省你不少时间。
1. 项目背景与整体方案设计
1.1 为什么非要做本地和远程两路温度
做 HVAC 控制的人应该都有体会:单测一个回风温度远远不够用。设备间里压缩机和冷凝器的散热温度、风管末端的送风温度,往往隔着几十米。我以前跟朋友做过一个冷站改造,控制柜装在配电间,风机盘管在吊顶里,温度传感器线拉到柜子之后读数莫名其妙差了三度,查了半天发现是走线槽跟强电并线太长,传感器供电被拉偏。所以后来再做监测,我坚持把温度拆成两路:本地温度负责反映机组柜体周边环境,远程温度节点就近装在被测风管或房间,再把数据通过 RS-485 汇到主控。
从系统角度说,本地和远程数据一起进控制器,才能支撑真正的逻辑联动。比如夏天制冷时,如果远程送风温度和本地回风温度的差值过大,说明风道堵塞或过滤网脏了;又比如冷媒压力正常但本地温度飙升,多半是柜内散热出了问题。单纯靠一个传感器做阈值报警,很多隐患根本暴露不出来。
1.2 PJ85718DM 和 PIC32MX795F512L 各自是什么角色
PJ85718DM 是一款单总线数字温度传感器,工作电压 3.0V 到 5.5V,测温范围 -55℃ 到 +125℃,在 -10℃ 到 +85℃ 范围内精度能够做到 ±0.5℃。它的通信协议只有一根数据线加一根地线,理论上同一条总线上可以挂多个传感器,每个传感器出厂带唯一的 64 位 ROM 编码。对我来说,最大的价值是长距离传输条件下比模拟热敏电阻抗干扰能力强得多,因为读回来的是数字量,不用再经过长线模拟放大那一套。
PIC32MX795F512L 是我选的主控芯片。它属于 32 位 MIPS 内核的 MCU,主频 80MHz,512KB Flash,128KB RAM,片内集成 6 个 UART、5 个 SPI、5 个 I2C、一个 10/100M 以太网 MAC 和 USB OTG。在这个项目里我用它的 UART 跟远程温度节点对话,用普通 GPIO 读本地单总线传感器,再用另一路 UART 把汇总数据上报给触摸屏。其实你如果只想做个简单温度计,用 8 位单片机也够,但一旦牵扯到多路通信、协议解析、后期还要接以太网或人机界面,32 位 MCU 的余量优势就体现出来了。
1.3 整体架构先画个轮廓
主控板放在控制柜内,本地 PJ85718DM 传感器直接插在板载端子座上,距离 MCU 十几厘米,单总线通信非常稳定。远程节点单独做成一个小板,上面也放一颗 PJ85718DM,用 RS-485 两线制总线跟主控连接,Modbus RTU 协议通信。主控每隔一秒发一次读取指令,远程节点收到后回传 16 位温度值,主控解析校验后存到环形缓冲区供显示和联动判断。
为什么远程走 Modbus 而不是直接拉长单总线?两个原因。第一,单总线虽然允许一定线长,但 HVAC 现场往往有变频器、接触器,电磁环境相当脏,超过十米就容易出时序毛刺;RS-485 是差分传输,共模干扰抑制能力明显更强。第二,Modbus RTU 是行业通用协议,后续想再接一个远程湿度节点、或者让第三方 PLC 直接来读数据,只需要改参数表,不用重新设计通信机制。
2. 硬件设计与电路连接要点
2.1 本地传感器电路怎么接
PJ85718DM 只有三个引脚:VCC、DQ、GND。电源我直接用 PIC32MX795F512L 的 3.3V LDO 输出,DQ 引脚接一个 4.7kΩ 上拉电阻到 3.3V,然后连到主控的一个 GPIO。很多硬件参考图会在 VCC 和 GND 之间加一个 0.1μF 瓷片电容,我实际做过对照,加电容之后传感器在电机启动瞬间的读数跳变明显减少,所以建议别省。
比较容易被忽略的是 DQ 走线。单总线协议对时序要求不算苛刻,但引脚还是尽量短,不要和 PWM 信号或开关电源的纹波走线平行。控制柜里空间紧张时,我习惯把传感器端子放在板子边缘,让插上去的传感器线尽早离开板内干扰区。
2.2 远程节点板和 RS-485 收发
远程节点我用了一颗低成本的 8 位单片机做数据采集,传感器部分依然是 PJ85718DM,接法同上,只不过这颗 MCU 的 UART 经过一个 RS-485 收发器接到总线上。收发器选用带自动方向切换的型号最省事,省去控制 DE/RE 引脚翻转的代码。如果没有自动方向切换,就得在发送前把 DE 拉高、发完后拉低,时间没控制好会把总线直接打崩。
总线两端各加一个 120Ω 终端匹配电阻,这个电阻不是摆设。我第一次搭测试台的时候偷懒只接了主控端电阻,远程节点端没接,结果把波特率从 9600 提到 38400 后波形振铃非常难看,收数据偶发乱码。后来老老实实在远程节点板端也焊上 120Ω,波形立刻干净了。
2.3 电源与隔离的处理经验
远程节点如果和主控共地,RS-485 芯片的 A、B 脚之间还要考虑共模电压问题。在工业现场,长短不一的设备地线会造成地电位差,极限情况能超过收发器的输入范围,所以我在远程节点板上用了隔离 DC-DC 和数字隔离器,把 RS-485 侧和传感器 MCU 侧彻底分开。这套方案成本会高几块钱,但换来的是现场调试少跑几次,我觉得非常值。
主控板的电源我采用 24V DC 输入,经过隔离模块降到 5V,再用 LDO 降到 3.3V 给 PIC32 和本地传感器供电。之所以绕这一圈,是因为 HVAC 工程上普遍用 24V 交流或直流作为控制电源,主控板直接兼容 24V 输入,现场接线的人不会懵。如果直接给板卡 5V,还得额外配一个电源模块,反而容易接错。
3. 软件架构与核心代码实现
3.1 底层初始化先搞定时钟、GPIO 和 UART
软件我用的是裸机前后台结构,定时器 1ms 中断做时基,主循环里跑传感器调度、Modbus 状态机、数据上报。原因很简单:项目功能边界清晰,没必要上 RTOS,但纯阻塞式的延时轮询也不行,Modbus 回复超时会卡死主流程。前后台加状态机这个结构既能保证时序,又容易调试。
初始化代码里比较关键的是时钟配置和串口映射。PIC32MX795F512L 的 UART 引脚可以重映射,我习惯把 UART1 配成 8N1 9600 给 Modbus,UART2 配成 115200 给触摸屏,GPIO 则单独分配一个引脚给单总线。这里贴一段串口初始化示例,XC32 环境下可以直接用:
// UART1: Modbus RTU, 9600 8N1 U1BRG = (80000000UL / 16 / 9600) - 1; // 80MHz PB时钟 U1MODE = 0x8000; // ON, 8N1 U1STA = 0x0400; // UTXEN 使能发送实际上波特率寄存器的计算要看外设总线时钟是否分了频,我板子上把 PB 时钟设成了 80MHz,所以公式里除 16 再除波特率再减 1。如果你用了别的分频,记得先读 CLKDIV 寄存器确认,这地方算错会静默地出现 7% 波特率偏差。
3.2 单总线温度读取的时序与容错
单总线通信的时序核心是:主机拉低总线产生复位脉冲,传感器应答;写 1 和写 0 的时间片长度不同;读时序要由主机主动产生。我的做法是把时序函数封装成三个基础操作,再组合出 skip ROM、convert、read scratchpad 这些命令。具体不逐行解释了,重点说几个容易翻车的地方。
第一是关闭全局中断再操作总线时序。PIC32 的中断延迟虽然短,但在 1-Wire 这种微秒级时间窗里,一个高优先级中断就能让时序超时,所以我用临界区保护整段时序,实测可能影响 μs 级别,对 1ms 中断时基没有影响。第二是读回来的原始温度要拼成 16 位有符号数,PJ85718DM 的 12 位分辨率对应负数用二进制补码表示。拼接时注意符号扩展,否则零下温度会变成天大的正数。
下面是我在实际项目里用的温度读取函数轮廓:
uint8_t ow_reset(void); void ow_write_bit(uint8_t bit); uint8_t ow_read_bit(void); int16_t read_pj85718dm(void) { uint16_t raw; ow_reset(); ow_write_byte(0xCC); // Skip ROM ow_write_byte(0x44); // Convert T // 12-bit resolution: wait max 750ms delay_ms(750); ow_reset(); ow_write_byte(0xCC); ow_write_byte(0xBE); // Read scratchpad raw = ow_read_byte(); raw |= (uint16_t)ow_read_byte() << 8; return (int16_t)raw; }等待 750ms 是 12 位分辨率的最坏转换时间,实际现场仪表允许的话,我更推荐用 10 位分辨率,转换时间降到 187.5ms,精度 0.25℃ 对 HVAC 足够,整个系统的采样节拍会舒服很多。你要是嫌等待浪费 CPU,可以用读忙标志的方式轮询,但代码复杂度会上去,裸机场景我一般直接延时。
3.3 Modbus RTU 轮询远程节点的实现细节
远程温度节点的协议定义成:主控发功能码 0x03 读保持寄存器,起始地址 0,寄存器数量 1,返回的 16 位数据就是温度值乘 10。这样加 0.1℃ 的分辨率,整数传输不丢小数。主控作为 Modbus 主机,需要维护轮询表,解析应答帧时先做 CRC 校验,校验不通过直接丢弃并进入超时处理。
CRC 校验是 Modbus RTU 最容易写错的地方。Polynomial 是 0xA001,初值 0xFFFF,结果低字节在前。我最初图省事用查表法,表格抄错三位导致整整半天都在查为什么通信对不上,后来改成熟练工按位计算,稳定可靠:
uint16_t modbus_crc(uint8_t *buf, uint8_t len) { uint16_t crc = 0xFFFF; for (uint8_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; // 发送时低字节在前 }主控发送请求帧之后,要留一个足够的应答超时窗口。我曾把超时设成 10ms,结果远程节点当场回但经过收发器、线缆、光电隔离后延迟稍大,被迫改成 50ms。50ms 对 HVAC 这种秒级响应的场景毫无压力,却避免了一大半偶发超时误判。
3.4 上位机显示和联动逻辑怎么组织
采集到的本地和远程温度会放到一个全局结构体里,结构体里同时保存时间戳和通信质量标志。触摸屏通过 UART2 定时轮询主控,主控每 500ms 自动上报一次所有温度数据。通信质量标志很重要,显示端如果发现数据帧超时,就显示绿屏改成灰屏,避免误以为 0℃ 是真实温度。实际项目中我遇到过一次远程节点掉电,如果只看数值变化,很容易忽略告警。
联动逻辑我放在了主控里:本地温度达到 60℃ 时输出超温告警,远程温度偏离设定值超过 3℃ 且持续 60 秒时,打开补偿执行器。为什么延时 60 秒?因为真实暖通环境里温度抖动很常见,瞬时超阈值就动作会让执行器频繁启停,半天就废了。这个延时要可配置,现场调试时能临时调短。
4. 关键参数计算、校准与实际测试
4.1 采样周期与滤波参数是怎么定的
本地单总线采样周期定为 1 秒,远程 Modbus 轮询周期 2 秒。这不是拍脑袋定的,采样周期太短会让单总线读时序频繁打断主循环,太长则报警响应跟不上。HVAC 热惯性很大,一秒钟采样完全可以,PID 控制的周期我另外用了 200ms,两套节奏互不影响。
温度滤波我用了一阶低通:filter = α * new + (1 - α) * old,α 取 0.2。为什么不用滑动平均?滑动平均在数据跳变时会跟着变,一阶低通对瞬时毛刺的吸收更自然,而且只需要一个状态变量。把 α 调到 0.1 会让曲线更平滑但响应变慢,现场调试时我一般从 0.2 起步,看曲线过冲再微调。
4.2 现场校准的实操步骤
把传感器和主控装好后,我第一件事不是急着联动,而是先把两个传感器跟一只精度 0.1℃ 的水银温度计放在同一个环境里等 30 分钟,记录各自读数的偏差。PJ85718DM 出厂精度是 ±0.5℃,两个传感器之间可能差 0.3℃ 左右,这种差异在 HVAC 逻辑里会让温差报警误动作,必须在软件里做偏移修正。
修正方法是在传感器结构体里加一个 int16_t offset,初始为 0,读取回来的温度统一加上这个偏移。校准完让我印象最深的是:远程节点因为走线长、电源噪声大,修正前的读数比本地高了 0.4℃,加上 -0.4 的偏移后两边终于对齐。这个偏移我做成参数表,现场可以透过串口命令修改,不必重新烧录程序。
4.3 实测数据跟效果评估
系统在 25℃ 空调运行环境下连续跑了 48 小时,本地温度波动范围 24.8℃ 到 25.4℃,远程温度波动范围 25.1℃ 到 25.6℃,去除毛刺后的稳定度在 ±0.3℃ 以内。最让我满意的是风机盘管启动瞬间,本地传感器读数几乎没有跳变,之前模拟电路方案在那时会瞬间飘 2℃ 以上。
Modbus 通信方面,跑了 2 天统计 86400 次轮询,正常应答 86214 次,超时 186 次,成功率 99.78%。这些超时基本集中在接触器动作的时刻,虽然概率不大,但我的代码已经做了三秒内连续失败才告警的冗余,所以历史记录里没有任何一条误报警。
5. 常见问题与调试坑点实录
5.1 单总线通信不稳定的几个来源
单总线不稳定,我排查的优先顺序是:电源、上拉电阻、线缆长度、终端。本地传感器直接挂在 3.3V LDO 上,如果 LDO 前级输入电压在电机启动时跌到 5V 以下,LDO 输出也会被拉低,传感器就会死掉。解决方法是给 LDO 前多留 1V 以上压降余量,或用更大容量的母线电容。还有一个坑是部分传感器模块板载了上拉,你再外加上拉就是并联,阻值变成一半,拉电流增大,波形反而不干净。所以焊接前看一下传感器模块原理图,确认板载上拉是否已存在。
我遇到过最诡异的一次是传感器线和继电器输出线绑在同一个线槽里,继电器一吸合,单总线读数就跳成 85℃。后来用示波器抓 DQ 波形才看到吸合瞬间总线电平被拉出好几个毛刺,把传感器线挪到另一个线槽后彻底正常。像这种信号干扰排查,建议先把数字示波器接上再看现象,盲改代码没有意义。
5.2 Modbus 通信超时和 CRC 的问题
远程通信超时,我先看 CRC 对不对,再看从站地址和寄存器地址是否匹配。CRC 算错的表现通常是应答帧能收到但长度对,校验永远不通过,而且如果从站收到的请求帧 CRC 不对,从站根本不会回复,这时你在主机端看到的就是超时。所以排查顺序应该是:先看从站有没有回帧,有回帧再算 CRC。没有示波器的话,可以临时把从站 UART 的 TX 接到一个 USB 转串口工具上,直接看从站是否收到正确请求。
波特率不匹配也很容易慢性坑人。PIC32 的 BRG 寄存器算错 5%,对短帧可能碰巧能收到,长帧就随机丢。我建议在所有节点用同一个时钟源换算公式,并在测试阶段故意发长帧(比如读 10 个寄存器)验证,长帧稳定代表波特率没问题。
5.3 温度读数跳变和负温度处理
读数跳变有三种原因:电源噪声、数据线干扰、拼接字节顺序错。字节顺序错表现为温度在 0℃ 附近时读数在 -0.5℃ 和 +255.5℃ 之间反复横跳,这不是干扰而是符号扩展没做。对照一下我的读取函数里(int16_t)raw的强制转换,就能看出问题所在。前面那个 85℃ 跳变则是电源/干扰问题,和符号位无关。
另一个容易踩的是转换时间不够。12 位分辨率下如果只等了 500ms 就发读命令,传感器会回上一次转换结果,表现为读数不变或变化滞后。我在调试时为了省时间把等待时间改成 100ms 测试,结果每个传感器状态都不一样,这个坑放到现场就是"温度半天不刷新"的伪故障。
5.4 看门狗、复位与固件升级的几个教训
主控程序里我开了看门狗定时器,但我曾吃过一个亏:Modbus 在握手阶段因为等待应答而阻塞,看门狗以为死机就复位,复位后继续阻塞,形成复位循环。后来我把所有可能的阻塞点都加上了超时判断,超过 50ms 没回帧就跳出等待。看门狗是用来兜底的,不是用来替代健壮的状态机的,这个观念必须记住。
还有复位期间的 GPIO 状态要小心。PIC32 复位后默认 IO 为高阻态,但 RS-485 收发器的 DE/RE 引脚如果不接上下拉,上电瞬间可能漂浮,导致收发器误进入发送状态,把总线短暂拉死。我在 DE/RE 引脚上加了 10kΩ 下拉,保证上电默认接收模式,这之后总线抢占问题就消失了。
固件升级我一开始用调试器直接烧写,后来程序量大了、现场又不能拆板,就换成了通过 UART 的 bootloader。PIC32 的 bootloader 分区比较简单,但注意要把 3.3V 供电稳定性放在第一位,升级过程中断电会把芯片锁死,只能用编程器恢复。我准备了专门的升级线缆和稳压电源,现场升级必接这两样,至今没再出过砖。
6. 我个人的一点体会
这套方案做完之后,我的最大体会是:HVAC 温度监测的难点从来不在"读温度"这个动作,而在"如何在一个电磁环境恶劣的现场,把两个传感器读到的数据稳定、可信地汇到一起"。PJ85718DM 数字单总线确实省了模拟调理那一大堆麻烦,但真正的工期都消耗在时序容错、通信超时、电源噪声和校准这类工程细节上。如果你打算在自己的项目里复刻这套东西,我建议先搭一个最小系统:一块主控板、一个本地传感器、一个远程 RS-485 节点,把通信跑通以后再逐步扩展,千万别一上来就铺开十几个节点调试,否则你连问题出在哪一路都分不清。后续我打算在这套基础上把 PIC32MX795F512L 自带的以太网 MAC 用起来,让数据直接进楼宇自控网络,届时再跟你们分享。