做 HVAC 控制器和嵌入式设备的朋友应该都有体会:温度监测看着简单,实际做起来全是细节。控制柜内一个本地温度,风道、回风口各一个远程温度,传感器选型、走线方式、通信时序、异常处理,哪个环节偷懒,最后调试阶段都要加倍还回来。我最近用 PJ85718DM 单总线数字温度传感器配合 STM32F101ZG,重新做了一套本地与远程温度监测方案,同时覆盖嵌入式设备本身的板温监控和 HVAC 空调箱的典型测点需求,实测下来稳定性比之前依赖模拟 NTC 的方案好不少。这里把选型思路、硬件电路、单总线驱动、采样状态机以及调试踩坑过程完整写出来,给正在做同类项目的朋友一个可以照着抄的参考。
1. 方案拆解:本地与远程温度监测到底在解决什么
1.1 先分清“本地”和“远程”各自的含义
很多人一看到“本地与远程温度”,下意识认为是有线网络或者上位机那种远程监控。但在嵌入式和 HVAC 控制器这个语境下,这两个词的含义要朴素得多。
本地温度,指的是传感器直接放在控制器主板上、或者紧贴设备外壳,用来反映设备自身的热状态。典型测点包括功率管附近、电源模块散热片、控制板中央区域。这个温度主要用来做板卡过热保护、风扇转速补偿、电子器件降额计算。
远程温度,指的是传感器通过线缆引到设备之外或设备的另一处物理位置,用来反映被控环境或远端设备的热状态。HVAC 场景下的典型测点有回风口温度、送风管温度、盘管表面温度、室外换热器温度;嵌入式场景下则可能是机箱角落、电池仓、电机绕组附近这类不方便把主板放过去的点。
两种温度的服务对象完全不同。本地温度服务于设备“自身安全”,远程温度服务于“控制逻辑”。如果把两者混在一起,后面做控制策略的时候一定会出问题。比如空调压缩机频繁启停,往往不是因为控制算法写得不好,而是传感器安装位置选错了,导致控制器读到的温度无法代表真实环境。
1.2 实际场景一:嵌入式设备内部的温度监控
我手里有个小型嵌入式设备项目,外壳密闭、没有风扇,里面有一块功率驱动板和一块主控板。最初只放了一个 NTC 贴在功率管上,通过 ADC 采样。问题很快暴露了:NTC 的 B 值离散性导致每块板子校准值都不一样,长线引到机箱角落时误差更大,而且读取异常时根本没有办法区分是传感器坏了还是真的温度过高。
换成 PJ85718DM 之后,功率板上放一个传感器作为本地测点,机箱角落电池仓放一个作为远程测点,一根总线串起来。固件里加上 CRC 校验和断线检测,至少能明确区分“通信故障”和“真实高温”,再配合简单的滞回逻辑做风扇启停,稳定性和可维护性都上了一个台阶。
1.3 实际场景二:HVAC 空调箱的温度控制
HVAC 场景对温度监测的要求更苛刻,因为回风温度、送风温度这些数值直接参与压缩机、风机、电动阀的控制。以前用模拟传感器时,现场强电线路一启动,传感器长线上感应出来的噪声就能让 ADC 读数上下跳好几度,控制逻辑跟着乱跳。
用数字单总线方案后,温度值在传感器内部完成模数转换,总线上传输的是数字信号,抗干扰能力天然比模拟电压传输强。一个控制器可以管理多个远端测点,每个传感器都有独立的 ROM 地址,固件里通过匹配 ROM 准确读取每一个测点。这套结构对 HVAC 这种“一个控制器带多个温度测点”的形态非常合适。
1.4 为什么是 PJ85718DM + STM32F101ZG
选 PJ85718DM,核心原因是它属于单总线数字温度传感器,接口简单到极致,只有一根数据线。与常见单总线传感器兼容,分辨率 12 位,每个芯片拥有唯一的 64 位 ROM 地址,可以在同一条总线上挂多个传感器。供电范围宽,3.0V 到 5.5V 都能工作,能直接配合 MCU 的 3.3V 电平,不需要额外电平转换。
选 STM32F101ZG,则完全是站在工程角度考虑。这是 F101 系列里的大容量型号,144 引脚封装,主频 36MHz,Flash 和 SRAM 资源对温度采集、日志存储和控制逻辑来说绰绰有余。单总线时序完全可以用 GPIO 模拟,不需要占用专门的硬件外设。相比性能更强的同系列其他型号,F101ZE/ZG 在不需要 USB、不需要 72MHz 主频的监控场景里成本更合适。如果项目以后要扩展更多通道、更复杂的 PID 运算、甚至存历史温度曲线,这颗芯片的资源也不会成为瓶颈。
这里也顺带说一句:温度和控制的工程里,芯片性能从来不是最难的部分,最难的是信号链路的可靠性和异常情况下的行为定义。这套方案的选型逻辑,就是把“简单可靠的传感器”和“资源够用且可控的 MCU”组合在一起,让开发者把精力集中在采集策略和控制逻辑上。
2. 硬件电路:把 PJ85718DM 的接口和长线布线做扎实
2.1 引脚定义与供电方式选择
PJ85718DM 这类单总线传感器通常只有三个引脚:GND、DQ(数据线)、VDD(电源)。手册上常见的接法是 VDD 接正经电源、DQ 通过上拉电阻接 MCU 的 GPIO,这是外部供电模式。还有一种寄生供电模式,VDD 直接接地,传感器靠 DQ 线上的寄生电容和整流电路取电,这样只需要两根线。
寄生供电看起来很省线,但在实际工程里我强烈不建议在远程测点使用。原因有两个:一是传感器做温度转换时电流峰值不小,长线供电会在导线上产生明显压降,远端传感器可能供电不足,转换结果直接不可靠;二是如果远程线缆被现场检修人员不小心短接,寄生供电模式下的故障表现更隐蔽,排查起来很痛苦。
所以在这个项目里,本地和远程传感器全部采用外部供电。四线制:VDD、GND、DQ,再加上一根备用线,方便以后扩展或替换。如果现场条件限制只能用两芯线,才去考虑数据线与电源线共用,但必须把供电线加粗、降低压降。
2.2 上拉电阻怎么选:不是无脑 4.7k
单总线协议要求总线上必须有上拉电阻,空闲状态时 DQ 保持高电平。很多人照着数据手册直接放 4.7kΩ,短距离没问题,远程测点一走线就翻车。
原因在于线缆有分布电容。普通双绞线的分布电容大概在 50~100pF/m 级别,30 米线缆大约带来 2~3nF 的电容。数据线上拉电阻和线缆电容构成 RC 低通网络,电阻值越大,信号上升沿越缓。单总线的读时隙要求主机在拉低总线后释放,并在 15μs 时间内采样,如果上升沿太慢,总线电平还没恢复到高电平就被采样,读回的数据会不稳定。
按经验值做个估算:4.7kΩ 上拉电阻配 2.5nF 负载电容,时间常数约 11.75μs,在 15μs 采样窗口里非常紧张,稍微有点噪声就会误判。换成 1kΩ 上拉,时间常数降到 2.5μs,余量就充足了。所以远程传感器线缆超过 10 米时,把上拉电阻从 4.7kΩ 换到 2.2kΩ 或 1kΩ。
当然也不能无限减小,上拉太小意味着器件拉低总线时灌电流变大,如果传感器输出级的驱动能力一般,反而可能拉不下去。实际项目里我一般这样配置:本地传感器距离在 50cm 内用 4.7kΩ;远程 10 米以内用 2.2kΩ;超过 10 米用 1kΩ。
2.3 线缆选择与保护电路
远程传感器线缆不要用普通跳线,至少要用双绞线。双绞结构能有效抵消一部分共模干扰,HVAC 现场动力线多,传感器线缆和强电电缆千万别走同一个线槽,即使走同一个方向也至少保持 30cm 以上间距。
屏蔽双绞线更好,屏蔽层单端接地,不要两端都接,否则地环路会产生更大干扰。线缆进入控制器板端时,加一个 TVS 管到地,同时再串一个 100Ω 左右的电阻限制浪涌电流,成本很低但效果明显。因为远程线缆在施工现场很容易被工人碰触、缠绕,可能引入静电放电,甚至直接碰到高压端子,保护电路不能省。
接线端子选可插拔的,方便现场更换传感器。每个远程测点做好线号标识,比如 T1-1 代表 1 号测点正电源、T1-2 代表信号、T1-3 代表地,没有标识的线缆在两三年后的维护现场就是灾难。
2.4 本地与远程传感器的接线参考
| 测点类型 | 传感器位置 | 与主控距离 | 上拉电阻 | 线缆形式 |
|---|---|---|---|---|
| 本地测点 | 主板功率管附近 | < 30cm | 4.7kΩ | 板内走线 |
| 中距离远程测点 | 设备外壳/机箱角落 | 1~10m | 2.2kΩ | 双绞线 |
| 长距离远程测点 | HVAC 风道/室外机附近 | 10~30m | 1kΩ | 屏蔽双绞线 |
需要注意的是,DQ 线上拉电源必须和 MCU 的 IO 电平一致。开发板用 3.3V 供电,传感器也支持 3.3V 工作,统一上拉到 3.3V 即可。如果 MCU 是 5V IO,则传感器电源、上拉电源都要接 5V,并且确认 MCU 的 GPIO 容忍 5V。
3. 单总线驱动:时序、ROM 匹配和 CRC 一个都不能少
3.1 单总线时序的三个关键环节
单总线通信的底层逻辑其实不复杂,所有操作都由主机拉低总线的时间长短来区分。核心是三个环节:复位、写时隙、读时隙。
复位脉冲是通信的起始信号,主机把总线拉低 480μs 以上,然后释放。传感器检测到这个长低电平后,会在 60~240μs 内拉低总线约 60μs,作为存在脉冲。主机只要在释放总线后 480μs 内能读到一次低电平,就说明总线上有设备在线。这是最简单的“设备是否存在”判断方式。
写时隙用来发送 1 和 0:主机拉低总线 1~15μs 后释放,传感器在 15~60μs 窗口内采样总线状态,如果总线保持高电平,代表写入 1;如果主机持续把总线拉低 60~120μs,代表写入 0。
读时隙正好反过来,主机发起一个短暂的低电平脉冲,然后在 15μs 内释放并采样总线,传感器如果拉低,读到的就是 0,不拉低就是 1。
时序窗口非常紧凑,最怕的就是延时不准。用标准库里的普通延时函数,在开中断情况下很容易被定时器中断打断,导致时序超时。稳妥的做法是关中断后使用 SysTick 或者 DWT 计数器做微秒级延时,读完一个完整字节再重新开中断。实测下来,时序抖动对通信成功率的影响远比想象中大,尤其是长线缆情况下,波形边沿本来就缓,时序再不准,误码率会成倍上升。
3.2 最简单可靠的单点读取流程
如果总线上只有本地一个传感器,或者只需要读取某个已知地址的远程传感器,最省事的流程是跳过 ROM。步骤如下:主机复位,发跳过 ROM 命令 0xCC,发启动温度转换命令 0x44,然后等待转换完成。12 位分辨率下转换时间典型值约 750ms。等待结束后再次复位,发跳过 ROM,发读暂存器命令 0xBE,连续读取 9 个字节的暂存器数据。
暂存器前两个字节是温度值,低字节在前。第 9 个字节是 CRC 校验值,由前 8 个字节计算得到。不要让程序无条件相信收到的数据,必须做 CRC 校验,不通过就丢弃本次采集结果,标记该通道为通信异常。
下面是核心读取函数的关键代码,基于 GPIO 模拟实现,不依赖具体库:
// 平台相关的两个基础操作 void bus_low(void); // 将 DQ 引脚配置为输出并拉低 void bus_release(void); // 将 DQ 引脚配置为输入并释放(靠上拉恢复高电平) uint8_t bus_read(void); // 读取 DQ 引脚当前电平 void delay_us(uint32_t us); uint8_t ow_reset(void) { uint8_t present; bus_low(); delay_us(480); // 主机复位脉冲 bus_release(); delay_us(70); // 等待传感器响应窗口 present = bus_read(); // 读到低电平表示有设备在线 delay_us(410); // 等待存在脉冲结束 bus_release(); return present; // 返回 0 表示有设备,1 表示无设备 } void ow_write_byte(uint8_t data) { for (uint8_t i = 0; i < 8; i++) { bus_low(); delay_us(3); // 写时隙起始 if (data & 0x01) { bus_release(); // 写 1:释放总线 } // 写 0:继续拉低 delay_us(60); bus_release(); data >>= 1; } } uint8_t ow_read_byte(void) { uint8_t data = 0; for (uint8_t i = 0; i < 8; i++) { bus_low(); delay_us(2); // 读时隙起始 bus_release(); delay_us(5); data >>= 1; if (bus_read()) { data |= 0x80; } delay_us(50); } return data; }这段代码里,ow_reset 的返回值设计成 0 表示有设备在线,这样函数调用处的逻辑更直观:如果 ow_reset() 返回非零,直接进入错误处理分支,不需要再对返回值做取反。
3.3 多传感器轮询:远程测点必须用匹配 ROM
一根总线上挂了多个传感器之后,跳过 ROM 就不行了。读取某个特定远程测点之前,必须先发送匹配 ROM 命令 0x55,然后紧跟该传感器的 64 位 ROM 地址,总线上只有地址匹配的传感器才会响应后续命令。
获取 ROM 地址有两种方法。第一种是出厂后单独读取:每个传感器单独接在总线上,发送读 ROM 命令 0x33,读取其 8 字节地址,记录下来并贴标签。第二种是写一个 ROM 搜索程序,通过算法在总线上自动枚举所有设备,原理是对每个地址位轮流写 0 和 1,观察总线响应来逐位确定地址,适合运行中动态识别新设备。
工程上优先推荐第一种。生产线上的传感器来料,逐个读取地址、写入配置表,比在固件里放一套搜索算法省事得多,而且标签贴好之后,现场维护人员不用带着调试器去重新识别地址。配置表存到 STM32F101ZG 的 Flash 里,掉电不丢失。
匹配 ROM 的发送流程如下:
void ow_send_rom_code(uint8_t *rom) { ow_write_byte(0x55); // 匹配 ROM 命令 for (uint8_t i = 0; i < 8; i++) { ow_write_byte(rom[i]); } }每次读取远程传感器时,先 ow_reset(),然后调用 ow_send_rom_code(),再执行 0x44 启动转换或 0xBE 读暂存器。本地传感器如果地址已知,也用同样方式处理,这样整个固件只有一条逻辑路径,不需要区分本地和远程,代码更统一。
3.4 温度解析和 CRC 校验实现
温度寄存器是 16 位有符号补码,12 位分辨率下每个 LSB 代表 0.0625℃。把两个字节组合成 int16_t 后,乘以 0.0625 就是实际温度值。实际工程中为了避开浮点运算,可以预先放大 100 倍存储,即温度值乘以 6.25,用整数运算保存到百倍单位,显示时再除以 100。
CRC 校验可以直接用查表法,也可以逐位计算。单总线 CRC 的多项式是 X^8+X^5+X^4+1,对应多项式系数 0x8C。逐位实现如下:
uint8_t ow_crc(uint8_t *data, uint8_t len) { uint8_t crc = 0; for (uint8_t i = 0; i < len; i++) { crc ^= data[i]; for (uint8_t bit = 0; bit < 8; bit++) { if (crc & 0x01) { crc = (crc >> 1) ^ 0x8C; } else { crc >>= 1; } } } return crc; }读回 9 个字节后,对前 8 个字节做 CRC 计算,结果必须等于第 9 个字节,才算一次成功读取。长线缆场景下,偶尔一次 CRC 失败很正常,三次连续失败才需要标记通道异常。
4. 采集调度与控制逻辑:嵌入式与 HVAC 场景分别怎么落地
4.1 用状态机而不是裸阻塞循环来管理采样
温度采集看起来就是循环读取,但如果把 750ms 转换等待、传感器轮询、控制逻辑全部塞进一个循环,程序结构很快就会失控。尤其是后续要加人机交互、通信协议、故障保护时,裸阻塞循环会拖累整个系统的实时性。
推荐用状态机的方式管理单总线流程。每个传感器通道对应一组状态,核心状态包括:空闲、启动转换、等待转换、读取数据、校验更新、异常计数。主循环里每次进入调度函数,根据当前状态执行一步操作,剩余时间可以处理其他任务,或者直接进入低功耗。
一个典型的调度节拍是 100ms。状态机的转换等待阶段,把 750ms 拆成多个 100ms 节拍,在最后一次节拍后才进入读取状态。这样比阻塞等待 750ms 好得多,主循环有充足时间处理按键、通信、显示刷新等任务。
4.2 嵌入式场景:过热保护与风扇调速
嵌入式设备的板温控制,重点在保护逻辑。本地测点贴在功率管散热器上,当温度超过 75℃ 时启动风扇,降到 65℃ 时关闭风扇,中间留 10℃ 的滞回区间,避免风扇在临界温度附近频繁启停。如果温度超过 85℃,直接进入降额模式,限制功率输出;超过 95℃,触发关机保护。
远程测点放在机箱角落,主要用来监测整个机箱的散热效果。如果机箱内部温度偏高但功率管温度正常,说明风道设计或风扇转速有问题,需要提示维护。嵌入式的温度变化相对较快,采样周期可以短一些,本地测点 2 秒一次、远程测点 5 秒一次足够。
对于通风散热系统,简单滞回控制就能解决大部分问题,不需要一上来就上 PID。我见过不少项目把风扇控制写得很复杂,结果现场调试周期被拉得很长,实际上大多数发热场景都是单峰热源,滞回加缓启动就够用。
4.3 HVAC 场景:滞回控制和运行趋势判断
HVAC 场景里,远程传感器测的是风道和回风温度,这些温度变化滞后大、惯性大。用温度直接做压缩机启停,必须在控制逻辑里加入滞回。以一个制冷应用为例:目标温度 25℃,设置 2℃ 滞回,回风温度升到 26℃ 时启动压缩机,降到 24℃ 时才停止压缩机。这样压缩机一个启停周期至少跨越 2℃,能有效避免短循环。
除了滞回,还可以用温度变化趋势做辅助判断。比如检测到送风温度在短时间内持续上升且速率超过某个阈值,说明制冷效果变差,可能是过滤器堵塞或冷媒不足,提前给出预警。远程温度采样周期可以放到 5~10 秒,因为气流温度变化本身不够快,采样太快反而会把高频噪声带进控制逻辑。
4.4 低功耗策略:睡 750ms 也是省电
如果你的设备需要电池供电,不要忽略等待温度转换的 750ms。在启动转换命令发出后,让 MCU 进入 Sleep 模式,用定时器在 800ms 后唤醒,然后读取温度数据。这样每次采集周期的功耗降低非常明显。
唤醒后要注意一个细节:Sleep 模式通常不关闭主时钟,启动时间短,单总线时序精度受影响较小;但如果进入了更深度的停止模式,主时钟会关闭,唤醒后必须等待时钟稳定再开始单总线通信,否则第一个复位脉冲的时间就错了。我建议在低功耗项目中,采集温度时只进入 Sleep 模式,不要进入 Stop 模式,保证时序可控。
另外,低功耗项目里 Flash 存储地址配置表时,要考虑写均衡和掉电保护。STM32F101ZG 的 Flash 擦写次数有限,不要在运行中频繁更新配置,更新时先写备份区、再做切换,避免写一半掉电导致配置表损坏。
5. 实测结果与排障:那些文档里查不到的坑
5.1 一组典型实测数据
整套系统搭建完成后,我在模拟 HVAC 风道上做了连续运行测试。本地测点放在控制器主板上,两个远程测点分别放在回风口和送风管内,线缆长度分别约 8 米和 25 米。环境温度稳定在 25℃ 左右,测试时长 6 小时,记录结果如下:
| 测点 | 线缆长度 | 上拉电阻 | 平均温度 | 波动范围 | 通信成功率 |
|---|---|---|---|---|---|
| 主板本地测点 | < 0.5m | 4.7kΩ | 32.4℃ | ±0.6℃ | 100% |
| 回风口远程测点 | 8m | 2.2kΩ | 24.8℃ | ±0.3℃ | 99.98% |
| 送风管远程测点 | 25m | 1kΩ | 26.1℃ | ±0.4℃ | 99.95% |
通信失败主要集中在送风管长线测点,原因是现场出现了一次较大幅度的电磁干扰,之后通过调整屏蔽层接地位置和增加 PTC 保护后,通信成功率进一步提升。温度波动范围里包含了空调压缩机周期性启停带来的真实温度波动,不是传感器噪声。
5.2 典型故障现象与定位思路
远程测点最常见的故障表现是读回固定值 85℃。这个值不是因为传感器真的到了 85℃,而是传感器复位后温度寄存器的默认上电值。如果程序在启动转换命令之后没有等待 750ms 就去读,就会把这个默认值当成真实温度。排查思路很简单:看读值是否长时间不变,并且恰好是 85.00℃,重点检查转换等待时间。
总线读回全 1 的情况基本可以断定是断线或者上拉电阻没接。传感器根本没有应答,主机读到的一直是高电平。现场排查优先检查接线端子是否松动、线路是否被压断,其次检查上拉电阻焊接是否虚焊。
另一种情况是读写正常但偶尔跳变。先看 CRC 是否频繁失败,如果是,说明数据线上有干扰或者上升沿过缓。解决办法是换上拉电阻、改用屏蔽双绞线、调整布线路径。如果 CRC 一直通过但温度值偶尔跳几度,多半是供电问题,重点检查远程传感器的 VDD 引脚电压在转换瞬间是否被拉低。
5.3 几个特别值得注意的避坑点
传感器地址一定要在来料入库时读取并贴标签。不要指望现场人员用调试软件去识别地址,更不要让现场人员去猜哪根线对应哪个测点。我见过最惨的现场,整个系统的远程传感器地址全部没有记录,维护时只能一个个接上去试,耗时巨大。
线缆颜色要有全局规范。电源线统一红色、地线统一黑色、信号线统一黄色,不能每批采购换一种颜色。否则后期加测点的时候,线缆颜色对不上,查线几乎要崩溃。现场布线时,传感器探头不要贴在金属风道壁面上,要保证探头悬空在气流中,并用支架固定住,避免长期振动导致探头磨损。
风道环境湿度较大时,传感器探头要做好防水凝露处理。温度传感器本身是密封的,但引出线和探头交界处容易凝露,建议用热缩管和密封胶把交界处封住。否则凝露进入引脚之间,电阻值变化会导致读数漂移,而且这种漂移最难排查,因为故障时有时无。
关于突发故障的处理,固件里必须保留“最后有效值”和“故障标志”两个概念。控制逻辑看到故障标志后,应该进入安全策略,比如停止压缩机、保持风扇运行,而不是拿一个没有更新的旧温度值继续做控制。如果控制逻辑在传感器异常时还照常工作,系统可能在一个错误温度下运行很长时间,等到发现时设备可能已经损坏。
整套方案做下来,我最大的感受是:温度监测的难点从来不在读寄存器那几行代码,而在选型、布线和异常处理这些容易被忽略的地方。PJ85718DM 和 STM32F101ZG 这套组合,硬件成本低、软件可移植性好,项目落地和后期维护都算省心。如果让我再做一版,我会把传感器地址、上限阈值、滞回区间这些参数做成运行时可配置,存到 Flash 里,这样不同现场不用重新编译固件,维护人员直接通过调试界面就能改参数,设备适配性会好很多。