PIC18F8722+PJ85718DM的RS-485温度监测系统实战 做HVAC暖通空调和嵌入式设备的温度监测真正的麻烦从来不是“测不到”而是“测得准、传得走、还能让该看的人随时看见”。我手头这套方案主控用PIC18F8722传感器前端用PJ85718DM跑一条RS-485总线把本地和远程多个测点的温度全部汇总到一起。本地温度直接挂在主控板上远程温度来自分散在现场各处的从机节点现场跑了两三个月把选型思路、接线细节、固件实现和踩坑过程整理成这篇实战记录希望能给正在做同样事情的朋友省点时间。这套架构不挑行业HVAC里的风机盘管、新风机组、冷站管温都适用嵌入式的设备机柜、配电箱、电机轴承这类测点换汤不换药。只要你有“现场仪表采集 集中管理”的需求下面的方案都可以直接参考。1. 项目整体设计与方案选型1.1 场景拆解为什么HVAC系统必须同时盯着本地和远程温度先想清楚测点分布。一套稍微完整一点的HVAC系统温度测点至少分三个位置机组所在设备间的回风或管温、楼层的送风管温度、还有室外新风温度。设备间里那个温度是“本地温度”中控机房或者值班室屏幕上看到的是“远程温度”。为什么要两路都做因为实际运维里经常遇到这种尴尬设备间里的数码管明明显示正常但中控室大屏上温度已经飘了3℃。反过来中控室如果只说“某区域温度异常”你不可能马上判断是传感器问题还是空调真的吹不出冷风。所以本地保留独立显示和报警远程提供集中汇总两者相互印证。嵌入式的场景其实一样。比如机柜里监测IGBT或功率电阻温度现场有本地LED显示和蜂鸣器报警后台通过总线远程读取很多时候还要叠加超温联动。这套“本地采集 远程汇聚”的框架是通用的区别只在传感器类型和通信介质不同。1.2 主控选型PIC18F8722凭什么胜任这台“温度数据枢纽”PIC18F8722是一颗80引脚的8位单片机Flash有128KB加起来差不多4KB的RAM还有1KB EEPROM两个EUSART增强型串口、两个CCP模块、10位ADC、64个I/O口。从规格上看它作为一台温度数据汇聚的“小枢纽”完全是杀鸡用牛刀但恰恰是多出来的资源带来了很大的工程冗余。选它有几个很现实的原因。第一8位机在HVAC行业里大量存量设备都在用工程师维护起来没有学习成本用STM32固然算力更强但一颗成熟8位机做温度采集和RS-485轮询已经绰绰有余项目进度和供应链都更稳。第二PIC18F8722的工作电压范围很宽5V供电时可以直接和大多数传感器模块电平匹配不用额外加电平转换。第三两个EUSART太有用了一个走RS-485接远程从机总线另一个接调试串口或者接本地显示屏两者互不干扰。补充一点很多人觉得“温度监测嘛用个小MCU就够了”。问题是你要面对的不只是看一个温度数而是本地采集、远程轮询、报警输出、校准数据保存、上位机上报这几件事同时跑。PIC18F8722的Flash和RAM足够我开多个缓冲区不用为了省内存写那种看两天就维护不了的紧凑代码。1.3 前端选型PJ85718DM在采集链路中的角色PJ85718DM在我的系统里承担的是“温度采集前端模块”的角色它把温度这个物理量转换成单片机容易读取的电信号而不是让我自己搭热敏电阻电桥、接运放、做标定。这个选择的核心逻辑是缩短开发周期。为什么不直接用“NTC热敏电阻 10位ADC”原因很实在一是HVAC现场供电波动大NTC这种模拟方案会跟着参考电压漂精度受电源质量影响二是传感器到主控的走线动辄一两米甚至更远模拟电压传输会被继电器、接触器的干扰拉出毛刺三是标定问题自己焊热敏电阻每一台的阻值-温度曲线都要单独标定量产阶段非常难受。PJ85718DM这类模块化前端输出的是数字或PWM信号把温度映射成占空比抗干扰能力比模拟电压强很多。我这批样品手册上给的是-20℃到80℃线性对应0%到100%占空比PWM周期大概1kHz单片机只需要测高电平时间和整个周期的比就能反推温度。需要提醒的是这类模块不同批次、不同版本可能存在差异用之前一定先对好手里实物的手册确认输出模式和映射关系再写固件不要照抄网上的代码。2. 硬件接线与电路设计要点2.1 从PJ85718DM到PIC18F8722本地温度通道的接线本地温度通道的接线不算复杂但有几个位置必须认真处理。PJ85718DM供电我直接用5V因为主控PIC18F8722也是5V供电这样不需要跨电平域。如果手头的模块是3.3V版本信号线上要加电平匹配不要硬接。信号输出脚OUT通过一个1kΩ电阻接到PIC18F8722的RB0/INT0脚同时在信号线上加一个10kΩ上拉到5V防止模块输出开路时引脚悬空。如果现场干扰明显可以在信号线上再并一个100nF电容到地构成一阶低通滤波能有效吸收高频毛刺。为什么非要用RB0/INT0而不是普通GPIO因为我要测量PWM波形的边沿时间普通查询模式在主循环忙着处理RS-485通信时很容易漏掉边沿导致占空比测出来是错的。用外部中断边沿一到自动记录时间主程序只需要在空闲时取结果。但滤波电容不能随便加。100nF电容会让PWM波形的上升沿和下降沿变缓从而让单片机检测到的边沿时刻滞后实际测量的高电平时间会比真实值偏差一些。解决办法是如果现场不是特别恶劣电容宁可用小一点的比如10nF如果必须用大电容就在软件里做补偿或者干脆通过校准偏移量把系统误差抹平。这个我在后面实测部分会展开说。2.2 远程通信链路USART转RS-485的标准接法远程温度监测的核心是通信链路。TTL串口在HVAC现场根本没法走远——电平幅度低、共地干扰大、多机连接也麻烦。所以我用了RS-485半双工差分信号抗共模干扰能力强最远距离可以到上百米一条总线上挂多台从机。接线架构如下PIC18F8722的USART1 TX和RX连接到一颗RS-485收发器上收发器的A、B差分对接到双绞线主机和总线最远端的从机两端各接一个120Ω终端电阻中间节点不跨接。波特率选96008N1这个速率在长线上既够用又稳定实测下来比19200的误码率低很多。双绞线用屏蔽双绞线屏蔽层单端接地不要两端都接地否则会形成地环路电流反而引入干扰。A、B两个端子接反是现场掉过最多坑的地方主机A接从机B、主机B接从机A一旦接反整个总线全部“失联”。还有一个RS-485必须处理的细节总线空闲没有任何节点发送时A、B之间的电平差是不确定的可能落在接收器判断的临界区这时候主机会收到一堆乱码。解决办法是在总线A、B之间加上下拉偏置电阻让空闲状态稳定在一个确定的电平确保不发送时总线表现为空闲状态。2.3 供电和抗干扰24V风机盘管现场的“隐形杀手”HVAC设备现场的供电环境比我预想的要脏得多。风机盘管的电机启停、继电器吸合放开的瞬间24V母线上会有明显的电压跌落和反向浪涌。之前有块板子在实验室跑得好好的一到现场就无缘无故复位查到最后就是电机启动瞬间把5V电压拉低到4.2V左右单片机进入欠压状态。我的处理办法是系统从外部接入24V经过一级DC-DC降到5V给主控和传感器模块供电。5V母线入口加大电解电容470μF以上再并一个0.1μF陶瓷电容吸收高频噪声PJ85718DM的供电支路再加一个磁珠把它和数字电路的电源隔离开。如果外部供电是直流24V输入口还要加反接保护二极管和TVS管防止接反或浪涌打坏后端电路。RS-485端口的防护同样不能省。A、B线直接暴露在现场线缆上容易受到感性负载启停的干扰和静电冲击在收发器A、B各串联一个电阻限流对地并联TVS管对差分信号之间也加一个TVS能扛住大部分现场干扰。要始终记住温度监测系统的传感器线、总线线缆是信号回路一定要和220V动力线分开走管并保持一定距离。3. 固件逻辑与核心代码实现3.1 初始化时钟、GPIO、UART和定时器固件我用的C语言开发主循环 中断的结构没有上RTOS温度监测这种任务用超循环就够了上了RTOS反而增加不必要的复杂度。系统时钟用外部4MHz晶振HS模式。为什么不用内部振荡器PIC18F8722内部振荡器虽然能省两颗晶振电容但UART波特率精度要求比较高外部晶振更可靠温度监测系统长期运行波特率漂移会导致通信时好时坏。关键初始化代码如下#pragma config OSC HS void sys_init(void) { // RB0/INT0 作为PWM输入 TRISBbits.TRISB0 1; // 输入 INTCON2bits.INTEDG0 1; // 初始上升沿触发 INTCONbits.INT0IE 1; // 使能INT0中断 INTCONbits.GIE 1; // 开全局中断 // USART1RS-485远程通信9600 8N1 SPBRG1 25; // 4MHz/(16*9600)-1 ≈ 25 TXSTA1 0x24; // 使能发送BRGH1 RCSTA1 0x90; // 串口使能连续接收使能 // Timer11:8分频内部时钟 T1CON 0x31; // 状态指示LED所在的IO口设为输出 TRISDbits.TRISD0 0; LATDbits.LATD0 0; }SPBRG的计算方式要简单验证一下4MHz时钟16倍过采样9600波特率分频值 4000000 / (16 × 9600) - 1 ≈ 25。这个值算出来是25误差不到0.2%对UART通信完全没问题。3.2 温度读取与软件滤波别让一个毛刺拉高整层楼的平均温度本地温度测量核心是测PWM占空比。我用INT0外部中断配合Timer1实现脉宽测量思路是第一个上升沿到来清零TMR1开始计时下降沿到来记录高电平时间下一个上升沿到来记录整个周期时间高电平时间除以周期时间就是占空比。中断服务代码如下volatile uint16_t pwm_high 0; volatile uint16_t pwm_period 0; volatile uint8_t pwm_done 0; static unsigned char edge_state 0; void __interrupt() isr(void) { if (INTCONbits.INT0IF) { INTCONbits.INT0IF 0; if (edge_state 0) { // 上升沿开始计时 TMR1H 0; TMR1L 0; T1CONbits.TMR1ON 1; INTCON2bits.INTEDG0 0; // 改为下降沿触发 edge_state 1; } else if (edge_state 1) { // 下降沿记录高电平时间 pwm_high (TMR1H 8) | TMR1L; INTCON2bits.INTEDG0 1; // 改为上升沿触发 edge_state 2; } else { // 下一个上升沿周期结束 pwm_period (TMR1H 8) | TMR1L; T1CONbits.TMR1ON 0; pwm_done 1; INTCON2bits.INTEDG0 1; edge_state 0; } } }拿到原始脉宽数据之后下一步是温度换算。PJ85718DM的映射是-20℃~80℃对应0%~100%占空比所以温度计算公式是温度 -20 100 ×高电平时间 / 周期时间我用整数运算避免8位机上浮点运算的额外开销结果用0.1℃为单位存储int16_t get_temp_deci(void) { int32_t temp; if (!pwm_done || pwm_period 0) { return -32768; // 无效温度表示传感器异常 } temp -200 (int32_t)1000 * pwm_high / pwm_period; return (int16_t)temp; // 单位0.1℃ }这里我故意用int32_t做中间运算避免“1000 × pwm_high”超出16位整数范围。单片机8位但C编译器对int32_t的支持并不差这几个乘除法运算一次也就几十微秒完全可以接受。软件滤波用的是“5次采样去极值平均”连续采5个有效温度值去掉最高和最低剩下3个取平均。为什么不去简单平均因为PWM脉宽测量偶尔会受到尖峰干扰一次异常值会把平均值拉偏好几度而去极值平均可以完全干掉单个毛刺。实测这个滤波在风机启停瞬间表现尤其明显温度显示稳定很多。3.3 远程温度上报自定义协议与RS-485轮询远程温度通过RS-485总线轮询实现。主机作为总线主站每个远程从机分配一个地址比如0x01~0x05。从机同样用PIC18F8722 PJ85718DM的架构把各自测到的温度值挂在总线上等待查询。协议帧我用了最简单的自定义格式字段字节数说明帧头1固定0xAA地址1目标从机地址功能码10x01读温度0x02读状态数据2返回的温度值单位0.1℃校验1前面所有字节异或值主机发送查询帧的代码void send_query_frame(uint8_t addr) { uint8_t frame[5]; frame[0] 0xAA; frame[1] addr; frame[2] 0x01; // 读温度 frame[3] 0x00; frame[4] frame[0] ^ frame[1] ^ frame[2] ^ frame[3]; // RS-485发送模式下发送 RS485_DE 1; delay_ms(1); for (uint8_t i 0; i 5; i) { while (!TXSTA1bits.TRMT); TXREG1 frame[i]; } while (!TXSTA1bits.TRMT); delay_ms(2); // 等待最后一字节发送完再切接收 RS485_DE 0; }这里有个RS-485的半双工切换细节发送完最后一字节后不能立刻把收发器切到接收模式否则最后一个字节的停止位可能被截断从机根本收不到完整帧。我加了一个2ms延时实测最稳妥。主机轮询完所有从机后把本地温度和每个远程从机的温度一起打包通过USART2发送给上位机或显示屏帧格式同样简单先发一字节地址0x00表示主机再依次发各从机温度值。上位机只管解析不用反向通信所以USART2不需要RS-485半双工处理TTL电平直接接调试串口就行。3.4 本地报警与指示逻辑报警逻辑看起来简单但做不好会闹笑话。我的报警条件是两个本地温度超过上限或者任一远程温度超过上限。上限值默认60℃用于HVAC管道防冻场景会调到5℃下限报警存在EEPROM里支持通过调试串口修改。报警触发后主控置位报警IO点亮LED并驱动蜂鸣器。为了避免报警频繁抖动我加了5秒的确认时间连续5秒超过阈值才真正报警防止传感器偶发毛刺直接触发。EEPROM写入是PIC18系列的经典坑写时序必须严格void eeprom_write_byte(uint8_t addr, uint8_t data) { while (EECON1bits.WR); // 等待上次写完成 EEADR addr; EEDATA data; EECON1bits.EEPGD 0; // 指向数据EEPROM EECON1bits.WREN 1; // 允许写 INTCONbits.GIE 0; // 关键关中断 EECON2 0x55; // 解锁序列 EECON2 0xAA; EECON1bits.WR 1; // 开始写 while (EECON1bits.WR); // 等待完成 EECON1bits.WREN 0; INTCONbits.GIE 1; }关中断那一步不能省。EEPROM写时序要求中间不能被任何中断打断否则解锁序列失效写入失败甚至可能把EECON1寄存器搞乱。4. 实测数据分析与调试记录4.1 精度验证用恒温水浴把仪表“抠”出误差固件写完只是第一步精度必须实测。我做了三组对比冰水混合物0℃、室内空调稳定后的环境25℃左右、恒温水浴50℃标准温度计精度0.1℃。测试点标准值PJ85718DM测得值误差冰水混合物0.0℃0.1℃0.1℃室温25.2℃25.4℃0.2℃恒温水浴50.0℃49.8℃-0.2℃整机误差在±0.2℃以内HVAC管道温度和房间温度监测完全够用。如果是对精度要求更高的冷链场景可以在系统中做一个单点校准记录同一温度下“标准值”和“测量值”的偏差作为固定偏移量写入EEPROM之后每次上报前把偏移量加上。值得注意的是误差在整个测温范围内并不是完全线性的PWM脉宽测量本身的量化误差是±1个Timer计数换算成温度大约是0.04℃很小主要误差来源是前端模块自身的精度和滤波电容引入的边沿滞后。所以我建议不要在软件里做复杂的非线性补偿做一点偏移校准就够了。4.2 通信测试RS-485在100米线缆上的表现远程通信的验证我分两步先实验室短距离验证协议再在现场拉长线测稳定性。实验室阶段用两台从机一根1米双绞线主机轮询1000次零丢包说明协议和代码逻辑没问题。现场测试用的是100米屏蔽双绞线从机分布在一楼和二楼主机在一楼配电间。测试条件轮询次数丢包数误码率1米短线无干扰100000100米长线无终端电阻1000121.2%100米长线两端120Ω终端电阻100010.1%100米长线终端电阻A/B偏置100000一个很直观的教训不加终端电阻长线上的信号反射会在波形上产生过冲导致部分数据帧校验错误加了终端电阻之后误码率直接下降一个数量级。所以总线长度超过30米时终端电阻不是可选项而是必选项。还有一个现场才遇到的怪问题把三台从机接成“手拉手”以外的形状比如从主机拉三条线分别接到三台从机星型拓扑其中一条支路稍微长一点就出现随机丢包。这其实是RS-485的经典禁区总线必须是“菊花链”式串联不能有分支分支会破坏差分阻抗匹配。后来把星型改为总线型问题立刻消失。4.3 现场典型故障排查实录这段时间维护下来我把遇到过的故障按“症状—排查思路—解决办法”整理成了速查表症状可能原因排查步骤解决办法本地温度一直显示-20.0℃PJ85718DM输出开路或停振示波器量输出脚是否有PWM波形检查供电、信号线虚焊模块损坏则更换本地温度偶尔跳变3~5℃信号线受继电器干扰观察故障是否发生在设备启停瞬间信号线改屏蔽线、并10nF滤波电容、软件去极值平均主机收不到某个从机的应答从机地址重复或通信速率不对逐个从机单独测试修改从机地址确认两侧波特率一致总线空闲时主机收乱码A/B缺偏置空闲电平不确定示波器看A-B间静态差分电压在主机端加A/B上下拉偏置电阻设备启动时整机复位24V母线跌落5V掉电用万用表记录启动瞬间5V波形加大输入电解电容DC-DC前端加TVS这里最容易被忽视的是“从机死机导致温度卡在旧值”。主机的轮询逻辑要设置超时发出查询帧后200ms内收不到应答就把对应通道标记为“通信故障”而不是继续显示上一次的温度。故障值在显示屏上用“--”表示并在报警状态里提示通信丢失这样运维人员才不会看着一个几小时前的旧温度做判断。5. 工程经验与避坑建议5.1 校准数据掉电保存的讲究温度传感器在产品出厂前一般都要做标定标定产生的偏移量必须保存下来。很多人图省事每次上电就把校准值写一次EEPROM这其实是在缩短器件寿命。PIC18F8722的EEPROM标称寿命是10万次擦写听起来不少但如果你每次上电都写一天开关机50次两年就消耗掉了。正确做法是把校准值读取到RAM中使用只有通过调试串口主动修改校准值时才触发一次EEPROM写入。写入前读一下当前值如果和要写的值一样就直接跳过。还有EEPROM写数据时如果断电很可能写坏。针对这一点我在EEPROM里存了两个校验副本启动时两个副本一起读若不一致则以校验值正确的那份为准并自动恢复另一份。多花10个字节的存储空间换来的是掉电不怕写坏的安全感。5.2 看门狗与主循环保底8位机跑裸机程序最怕某个函数卡死导致整个采集系统停止工作。我的做法是主循环里用“软件喂狗”一旦程序跑飞看门狗定时器超时强制复位。具体来说主循环每次从头执行到末尾时清一次看门狗计数器。如果某段代码陷入死循环或长时间阻塞喂狗失败芯片自动复位重新初始化。实测下来RS-485接收等待操作最容易卡死——比如从机一直没发送主机代码如果死等接收寄存器整个系统就僵住了。所以我在接收函数里全部用了超时模式循环读接收标志位但设置最大等待次数200ms内没数据就跳出返回超时错误码。这样即使某个从机彻底掉线主机也不会瘫痪。5.3 刷新率、采样次数与功耗的权衡温度本身是一个慢变量没必要追求1ms一次的采样频率。我最终定的是每500ms启动一轮采样每轮采样5次做去极值平均主机的轮询周期是1s把所有从机轮询一圈同时刷新本地温度显示。这个刷新率下CPU的占用率不到10%剩下时间都在低功耗的空转循环里。功耗的大头反而不是单片机而是RS-485收发器它在空闲时仍然在线接收静态功耗有几毫安。对于长期市电供电的HVAC设备这几毫安完全无所谓但如果是电池供电的嵌入式设备就需要在空闲时把收发器切换到关断模式。温度监测的代码要尽量避免浮点运算。8位机上做浮点乘除法一条指令要模拟几十条汇编指令耗时增加很多。我用整数定点运算后单位是0.1℃显示时手动补小数点精度和速度都够了。5.4 后续可以往哪个方向扩展这套系统跑稳定后扩展方向其实很明确。最值得做的是把自定义协议升级成标准Modbus RTU。很多楼宇自控系统本身就支持Modbus协议改成标准协议之后主控板可以直接被第三方上位机或组态软件轮询不需要额外的协议转换器。PIC18F8722的Flash足够大Modbus主从库都能放进去。第二是增加本地显示。我后来在主机上接了一块段码LCD屏第一行显示本地温度第二行轮流显示各远程从机温度现场巡视时一眼就能看到所有温度比带电脑方便太多。PIC18F8722的IO口很多接4位段码LCD用并行口直驱也绰绰有余。第三是冗余测温。PIC18F8722的ADC还空着十几个通道我在关键测点上并联了一颗NTC做备份平时不参与控制当PJ85718DM通道报故障时自动切换到备份通道。HVAC这种常年运行的设备冗余备份的价值是实打实的。最后再说一句现场体会。这类温度监测项目后期90%的问题不在固件而在布线和供电。我曾经有个从机节点安装在吊顶里信号线和风机驱动线绑在一起走了五米本地显示稳定但上位机收到的温度偶尔会跳出一个离谱的大数排查了好久最终把信号线改成屏蔽线并和动力线分开走管才解决。所以设计阶段一定要把现场布线和电源拓扑当成一等公民来考虑而不是等项目上线后出了问题再补救。固件上的坑踩几次就踩完了现场布线的坑往往要花好几倍的时间去填。