上次给空调机组做控制器,我一开始也没把“温度测点”太当回事,结果在现场差点被远程那一路折腾到怀疑人生。这个项目里,控制柜距离执行器不算远,但送风温度探头要拉到二十几米外的风管上,还有两个房间的室温测点分布在四十几米开外。控制器用的 PIC18F85K22,温度探头用的是 PJ85718DM——一颗单总线数字温度传感器,命令时序和常见的 DS18B20 生态完全兼容。整套系统弄下来,难点不在于“读温度”本身,而在于本地测点和远程测点的电气差异、1-Wire 时序的稳定性、以及 HVAC 控制逻辑怎么把这些温度真正用起来。
这篇就当一次完整复盘吧:从为什么选这个组合,到硬件上拉电阻怎么选,再到固件时序怎么写、现场哪些坑必须避开,我都捋一遍。做嵌入式温控的同行,尤其是暖通空调、楼宇自控方向的朋友,应该能直接从里面抄到不少能落地的方案。
1. 方案定位:本地与远程温度为什么不能混为一谈
1.1 先分清“本地测点”和“远程测点”的物理边界
我这套系统的本地测点,指的是离控制器不到 1 米的回风温度探头;远程测点是送风风管、盘管防冻、还有两个独立区域的室温探头。物理距离就决定了它们在电气环境上是两种完全不同的处境。
本地探头线很短,我可以直接把传感器焊在转接板上,用最短的线接到 MCU,干扰路径基本可控;远程电缆要经过变频器附近、接触器旁边、电机的动力电缆桥架,现场还经常有工人把信号线和强电绑在一起走线。更麻烦的是,远程端子排长期处在振动和温湿度变化环境里,氧化、松动都是迟早的事。
所以我没有把本地和远程探头全挂到同一条 1-Wire 总线上,而是用了 PIC18F85K22 的两个独立 GPIO 分别做 DQ_LOCAL 和 DQ_REMOTE。原因很直接:本地回风温度是参与 PID 控制的主反馈量,如果远程线路上某个传感器出问题,把整条总线拉死,本地温度也读不回来,那风机、水阀就全乱套了。分开以后,远程总线就算彻底故障,本地温度依然能维持设备的基本保护逻辑。这个冗余设计在控制类产品里不是花活,是真能救命的东西。
1.2 选型逻辑:PJ85718DM 适合多点分布式测温,PIC18F85K22 适合工业现场
传感器选型时我对比过 NTC 热敏电阻和 PT100。NTC 成本确实低,但分散测点意味着每一路都要单独做分压、校准和长线补偿,现场换一个探头,曲线又得重新来一遍,维护成本直接抵消掉物料成本。PT100 精度高,但信号调理电路和 ADC 差分输入占了板子面积,在多点测温场景下价格很快失控。
PJ85718DM 这类单总线数字传感器就没有这些问题。它内部已经做好了模数转换,出来的数据直接就是摄氏度,-55°C 到 +125°C 范围内基本精度在 ±0.5°C 上下,12 位分辨率下最小分辨率 0.0625°C,对 HVAC 控制来说已经非常够用。更关键的是,它支持一条总线挂多个器件,每个传感器都带唯一 ROM ID,主机通过 ROM 命令就能区分总线上不同的探头,非常适合风管、房间、水管这种多点布置。
DM 后缀的探头形式本身就是带封装、引出线的,适合直接塞到风管里面,抗潮湿和粉尘能力比裸芯片强很多。主控选 PIC18F85K22,看中的是几个硬指标:工作电压范围宽、工业级温度范围、主频能到 64MHz,处理 1-Wire 这种微秒级时序足够了。它带 128KB 程序 Flash,跑完温度协议之后还有充足空间放 PID、MODBUS、显示逻辑。80 引脚的封装把 I/O 做得很宽裕,后续加按键、继电器输出、串口通信都不会捉襟见肘。
1.3 单总线拓扑与两路测温的整体架构
整个系统在物理拓扑上分成两条总线:
第一条是 DQ_LOCAL,挂在靠近 MCU 的板边连接器上,只接回风温度探头,上拉电阻 4.7kΩ,线长控制在 1 米内,这条总线的工作环境最干净,固件里负责给 PID 提供回风温度反馈。
第二条是 DQ_REMOTE,通过屏蔽电缆引到端子排,再接远端的送风温度、防冻温度和两个房间温度探头,最多支持 8 个设备。上拉电阻我用的是 2.2kΩ,因为远程线长、电容大,需要更低阻抗提供更快的上升沿。每条总线在固件里都是独立的扫描状态机,各自轮询、各自维护 CRC 失败计数、各自做故障超时。这样设计的好处是,远程总线出故障时,我可以把故障状态上报给上位机,但本地总线上的回风温度照常参与控制,设备不会因为单点传感器故障直接停机。
供电方面,5V 给传感器供电,PIC18F85K22 也工作在 5V 系统上,DQ 信号线不需要额外做电平转换,直接对接。整板电源由外部 24V 开关电源降压得到,电源端加了防反接和 TVS。
2. 硬件与电气设计:接口、上拉、电源和抗干扰
2.1 引脚连接与最小原理图
PJ85718DM 跟很多单总线温度传感器一样是三个引脚:VDD、DQ、GND。硬件接法非常传统:VDD 接 5V,GND 接系统地,DQ 通过上拉电阻接 5V,然后直接连到 PIC18F85K22 的 RD0(本地)和 RD1(远程)。
我把每个远程探头的连接器都做成了三针端子,并且明确标注 DQ / GND / VCC,不允许只用两根线。原因后面讲寄生供电时会细说。本地那一路,传感器板可以直接插在主控板上,不需要端子,但要留一个 0Ω 电阻的选项,方便调试时断开传感器,测量总线波形。
主控侧的 DQ 引脚都加了 33Ω 串联电阻,作用是限制浪涌电流,同时能在一定程度上隔离外部电容对 MCU 引脚的影响。引脚旁边对地并联了一个双向 TVS 管,选的是 6V 左右的钳位型号,防静电和浪涌。需要注意,不要在 DQ 上并过大的对地电容,有些工程师习惯在每根线上加 100nF 滤波,这个做法在 1-Wire 长线上绝对不能抄,那会让信号上升沿慢到无法识别。
2.2 上拉电阻如何根据线长选
1-Wire 总线是开漏结构,主机和从机只负责把总线拉低,释放以后要靠上拉电阻把电平恢复成高电平。这个恢复速度决定了总线上能跑多长电缆、能挂多少个传感器。
总线上的分布电容是主要敌人。普通屏蔽电缆的芯线对地电容大约 100pF/m,30 米电缆就有 3nF。上拉电阻乘上总线电容,就是 RC 常数。上升沿太慢的话,主机采样时会读到不确定电平,轻则偶尔 CRC 错误,重则完全读不到设备。
我实际使用的原则是:
- 线长不超过 3 米,传感器数量少于 3 个,用 4.7kΩ 上拉;
- 线长 3 米到 30 米,或者单条总线超过 5 个传感器,换成 2.2kΩ;
- 线长超过 30 米,减小到 1kΩ,但必须确认所有从机的灌电流能力,否则低电平可能拉不下去。
单纯减小上拉电阻也不一定就行。总线上所有设备同时下拉时,总线电流等于上拉电压除以上拉电阻,1kΩ 在 5V 下的灌电流就有 5mA,如果从机数量很多,这个电流会成倍增加,部分芯片在极限情况下会过流损坏。所以更稳妥的做法是分段:在总线中间和末端各加一个 2.2kΩ 上拉,而不是在主控端用一颗 1kΩ 硬拉。我远程那条线实际就是这样处理的:主控端一颗 2.2kΩ,远程端子排旁边再并了一颗 2.2kΩ,效果比单端硬拉好很多。
2.3 为什么强烈建议三线供电,而不是寄生供电
单总线器件有个经典的寄生供电模式,就是 VDD 和 GND 短接,芯片完全靠 DQ 线上的高电平给内部电容充电。这样传感器只需要两根线:DQ 和 GND。很多教程都喜欢这么做,因为现场布线省一根芯。
但 HVAC 这种现场,我劝你千万别用寄生供电。原因是温度转换期间传感器需要的电流比较大,转换时间又长达几百毫秒,DQ 线如果只是被上拉电阻供给的微弱电流充电,内部电容很快就放完,转换结果就会失效。长线上还有分布式电容的影响,你在主控这边使劲拉高 DQ,传到末端也已经被电容吸收得七七八八了。
我在这个项目里所有远程探头都用了三芯屏蔽线:一根 5V、一根 GND、一根 DQ。三根芯线最好用双绞线结构,DQ 和 GND 绞合在一起,5V 单独走。这样 DQ 信号的回流路径就在自己身边,环路面积小,抗干扰能力强。接线端子上我还特意把 GND 放在 DQ 和 5V 之间,防止安装工人误操作短接。
2.4 风管现场的防潮、防静电与屏蔽接地
风管里湿度大,探头封装本身防水,但接线端子不防。我在端子盒里灌了导热密封胶,既固定线缆,又防止水汽沿芯线渗入。传感器探头不要直接贴在风管金属壁上,那样测到的其实是管壁温度,不是空气温度。探头应该伸到风管截面靠近中心的位置,至少要插入管径三分之一以上,并且迎风安装。
屏蔽线的屏蔽层要在主控侧单点接地,远端不接。两头都接地会形成地环路,变频器、电机产生的共模电流会直接灌进屏蔽层,反而把干扰耦合到信号线上。这个细节在调试初期很容易被忽略,我用示波器验证过,一头接地比两头接地在 DQ 线上能少掉不少毛刺。
电源入口处我加了共模电感和 TVS,防止现场浪涌通过电源线打到传感器上。传感器供电电压虽然只有 5V,但 HVAC 现场的 24V 交流电、变频器母线,都可能通过接线错误或感应电压串到长线上,所以隔离和保护一个都不能省。
3. 固件实现:1-Wire 时序、多传感器识别与温度解析
3.1 用 PIC 引脚模拟 1-Wire 总线的底层函数
PIC18F85K22 的 IO 没有硬件 1-Wire 控制器,所以底层全靠软件模拟。实现方式很简单:把引脚配置成输出并输出低电平,就是拉低总线;把引脚配置成输入,外部上拉电阻就会把总线拉高。PIC 的 TRIS 寄存器控制方向,LAT 控制输出锁存,PORT 读引脚电平。
我用了 RD0 和 RD1 分别做两条总线,宏定义如下:
#define OW_LOCAL_DIR_IN TRISDbits.TRISD0 = 1 #define OW_LOCAL_DIR_OUT TRISDbits.TRISD0 = 0 #define OW_LOCAL_LOW LATDbits.LATD0 = 0 #define OW_LOCAL_PIN PORTDbits.RD0 #define OW_REMOTE_DIR_IN TRISDbits.TRISD1 = 1 #define OW_REMOTE_DIR_OUT TRISDbits.TRISD1 = 0 #define OW_REMOTE_LOW LATDbits.LATD1 = 0 #define OW_REMOTE_PIN PORTDbits.RD1写时序时最关键的是延时精度。我用的是 XC8 编译器的__delay_us函数,使用前需要设置_XTAL_FREQ。延时参数要留一定余量,但不能过量,否则周期长度会超出 1-Wire 协议的要求。
读时序要注意采样点。主机把总线拉低后释放,从机在时隙里决定是否继续拉低总线来代表数据 0。我在释放后等了大概 10µs 再采样,而不是立刻采样。这是因为远程线长、电容大,电平稳定需要时间;如果采样太早,总线可能还在上升过程中,读到的永远是高电平,数据全是 1。
void ow_write_bit(unsigned char bit, unsigned char portSelect) { if (portSelect == 0) { OW_LOCAL_LOW; OW_LOCAL_DIR_OUT; if (bit) { __delay_us(6); OW_LOCAL_DIR_IN; __delay_us(55); } else { __delay_us(60); OW_LOCAL_DIR_IN; __delay_us(5); } } else { OW_REMOTE_LOW; OW_REMOTE_DIR_OUT; if (bit) { __delay_us(6); OW_REMOTE_DIR_IN; __delay_us(55); } else { __delay_us(60); OW_REMOTE_DIR_IN; __delay_us(5); } } } unsigned char ow_read_bit(unsigned char portSelect) { unsigned char b = 0; if (portSelect == 0) { OW_LOCAL_LOW; OW_LOCAL_DIR_OUT; __delay_us(3); OW_LOCAL_DIR_IN; __delay_us(10); b = OW_LOCAL_PIN ? 1 : 0; __delay_us(50); } else { OW_REMOTE_LOW; OW_REMOTE_DIR_OUT; __delay_us(3); OW_REMOTE_DIR_IN; __delay_us(10); b = OW_REMOTE_PIN ? 1 : 0; __delay_us(50); } return b; }3.2 复位、存在脉冲和 ROM 地址扫描
1-Wire 总线每次操作前都需要复位。主机把总线拉低 480µs 以上,然后释放,总线上所有从机会在 60µs 到 240µs 之间拉低总线,形成一个“存在脉冲”。主机在这个窗口内采样,如果读到低电平,说明总线上还有设备在线。
复位之后发 ROM 命令。对单点系统,最省事的是发0xCC跳过 ROM,然后直接读温度。但我要一总线上挂多个远程传感器,所以必须先扫描出每个传感器的唯一 64 位 ROM 地址。
扫描命令是0xF0,搜索算法会逐位筛出总线上各设备的 ROM。初次开发时可以先把单个传感器接上,读 ROM 命令0x33直接读出地址,再用数组存起来。实际项目中更稳妥的做法是在主控上电时自动扫描一次,把识别到的 ROM 地址打印到调试串口,然后固化到 EEPROM 里。以后每次轮询就用匹配 ROM 命令0x55加上地址,精确点名某一颗传感器,避免把总线上的数据搞混。
我这里一个简化版的扫描伪代码思路:
for (bit = 0; bit < 64; bit++) { read_bit_1 = ow_read_bit(port); read_bit_2 = ow_read_bit(port); if (read_bit_1 && read_bit_2) { // 总线冲突或设备掉线 } else if (read_bit_1 && !read_bit_2) { rom[bit] = 1; ow_write_bit(1, port); } else if (!read_bit_1 && read_bit_2) { rom[bit] = 0; ow_write_bit(0, port); } else { // 两个方向都有设备,需要分支处理 // 工程上一般直接报“总线重复地址/拓扑过复杂” } }实际产品里我不会把搜索算法做得很复杂,因为每条总线的设备数量是固定的,传感器型号也固定。我更加倾向于固定 ROM 表,而不是每轮都全总线动态搜索。动态搜索的好处是灵活,坏处是一旦总线上有干扰,搜索过程可能进入死循环或选错设备。固定表匹配在这种工业场景里更可控。
3.3 温度转换、暂存器读取与 CRC 校验
温度和地址都确认之后,读取流程就是固定套路:复位,匹配 ROM,发送0x44启动温度转换,等待转换完成,再复位,匹配 ROM,发送0xBE读取暂存器。
PJ85718DM 在 12 位分辨率下最大转换时间约 750ms。实际工程中我不会在主循环里干等 750ms,而是用一个状态机:发完转换命令后置一个定时标志,等 750ms 时间到了再回来读暂存器,这期间 MCU 还能处理显示刷新、继电器控制和串口通信。状态机虽然代码多一点,但系统的实时性完全不一样。
暂存器有 9 个字节,前两个字节是温度数据。第一个字节是低字节,第二个是高字节,最高位是符号位。把两个字节拼成一个 int16,再乘 0.0625 就得到实际温度:
int16_t raw = (scratchpad[1] << 8) | scratchpad[0]; float temperature = raw * 0.0625f;负温度也很好处理,因为温度寄存器用的是二进制补码。比如0xFFF0代表 -16,乘以 0.0625 就是 -1.0°C;0xFC90代表 -880,乘以 0.0625 就是 -55.0°C。暖通空调里虽然极少出现零下几十度的工况,但防冻保护要检测 0°C 附近,这个判断必须做对。
CRC 校验一定要做。第九字节是前八个字节的 CRC,我用 LSBF 方式的 CRC-8 来校验,多项式掩码用0x8C,逐位带进位地算:
unsigned char crc8_update(unsigned char crc, unsigned char data) { for (int i = 0; i < 8; i++) { unsigned char xor = ((crc ^ data) & 1); crc >>= 1; data >>= 1; if (xor) crc ^= 0x8C; } return crc; }收到暂存器后把 8 个字节逐个喂进 CRC 函数,算出的结果和第九字节比对。不一致就直接丢弃这一帧,连续三次 CRC 错误就把该测点标记为故障。这里有个实际经验:偶尔一帧 CRC 错误可以通过软件忽略,但连续 CRC 错误基本是硬件问题,先查线缆和上拉,不要只盯着固件调。
3.4 工程滤波与数据有效性处理
温度传感器本身是数字输出,数据不太容易受瞬时干扰,但总线的电气噪声会直接体现在读数上,偶尔会读到明显偏离现场的野值。我在软件里对每个测点做了一个 5 点滑动窗口,窗口内数据先去掉最大值和最小值,再取平均。温度变化本来就有惯性,在 HVAC 场景里根本不需要太激进,平滑之后的数值用来做 PID 反而更好。
另外每个测点还要做有效性判断:CRC 失败次数、连续无响应次数超过阈值就报警;报警期间 PID 输出保持上一个有效值,不允许继续积分。这个在风机盘管控制里特别重要,如果温度反馈丢失后 PID 还在增大输出,水阀会变成一个没头苍蝇,风管温度可能冲到很高。
4. 把温度数据用起来:HVAC 控制逻辑与联网输出
4.1 回风、送风、防冻三个关键测点在控制里的角色
我这套系统的控制对象是空调机组的冷热水盘管和送风机。回风温度探头虽然是“本地”测点,控制逻辑上它却被当成空调区域的综合室温反馈:回风温度高,说明房间热负荷大,水阀开大一点;回风温度接近设定值,水阀就慢慢收力。
送风温度探头在风管内,信号经过 20 多米长线传到 DQ_REMOTE 总线,作用有两个:一是作为盘管出风温度的直接监测,防止送风温度过低或过高;二是用送风温度和回风温度的差值判断盘管换热效率,温差太小可能是水阀卡死或盘管脏堵。
防冻温度探头放在盘管下游最容易结冰的位置。当送风温度开始往 5°C 以下掉,系统先输出预报警,同时把风机频率降下来;掉到 2°C 以下,直接关断水阀反向保护,启动防冻电加热,甚至切段分区的风阀。这些动作都是硬逻辑,不依赖上位机,就算远程总线断了,本地 MCU 也要能独立完成保护。
4.2 根据温差做 PID 与报警联动
PID 的输入我选了送风温度,设定值来自回风温度修正后的目标值。这个组合是我实际调试后确定的:直接用回风温度做 PID 反馈,响应太慢,因为回风温度要等整个房间的空气都循环一遍才有变化;直接用送风温度做反馈,又容易受风管内局部气流影响而波动。折中的方案是:主反馈用送风温度,回风温度只负责修正设定值和判断负荷变化趋势。
PIC18F85K22 算一个温度 PID 的控制周期放在 500ms,扫描每个测点大约 100ms,完全没有压力。输出经过 PWM 占空比变换后接到 0-10V 水阀执行器。PID 参数我没有用太高增益,因为水阀执行器本身的动作时间就有几十秒,增益太高只会震荡。积分项加了抗饱和措施,输出到了上下限就冻结积分。
报警逻辑上,本地和远程的温度都会参与。举例来说,如果送风温度比回风温度还高 10°C 以上,而水阀处于全关位置,那说明电加热被异常开启或阀体泄漏;如果送风温度和回风温度同时快速下降,可能是风机皮带断了,回风测点会因为风量骤减而温度漂移,这些组合判据都要写在报警逻辑里。
4.3 通过 UART/RS-485 上报监控平台
温度数据最终要交给楼宇监控,我用 PIC18F85K22 的一个 UART 接 RS-485 收发器,跑的是 MODBUS-RTU 协议。每 1 秒更新一次保持寄存器,地址表固定:寄存器 0 是本地回风温度,寄存器 1 到 4 是远程几个测点温度,寄存器 5 是设备状态字,CRC 失败次数也放在里面,方便远程诊断。
因为温度值我在固件内部已经转成了浮点,上报到 MODBUS 时必须重新定标。我直接扩大 10 倍存成 int16,比如 25.4°C 就存整数 254。上位机那边统一除以 10,减少浮点数在 Modbus 里的传输歧义。这样虽然损失了 0.1°C 的精度,但暖通空调系统根本不需要 0.0625°C 那么细的显示粒度,换来的是简洁可靠。远程传感器的更换、地址变更,在上位机端要有替换记录的标注功能,这个在现场维护时能省掉一大半的扯皮时间。
5. 现场调试中踩过的坑与排查方法
5.1 传感器“失联”:先从供电与地线查起
不管工程做得多规范,现场总会出点幺蛾子。最常见的问题是整条远程总线读不到任何设备。我一般按这个顺序查:
先看供电:远程端子上的 VCC 和 GND 有没有电压,很多问题出在安装工人把 VCC 和 GND 接反了,或者共用一根接地线时出现压降。再看 DQ 上拉:有些改造项目会把原有传感器总线并到新系统上,结果一条总线接了两套上拉,设备工作反而不稳定。最后看地线:远程设备的地和主控的地必须是同一个参考点,不能只靠机柜的金属外壳导电。
如果总线部分设备能读到,个别设备读不到,优先怀疑那一段线路有问题。我用数码管辅助调试时,会直接给那一段单独接一个临时传感器,逐段二分定位。在没有示波器的现场,这个办法虽然土但非常高效。
5.2 温度跳变和 CRC 错误的上拉与采样调整
远程总线偶尔正确、偶尔跳变,十有八九是上升沿不够快。我在排查时用示波器看过一次,30 米电缆配 4.7kΩ 上拉,上升沿慢得离谱,已经严重影响数据采样。
解决方法有两个:一是把主控端上拉改成 2.2kΩ,并在总线远端再补一个 2.2kΩ;二是调整固件里读时序的采样点位置,从释放后的 10µs 往后再延几微秒。但采样点不能无限延后,因为 1-Wire 读时隙总共只有 60µs 左右,你得保证在时隙结束前采样完并且给下一次拉低留出足够时间。
现场还有一类的跳变是因为屏蔽层接地问题。我反复验证了,屏蔽层在主控侧单点接地最干净。如果发现温度数据像随机数一样乱跳,检查一下是不是施工队把屏蔽层在两端都焊到地了。
5.3 同一总线挂多个探头的顺序与识别问题
远程总线上我挂了送风温度、防冻温度和两个房间温度,如果传感器地址没预先登记,接上就会乱套。我们的做法是上电后通过调试串口逐个扫描 ROM,然后给每个测试点固定匹配 ROM。更换损坏的远程探头时,必须重新扫描并更新地址关联表,否则系统会把新探头的读数当成旧测点。
总线上的设备顺序也很有讲究。不要用“一条线串到底”的菊花链时只靠一根总线连接,而是在每个探头处做三根线的短跳线:进线 DQ、GND、5V,出线同样三根。总线的末端要预留一个可插拔的上拉电阻座,方便调试时调整末端上拉。如果设备挂多了,总线电容会变大,示波器上会看到信号边沿明显变缓,这时可以把末端上拉人为断开,分段测判断到底哪一段拖慢了时序。
5.4 一份实用的现场验收检查顺序
基于这次项目,我整理了一个自己的检查清单,每次新装系统都会照着走一遍:
第一步,检查每个远程端子的 VCC/GND/DQ 接线顺序,上电后测总线静态电平,应该是高电平;第二步,用串口或调试屏逐条总线扫描,确认设备的 ROM 数量和台账一致;第三步,用经过校准的温度计和传感器并排放在同一个稳定温度环境下,比较读数偏差;第四步,让系统连续跑 72 小时,观察 CRC 失败计数是否持续增长;最后,用一个可编程电阻箱在远程端子处制造瞬间短路,验证系统能否自动恢复,而不是死锁在等待状态。
最后再分享一个小技巧:远程总线上任何一个探头出了问题,不要靠感觉一段段查线,先把故障端的 DQ、GND、5V 三根线从端子上全部拆下来,用万用表电阻档量对地短路和断路。很多时候问题不是出在芯片上,而是出在接线端子内部接触不良。这类问题用再高级的调试软件也查不出来,只能靠硬件逐段排除。