ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

单总线温度传感器与STM32在HVAC远程测温中的工程实践

单总线温度传感器与STM32在HVAC远程测温中的工程实践

前阵子调一套 HVAC 控制器,温度采集部分用的是 PJ85718DM 这颗单总线数字温度传感器,主控是 STM32F722VE 这颗 Cortex-M7 内核的高性能 MCU。项目要求覆盖两种距离:控制器机箱内部回风温度,还有顺着吊顶走到风管另一侧的送风温度,最远的探头离主控超过十米。折腾完硬件和驱动后发现,这套组合用在嵌入式和 HVAC 场景里确实顺手,但有不少坑不会出现在数据手册里,今天把这些实测经验整理出来,给后面做类似本地和远程测温方案的朋友做个参考。


1. 温度采集方案选型:为什么是 PJ85718DM 配 STM32F722VE

1.1 从 HVAC 现场需求反推传感器要求

HVAC 温控系统对温度采集的要求说高不高,说低也不低。回风温度、送风温度、室外补偿温度、冷热水盘管进出水温度,这些点位大多分布在设备间、吊顶、风管内部,和主控板之间隔着几米到几十米的线缆。精度要求通常是 ±0.5°C 以内,采样周期在秒级即可,和控制环路相比算慢变量。

传统做法是 NTC 热敏电阻加一路 AD 采样,成本低,但有一个很麻烦的问题:NTC 的引线电阻和线路噪声会直接叠加到测量结果里。距离一长,要么用三线制、四线制消除线阻,要么加前端调理电路,调试成本立刻上来了。PT100 铂电阻精度好,但调理电路更贵,而且接入 MCU 还得过一路模拟前端。

PJ85718DM 这类单总线数字温度传感器把模拟信号链的麻烦从源头消掉了。传感器内部直接完成温度到数字值的转换,通过单总线协议读出来;每个芯片还有唯一 64 位 ROM 编号,可以在同一条总线上挂多颗,天然适合多点测温。12 位分辨率下最低位对应 0.0625°C,工程上做 ±0.5°C 控制余量充足。HVAC 领域最常用的三种测温方案对比下来,数字单总线方案在中等距离、多点位、低成本这三件事上平衡得最好。

方案精度布线复杂度多点扩展抗干扰单点成本
NTC 热敏电阻中,受线阻影响高,需三线/四线补偿每点一路 AD弱,需滤波低
PT100 铂电阻高高,需调理电路每点一路采集中中高
PJ85718DM 单总线高,数字输出低,一根总线挂多颗靠 ROM 识别中,需注意布线中低

1.2 PJ85718DM 的接口特性与 STM32F722VE 的配合点

PJ85718DM 在协议层面采用单总线,命令集和常见的单总线温度传感器兼容,比如 0xCC 跳过 ROM、0x44 启动温度转换、0xBE 读暂存器。这意味着驱动移植成本很低,以前写过单总线驱动的人基本改改延时函数就能用。

主控选 STM32F722VE 不是大材小用。这颗芯片有一个很多人没注意到的优势:Cortex-M7 在 216MHz 主频下做单总线时序非常轻松。单总线的读时序要求在微秒级别精确采样,主频越高、CPU 抢占越少,时序抖动越小。另一个原因是它接口资源足够丰富,除了温度采集,还同时承担触摸屏、RS485、以太网远程上报等工作,一颗芯片把本地采集和远程通信全包了,板子体积和 BOM 成本反而更低。

这里补充一句个人看法:很多人觉得温度传感器随便找个低端 MCU 也能驱动,何必上 F7。但你要是在同一个项目里跑 RTOS、控制多路 PWM、还要处理 Modbus 协议,PJ85718DM 的软件时序经常会被高优先级任务打断,而 STM32F722VE 的性能余量允许用简单可靠的阻塞式驱动配合短临界区搞定,不需要花大量精力去写复杂的 DMA 或超时补偿逻辑。工程上用“更高性能的主控换更简单的软件设计”,这笔账通常是划算的。

2. 本地与远程测温的硬件布线设计

2.1 主干线拓扑与上拉电阻取值

传感器离主控十几米,第一件事是确定拓扑。我的做法是绝对不搞星形连接。假设总线上挂了 6 个传感器,每个传感器各自拉一根线到主控,汇合点在主控板,那么末端反射、线间串扰、寄生电容都会叠加在一起,波形惨不忍睹。正确做法是像 RS485 一样走总线型:一根双绞线或屏蔽线从主控附近开始,串联穿过所有传感器安装位置,最近和最远的传感器实质上都在同一条物理总线上。

布线距离拉长以后,上拉电阻要重新算。常用 4.7kΩ 上拉在短线没问题,但在十几米的线缆上会出问题,因为线缆分布电容大,RC 时间常数变大,下降沿和上升沿被拉缓。单总线对上升沿要求没那么苛刻,但下降沿过缓会直接影响复位和写时序。

我实测了 15 米带屏蔽双绞线、线间电容约 50pF/m 的情况,4.7kΩ 上拉时上升沿明显变钝,偶发抖动;换成 2.2kΩ 后波形干净不少。如果距离超过 20 米,再降成 1kΩ。但要注意,上拉太小会增大器件功耗,对远端的供电能力要求也更高,所以不要一味求小,够用就行。

// 上拉电阻经验值参考(典型 3.3V 供电,双绞线,室温环境) // < 3m : 4.7kΩ // 3m ~ 15m: 2.2kΩ // > 15m : 1kΩ,且尽量保证每个分支点短接、末端无分枝

2.2 防静电与抗干扰电路

HVAC 现场和实验室环境完全是两回事。风机、压缩机、交流接触器一启停,母线电弧和感性负载会产生强烈的瞬态干扰。这些干扰会通过线缆耦合进单总线,轻则读错数据,重则把 GPIO 口打坏。所以远程传感器线缆在进入主控板的地方,我要求硬件同事必须加保护电路。

最简单的做法是三个器件:一个 3.3V 稳压管或 TVS 管放在总线引脚对地,限制瞬间高压;一个 100Ω 的电阻串在总线入口,配合后级电容形成 RC 滤波,削弱高频噪声;如果传感器由主控板供电,那么供电脚还要加一个 470μF 电解电容或者超级电容容灾。

有人会问,单总线是不是只要传感器侧也加保护就行?理论上两端都加最好,但实际施工中远程探头装在风管里,那地方没有 3.3V 参考地,在传感器侧加稳压管经常因为地电位不一致产生额外泄漏电流。比较务实的方案是只做主控入口保护,远程侧用环氧封装的传感器头,线缆选择带屏蔽层并一端接地。这样做下来,我在现场用示波器看过,总线上的干扰幅度从最初的 2V 多毛刺压到了 0.3V 以下。

注意:如果你做的是末级 HVAC 设备,现场可能和强电共箱体。总线线缆务必走单独的线槽,不要和 220V 动力线绑在一起走。哪怕只是平行走线超过半米,日后的稳定性也会大打折扣。

3. 单总线驱动在 M7 上的落地要点

3.1 先搞定精密时序:用 DWT 做微秒级延时

在 STM32F722VE 上写单总线驱动,很多人顺手就用 HAL_Delay。我劝你趁早放弃这个想法。HAL_Delay 基于 Systick,默认分辨率 1ms,单总线的时序是微秒级的;就算你改 tick 频率,HAL_Delay 调度期间还受中断影响,抖动很大。更好的做法是利用 Cortex-M7 的 DWT_CYCCNT 周期计数器实现微秒延时,它是硬件计数器,几乎没有软件抖动。

// 初始化 DWT 周期计数器,必须在项目初始化阶段调用一次 void dwt_delay_init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; } // 微秒延时函数,基于 CPU 周期数,分辨率和精度远高于 HAL_Delay void dwt_delay_us(uint32_t us) { uint32_t ticks = us * (SystemCoreClock / 1000000U); uint32_t start = DWT->CYCCNT; while ((DWT->CYCCNT - start) < ticks) { // spin } }

SystemCoreClock 在 STM32F722VE 上通常是 216000000,也就是一个周期约 4.63ns。延时时间精确到微秒级别,对于单总线协议完全够用。注意这里用了无符号减法做周期计数溢出保护,只要每次延时不超过约 4294 秒就不会有问题。

有了这个延时函数,复位脉冲、写时隙、读时隙就可以用标准的阻塞方式实现。个人经验是先把 GPIO 配成开漏输出模式,这样天然具备线与能力,也方便上拉电阻外部接。

3.2 读 ID、温度转换、CRC 校验的完整流程

单总线的读写流程不复杂,但细节很重要。以读温度为例,顺序是:复位脉冲、设备存在应答、发送跳过 ROM 命令(0xCC)、发送温度转换命令(0x44)、等待转换完成、再次复位、发送跳过 ROM 或匹配 ROM 命令、发送读暂存器命令(0xBE)、连续读出 9 个字节(温度两个字节 + 告警阈值 + 配置字节 + CRC)。

// 读取单个温度值(整数形式,单位 0.001°C,便于上位机直接显示) int32_t pj85718dm_read_temperature(void) { uint8_t scratchpad[9]; int32_t temp_raw; ow_reset(); ow_write_byte(0xCC); // Skip ROM ow_write_byte(0x44); // Start temperature conversion while (ow_read_bit() == 0); // 等待转换完成,传感器会拉低电平直到转换结束 ow_reset(); ow_write_byte(0xCC); // Skip ROM ow_write_byte(0xBE); // Read Scratchpad for (int i = 0; i < 9; i++) { scratchpad[i] = ow_read_byte(); } if (ow_crc8(scratchpad, 8) != scratchpad[8]) { // CRC 校验失败,返回错误码,交由上层做健康度处理 return OW_CRC_ERROR; } temp_raw = scratchpad[1] << 8 | scratchpad[0]; return (int32_t)temp_raw * 625 / 10; // 12位分辨率,LSB=0.0625°C,转为 0.001°C 单位 }

特别注意等待转换的那句while (ow_read_bit() == 0)。传感器启动转换后会主动把总线拉低,转换完成后释放。读取这个“忙状态”的方式也是读时隙,和读数据位完全一样。这种方式比固定延时 750ms 更高效,尤其在同一总线上挂多颗传感器时,可以做到“谁先转换完谁先被读取”,总线利用率和现场响应速度都更好。

CRC 校验不能省。HVAC 现场的电磁干扰很现实,没有 CRC 你根本分不清读回来的 0.0625°C 是真值还是干扰凑出来的值。CRC8 多项式是 x^8+x^5+x^4+1,实现网上到处都是,我建议把读到的每个字节都送进 CRC,包括温度字节和配置字节,最后和暂存器里的 CRC 字节比对。宁可这次数据丢,也不要拿脏数据去驱动 PID 调节。

3.3 多点 ROM 搜索与传感器分组挂载

远程和本地测温的典型场景是同一总线挂 5 到 10 个探头。只要挂在同一条总线上,就一定会遇到 ROM 搜索问题。搜索算法不复杂,核心流程是逐位探测:每一位读两次,第一次读该位的原码,第二次读反码,通过“线与”特性判断冲突情况。

// ROM 搜索的核心位探测逻辑 uint8_t ow_rom_next_bit(uint8_t bit_choose) { uint8_t bit_a = ow_read_bit(); uint8_t bit_b = ow_read_bit(); if (bit_a && bit_b) { return OW_ROM_ERR; // 两个设备该位都为 1,读到的只能是 1,属于异常 } if (!bit_a && !bit_b) { // 冲突位:一条总线上既有 0 也有 1 // 根据搜索方向选择 0 或 1,bit_choose 来自搜索栈 return bit_choose; // 返回 0 或 1 } // 一个为 1、一个为 0,说明所有设备该位相同 return bit_a ? 1 : 0; }

实际工程中我不建议自己在项目里临时写这个算法,除非你有一整块时间用来调试。稳妥的办法是先用单点读取方式验证所有传感器都能正常响应,然后在固件里做一次上电到断电的动态扫描:从 64 位全零开始,每一位按上述逻辑确定,收集出所有 ROM 编号,存入列表;之后每次读取温度时直接用 MATCH ROM(0x55)命令点名读取。这样容错率最高,也方便本地和远程分组的固件配置。

分组建议用两路独立的 GPIO 总线,而不是把所有传感器塞进同一条总线。本地组走机箱内部的短线,远程组走长线。两路物理隔离最大的好处是:远程线缆被雷击或短路时,不会拖着本地组一起失效;其次,本地组和远程组的供电、上拉电阻、保护电路可以分别配置,远程组用低阻上拉、本地组用标准上拉。

很多人贪图省一根线,把所有探头全挂一条总线上。我见过每个传感器带不同长度支线的案例,最后总线反射严重,干扰一大就整组掉线。分两路以后,即使远程组故障,本地组依然能维持最低限度的回风温度监测,这个冗余设计在 HVAC 系统里价值很高。

4. HVAC 采样调度:非阻塞状态机与数据健康度

4.1 温度环路的采样周期设计

HVAC 温度控制是慢过程,温度采集周期通常 1 秒到 5 秒完全够用。但单总线的转换时间却有讲究。PJ85718DM 在 12 位分辨率下典型转换时间约为 750ms,如果总线上有 8 个传感器,最傻的写法是循环 8 次“转换–等待–读取”,每个周期要 6 秒以上,控制环路根本没法用。

正确的做法是广播启动转换。给整条总线发送一次跳过 ROM 命令加转换命令,所有传感器会同时开始转换;等待 750ms 之后,再用 MATCH ROM 逐个点名读结果。这样一来,总耗时变成了一次转换等待 750ms 加上 8 次短读,总周期可以压缩到 1 秒左右。

// 广播转换 + 点名读取,8 个传感器总耗时约 750ms + 8 * 20ms void temp_bus_start_conversion(void) { ow_reset(); ow_write_byte(0xCC); // Skip ROM,广播 ow_write_byte(0x44); // 所有传感器同时启动转换 } int32_t temp_bus_read_point(uint8_t *rom_id) { int32_t temp; ow_reset(); ow_write_byte(0x55); // Match ROM for (int i = 0; i < 8; i++) { ow_write_byte(rom_id[i]); } ow_write_byte(0xBE); // 读暂存器 // ... 读取 9 字节,和单点读取一致 return temp; }

这个广播转发的思路,是所有单总线多点测温项目的关键优化点。同样的总线上挂多少传感器,成本只增加一点点读取时间,而不会把等待时间成倍累加。实测下来,9 个探头加一个广播转换,单轮全量采集在 900ms 内完成,更新周期 1 秒绰绰有余。

4.2 状态机与温控任务降级

既然用了 RTOS 或前后台大循环,温度采集就不能是阻塞的 900ms 死等。我习惯把整个采集流程切成状态机,每个系统 tick(比如 10ms)推进一次,采集任务和其他任务共享 CPU,互不阻塞。

typedef enum { TEMP_STATE_IDLE, TEMP_STATE_START_CONV, TEMP_STATE_WAIT_CONV, TEMP_STATE_READ_POINTS, TEMP_STATE_PUBLISH } temp_state_t; temp_state_t temp_state = TEMP_STATE_IDLE; void temp_task_tick(void) { switch (temp_state) { case TEMP_STATE_IDLE: if (temp_cycle_trigger()) { temp_bus_start_conversion(); temp_state = TEMP_STATE_WAIT_CONV; temp_conv_start_ms = now_ms(); } break; case TEMP_STATE_WAIT_CONV: if (now_ms() - temp_conv_start_ms >= 800) { temp_state = TEMP_STATE_READ_POINTS; } break; case TEMP_STATE_READ_POINTS: // 逐个读取,读取失败标记该通道无效 temp_state = TEMP_STATE_PUBLISH; break; case TEMP_STATE_PUBLISH: // 数据平滑、阈值判断、送入调控队列 temp_state = TEMP_STATE_IDLE; break; } }

除了调度,我还为每个温度点维护了健康度字段:当前值、上一有效值、连续失败次数、数据有效标志。连续三次 CRC 失败,我就认为当前温度点失效,对应通道标记为“无效”,温控逻辑改用上一有效值或同组均值;连续失效超过 60 秒,触发告警,但不允许把无限循环取出的僵尸数据送进 PID 环路。这个“数据健康度”设计在 HVAC 里非常重要,因为温度探头失效时,错误的超低温读数会直接让系统进入满负荷加热或制冷状态,现场损失很大。

温控的最终目标是舒适和节能,宁可保持旧值维持设备稳定运行,也不要因为一个坏数据让系统剧烈振荡。状态机里最容易被忽略的是:切换通道时,总线上的时序间隙要足够长,避免上一通道的读过程影响下一通道复位。我一般在每个通道读取结束后插一个 200μs 的空闲时间,这个问题就不会出现。

5. 远程上报:把温度数据送到上位机的那段路

5.1 系统级远程:STM32F722VE 作为采集网关的接入方式

如果你的“远程监测”不仅指远程风管探头,还指把数据传到中控室,那 STM32F722VE 天生适合当采集网关。它自带以太网 MAC,配合 PHY 芯片能直接上 Modbus TCP;也可以用现成的 RS485 加 Modbus RTU,成本更低。

我的建议是,在固件里把温度数据整理成一张统一的寄存器表,不论走串口还是走以太网,上位机看到的数据结构完全一致。举个栗子:寄存器地址 0x100 到 0x10F,16 个通道的温度值,每个温度用 16 位有符号数表示,单位 0.01°C;寄存器 0x120 放每个通道的有效标志位;0x121 放全局告警状态码。上位机只管轮询这张表,不需要关心下位机总线上到底挂了几颗传感器、哪一颗失效了。

// 简化版温度寄存器表结构 typedef struct { int16_t temp_100x[16]; uint16_t valid_flags; uint16_t alarm_flags; } temp_register_map_t;

实际项目中,我用 STM32F722VE 的以太网做主链路、串口做调试备份链路。主链路断了,上位机几分钟后会发现通信超时,但本地控制环路不受影响,系统继续按最后有效的温度数据运行。这种降级设计在楼宇自控调试阶段非常救命,因为你永远不知道现场网络交换机会在哪一刻断电重启。

5.2 长线实测和故障复盘

硬件和驱动做完了,我在实际现场跑过的测试数据列出大概情况:本地组总线长度约 1.5 米,上拉 4.7kΩ,测试 24 小时,读取成功率 100%。远程组走 15 米屏蔽双绞线,上拉换成 2.2kΩ 后,读取成功率稳定在 99.9% 以上;偶发的失败集中在压缩机启动瞬间,加了 TVS 和 RC 滤波后失败率进一步下降。

还有一次很奇怪的故障:远程组温度读数稳定,但每隔约 10 分钟有一次超过 ±1°C 的跳变。排查了很久才发现是同一线槽里有一根 220V 输出线,用的继电器触点在零点切换时产生干扰毛刺。把远程总线挪开 30cm 或者增加屏蔽层接地后,问题彻底消失。这个案例说明,现场布线的物理隔离比固件里的任何滤波算法都有效。

6. 实测数据与几个必须记住的坑

场景线长上拉电阻实测结果备注
本地机箱内1.5m4.7kΩ24h 成功率 100%无强干扰
远程风管15m2.2kΩ成功率 99.9%+需 TVS 保护
远程风管 + 强电并线15m2.2kΩ偶发数据跳变移开线槽或屏蔽接地
远程风管(不加保护)15m4.7kΩ频繁 CRC 失败干扰无法容忍

坑一:上拉电阻千万别看着数据手册照抄。数据手册给的是典型应用电路的推荐值,不会考虑你的线缆长度和现场干扰。一定要根据实际距离现场测一遍,波形有振荡就降阻,有毛刺就加滤波。

坑二:单总线器件供电方式要提前定死。寄生供电模式在短距离、传感器数量少时勉强能用,但到远程长线、多传感器场景就是给自己挖坑。我建议统一采用外部供电:3.3V 电源线单独拉一根给远端传感器,数据线只是纯信号线,电源和地单独走。这样阻容负载更易控,干扰也小得多。

坑三:对 STM32F722VE 这类 M7 内核,延时函数不要在运行后再修改主频。我踩过的坑是在低功耗模式下切换了时钟频率,导致 DWT 延时按新的 CPU 周期计算,单总线时序全部错掉,传感器一个都读不到。要么降低功耗时保留总线时钟不变,要么改变主频后重新初始化 DWT。

坑四:CRC 一定要做,而且一定要把传感器上报的 CRC 字节和自己算出来的对比,不要只看温度字节是否在合理范围。HVAC 现场太嘈杂了,干扰可以同时把多个字节污染成“看起来合理”的值,只有 CRC 能发现这种错误。

坑五:不要让温控任务在传感器读取时被打断。如果用了 RTOS,给温度采集任务挂一个信号量,在单总线读取期间关闭调度或使用临界区。STM32F722VE 有一个很实用的特性,可以用 MPU 把温度采集相关的 RAM 区设为非缓存区域,避免 DMA 和 CPU 在缓存一致性上出问题;如果没有用 DMA,那问题不大,但开了 DCache 的 M7 用户要留心。

最后一个实际体会:这套 PJ85718DM 加 STM32F722VE 的方案,真正关键的不是驱动代码本身,而是把“采集—校验—调度—上报”这条链路当成完整的数据通道来设计。单点读温度谁都会写,但要让它在带压缩机、带变频器的 HVAC 现场稳定地跑上几个月,布线、上拉、保护、健康度这些工程细节一个都不能省。把这几个坑避开,这方案在嵌入式设备里做本地和远程温度监测,是我近期用过最省心的一套组合。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进