上一个项目里接触到一款空调箱控制板,需要同时监测送风温度、回风温度、盘管进出口水温,还把机房里的环境温度一并传到值班室。最初的设计是每个测点一路模拟量信号线,一套机组下来七八根线,接线端子占了控制板将近三分之一的空间,维护的时候还得拿着标签逐条核对,现场施工的师傅看一次吐槽一次。后来我把测温点全部换成 PJ85718DM 这颗单总线数字温度传感器,让它们挂在同一根总线上,用一片 STM32F410RB 集中读取,本地和远程的温度点用一条总线全部拉到 MCU,再通过 RS485 上报给上位机。这套方案在样机里跑了两周,温度数据稳定,接线量直接砍掉一大半。这篇文章就从头到尾拆解这套方案:硬件怎么接、单总线时序怎么调、固件怎么把多颗传感器管起来、实测会遇到哪些坑,给同样在做嵌入式温控和 HVAC 系统的人一个可以直接抄作业的参考。
1. 温控系统里“本地+远程”到底要解决什么问题
1.1 HVAC 场景的温度点位与接线痛点
HVAC 设备里的温度测点往往分布在两个位置:一类是控制板附近,比如机箱内部温度、回风温度,这些点离 MCU 近,叫本地温度;另一类在设备远端,比如风管末端、盘管进出口、冷却塔管路,导线可能要拉个十几米甚至几十米,这类就是远程温度。传统做法是每个测点单独接一路模拟传感器,NTC 热敏电阻或者 PT100,每路都要占用一个 ADC 通道,还要做分压电阻、滤波电容、校准表。测点一多,控制板的 PCB 面积和端子数量立刻紧张,而且模拟信号长线传输很容易被变频器、接触器的干扰带偏,测出来的温度跳来跳去。
单总线数字传感器改变的是整个布线逻辑:它把温度直接转成数字量,传感器里面自己做模数转换,MCU 只需要一根 GPIO 引脚挂一条总线,所有传感器并联在这根线上,靠芯片里唯一的 64 位序列号区分彼此。本地装两颗、远程装三颗,都在这条总线上,主控芯片轮流寻址读取就行。这样既减少了接线数量,又摆脱了对模拟采样的依赖。
1.2 PJ85718DM 的关键特性
PJ85718DM 的工作方式和很多人熟悉的 DS18B20 基本一致,属于单总线接口的数字温度传感器,引脚只有三个:VDD、GND 和 DQ。DQ 既是数据线也是时钟线,协议要完全靠主机产生时序来控制。芯片内部有 64 位唯一序列号,出厂时烧录,不会重复,这是多点测温的基础。测温范围覆盖 -55℃ 到 +125℃,对 HVAC 的常规应用完全够用,分辨率可以配置成 9 到 12 位,12 位模式下每个 LSB 是 0.0625℃,典型精度在 ±0.5℃ 左右,属于免校准的级别,贴上去就能读数。它支持外部供电,也支持寄生供电,即 DQ 在特定时段既是数据线又给芯片充能,但寄生供电在实际工程里限制比较多,我后面会专门说为什么建议外部供电。
还有一个比较实用的点是报警寄存器:可以给每颗传感器设置 TH 和 TL 上下限,温度越限时传感器会记录一个报警标志,主机读取告警状态时不用把每个字节都读出来,适合做主控端的快速巡检逻辑。
1.3 STM32F410RB 为什么适合这类控制板
STM32F410RB 是 Cortex-M4F 内核的 MCU,主频 100MHz,Flash 128KB,SRAM 64KB,单总线测温这种 bit-banging 的活对它来说很轻松。选它不是因为性能堆料,而是外设组合正好贴合 HVAC 控制板的需求:它有多个 USART,可以一个接 RS485 做上位机通信,一个接调试串口;有丰富的定时器,用来做 PID 控制周期和通信超时管理;还有 12 位 ADC 和比较器,虽然温度走了数字总线,但如果还需要采集湿度、压力、电流这些模拟量,也不用再加 MCU。另外它的低功耗模式做得不错,待机电流很低,后面如果想做电池供电的采集节点,同一颗芯片还能继续用。
F410RB 的另一个好处是内部有真随机数发生器 TRNG,这在做设备 ID 登记、通信加密时能派上用场。对一套要跑很多年的 HVAC 控制器来说,预留这种能力比换芯片划算。
2. 硬件连接的关键:单总线不是随手一接就行
2.1 最小系统连接与上拉电阻取值
单总线的连接看起来特别简单:所有传感器的 DQ 引脚并联到一个 GPIO,中间接一个上拉电阻到 3.3V,VDD 统一接电源,GND 共地,完事。但这里面最容易出问题的就是上拉电阻的值。
上拉电阻的作用是让总线在空闲时维持高电平,传感器想拉低总线时,靠的是内部的漏极开路结构,所以总线上任何时刻只有一个设备可以强调总线。电阻选太大,总线电容会让上升沿变得很缓;选太小,传感器拉低时会分担太多电流,导致低电平电压偏高。经验值是在 4.7kΩ 左右,板内短距离连接完全没问题。如果总线要拉出去几米甚至十几米,上拉电阻要适当降到 2.2kΩ 或 3.3kΩ,补偿线缆的分布电容。还有一种做法是用 P 沟道 MOSFET 做主动上拉,上升沿更陡,适合特别长的总线。
提示:上拉电阻的 3.3V 电源必须和 MCU、传感器同源,不能在两个电源域之间跨接。如果传感器用 5V 供电而 MCU 是 3.3V,DQ 线上的电平转换不能省,最简单的方式是在 DQ 上串一个电阻并加钳位二极管。
GPIO 一定要配置成开漏输出模式。很多人第一次写单总线驱动时习惯用推挽输出,结果读数据永远不对,原因就是推挽输出强驱动高电平时,传感器根本拉不动总线,时序直接乱套。开漏模式配合上拉电阻,总线才能被主机和从机共同驱动。
2.2 本地和远程传感器怎样组织在一条总线上
一条总线上挂多少颗 PJ85718DM 没有硬性限制,总线电容才是真正约束。中短距离挂十几颗都没问题,每颗传感器在温度转换时会从电源吸取几毫安的电流,几十颗同时转换对电源也是一个考验。组织方式上,本地传感器可以直接把 DQ 走线就近汇到 MCU,远程传感器走一条双绞线出去,沿途分线接到各测点。
这里有一个重要的原则:传感器的地址匹配是逻辑层面的,和物理位置无关。你第一次上电搜索总线时读到的 64 位 ID 和物理位置没有对应关系,所以工程上一定要做一步“登记”:先在每个安装位置接上一颗传感器,读回它的 ID,记录在配置表里,之后固件按配置表轮询。如果不做登记,以后想判断哪个数据对应哪个测点就只能靠猜,这在现场维护时是灾难。
2.3 长线传输与抗干扰设计
远程传感器真正考验的是硬件抗干扰。变频器启动的瞬间、接触器吸合、电机换向都会在长线上耦合出噪声,噪声轻则造成 CRC 校验失败,重则让单片机把噪声当成传感器应答,总线直接卡死。
几个比较靠谱的做法:
- 远程线用双绞线,屏蔽层单端接地,不要两端都接地,避免地环路电流。
- 总线入口处加 TVS 管,钳位浪涌电压,再做一级 RC 滤波。
- DQ 线上可以串一个 100Ω 到 220Ω 的电阻,降低振铃幅度,代价是边沿变缓,所以和上拉电阻要配合调整。
- 每个传感器的 VDD 和 GND 之间放 100nF 的陶瓷电容,最好再加一个 10μF 的电解电容,温度转换瞬间电流突变时能稳住电压。
实际项目里,如果总线上同时有传感器做外部供电,还要注意传感器的 GND 和主控 GND 必须可靠连接,不能依赖屏蔽层走地电流。远程点分散在几个方向上时,宁可分两条总线,也不要一条总线绕一大圈,总线电容和噪声耦合都会成倍增加。
3. 单总线时序:从复位脉冲到 CRC 校验的完整链路
3.1 初始化、读写时隙的时间参数怎么卡
单总线协议里所有通信都是从主机主动发起开始的。第一步是复位脉冲:主机把总线拉低至少 480μs,然后释放,在外接上拉电阻的作用下总线回到高电平,这时总线上的每一颗传感器都会在 60μs 到 240μs 之间把总线拉低一段时间,形成一个存在脉冲。主机检测到这个低电平,就表示总线上有设备在,可以继续通信。
读写每一位的时间叫时隙,每个时隙最短 60μs,时隙之间要有至少 1μs 的恢复时间。写 1 和写 0 的区别在于低电平持续的长短:写 0 时主机把总线拉低 60μs 到 120μs;写 1 时主机只拉低 1μs 到 15μs 就释放,剩下的时间让总线恢复高电平。传感器在时隙开始的 15μs 到 60μs 这段时间采样总线电平,所以主机只要保证时序窗口正确,就能把数据位传过去。
读时隙稍微绕一点:主机先拉低总线 1μs 到 6μs,然后释放总线,从机如果这一位是 0,就会继续把总线拉低;如果这一位是 1,就什么都不做,总线被上拉电阻拉高。主机必须在释放总线后的 15μs 内采样,才能在从机释放总线前读到正确的电平。总结一句话:读到低电平就是 0,读到高电平就是 1。
3.2 读取温度的完整命令流
以读取单颗传感器温度为例,完整流程是:
- 主机发复位脉冲,等待存在脉冲。
- 发 Skip ROM 命令 0xCC,跳过地址匹配,直接对总线上唯一一颗或者全部传感器操作。
- 发 Convert T 命令 0x44,启动温度转换。12 位分辨率下,转换时间最长 750ms。
- 等待转换完成,再次复位。
- 发 Skip ROM 0xCC,再发 Read Scratchpad 命令 0xBE。
- 连续读 9 个字节:温度低字节、温度高字节、TH 寄存器、TL 寄存器、配置寄存器、三个保留字节、CRC 校验字节。
- 把温度字节拼接成 16 位数据,根据符号位做正负处理,再乘以 0.0625 得到实际温度。
如果是多点测温,更高效的做法是先发 Skip ROM + Convert T,让总线上所有传感器同时开始转换,等待 750ms 后再逐颗读取。读取时用 Match ROM 命令 0x55,后面跟 8 个字节的 64 位 ID,主机会先确认是不是在跟目标设备说话,然后再发 Read Scratchpad。这样每颗传感器只需要一次独立的读取周期,效率比逐个启动转换高得多。
3.3 CRC 校验为什么不能省
PJ85718DM 和 DS18B20 一样,在 Scratchpad 的第 9 字节放了前 8 个字节的 CRC 校验值,多项式是 x^8+x^5+x^4+1。主机读完 8 个数据字节后,用同一个多项式对前 8 个字节做 CRC 计算,结果和第 9 字节比对,一致才认为这一帧数据是可信的。
这东西在长线现场绝对不能省。我见过一个项目,远程传感器布线跟变频器动力电缆走了同一个桥架,读回来的温度偶尔会跳到 50 多度又跳回正常值,加了 CRC 校验之后发现所有跳变的数据帧全部 CRC 错误,直接丢弃重读,读数立刻恢复正常。没有这层校验,错误数据就会被当成真实温度送进 PID 控制器,造成阀门误动作。
CRC 计算在 MCU 上用查表法很轻松,100MHz 主频下读一次完整数据帧加上校验时间也就几十微秒。建议把 CRC 校验放在读取函数的尾部,校验失败时不要立刻重发,先等一下个节拍再重读,避免总线风暴。
4. 固件实现:把 MCU 变成一台温度采集网关
4.1 驱动层:开漏 GPIO 和微秒延时
写单总线驱动,底层就是两个函数:拉低总线、释放总线,以及一个微秒级延时函数。GPIO 配置为开漏模式后,拉低就是往 ODR 寄存器写 0,释放就是写 1,开漏模式下写 1 实际是让引脚变成高阻态,总线电平由外部上拉决定。读取时直接用 IDR 寄存器采样电平。
延时函数是最容易被编译器坑的地方。用 for 循环空转做微秒延时,开优化之后编译器可能直接把这个循环优化掉,延时时间严重缩水,时序就乱了。稳妥做法是使用 DWT 寄存器做精确延时,或者用 SysTick 搭配微秒计数。
void delay_us(uint32_t us) { DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; while (DWT->CYCCNT < us * 100) { // wait } }这条代码假设内核时钟是 100MHz,每个周期 10ns,延时 us 微秒需要 us * 100 个内核周期。实测下来,用 DWT 延时的时序抖动控制在几个内核周期以内,非常稳定。
4.2 搜索 ROM:把每颗传感器的唯一 ID 抄出来
第一次上电或者新增传感器之后,软件要做一次总线扫描,把总线上所有传感器的 64 位 ID 找出来。搜素 ROM 的原理是位分支法:
主机发 Search ROM 命令 0xF0,然后开始逐位搜索。每一位要读两次,第一次所有设备输出自己 ID 当前位的“与”,第二次所有设备输出当前位的补码。所以读到的两个电平组合有四种含义:00 表示总线上既有这位是 0 的设备,也有这位是 1 的设备,出现分支;01 表示所有设备这位都是 1;10 表示所有设备这位都是 0;11 表示总线上没有设备。
主机在每一对读操作之后,需要写一位数据。这一位的作用是指定下一步往哪个分支搜索。如果出现 00,主机可以选 0 或 1,并把选择记录下来,遇到冲突时回到最近的冲突点走另一条分支。遍历完所有位,就能恢复出一颗设备的完整 ID。对多颗设备重复这个过程,直到没有新的分支为止,就拿到了全部门牌号。
搜索 ROM 的代码逻辑上像二叉树遍历,写起来不算复杂,但现场调要是遇到总线上挂的设备型号不一致,时序稍微不稳,搜索过程就会漏掉设备。建议在扫描时做多轮尝试,把连续三次结果一致的 ID 才收入地址表。
4.3 轮询调度:批量转换、逐个读取、容错重试
地址表拿到之后,温度采集就变成固定的轮询循环。建议把流程做成状态机,而不是一个死循环里做长延时。状态可以这样定义:
- 空闲状态:准备新一轮采集,复位总线。
- 广播转换状态:发 Skip ROM + Convert T,让所有传感器同时转换,然后启动 750ms 的软件定时器。
- 等待转换完成:这段时间 MCU 可以去处理通信、按键、显示等其它任务。
- 逐颗读取状态:复位总线,对地址表里的第一个 ID 发 Match ROM + Read Scratchpad,读完 9 字节做 CRC 校验,校验通过就解析温度,校验失败就记一次错误计数,下一轮再重试。然后切到下一颗传感器的读取。
- 全部读完回到空闲状态,等下一个采集周期。
这个流程的好处是异常隔离:某颗传感器坏了或者接线断了,不会拖垮整个总线。单总线协议里,没响应就是没响应,超时后直接跳过,系统继续读其它传感器就好。
4.4 上行链路:RS485/Modbus 与 MQTT 上报
STM32F410RB 把温度读回来之后,要有出口。HVAC 现场最常见的上位机接口是 RS485 跑 Modbus 协议。F410RB 的 USART 很充裕,接一个隔离型 RS485 收发器就能组网,Modbus 寄存器表可以简单定义成:每颗传感器对应两个寄存器,一个存温度整数值(放大 10 倍),一个存传感器状态。这样上位机读温度、判断传感器故障都很直观。
如果要对接物联网平台,可以再加一个 UART 转 WiFi 模块或者 4G 模块,把温度数据按 MQTT 协议发布到云端。MCU 侧只负责打包 JSON 格式的数据块通过串口发给模块,复杂的协议解析都在模块侧完成。这样的架构里,单总线多点采集和远程上报是两条独立链路,温度采集的稳定性不会受网络波动影响。
5. 实测数据与踩坑记录
5.1 实测表:本地点与远程点的读数偏差
样机调试完成后,我做了一轮对比测试,把 PJ85718DM 的读数和一台精度 0.1℃ 的工业温度计放在同一环境里比对。下表是其中一组稳态数据:
| 测点位置 | 传感器ID后3字节 | 参考温度 ℃ | 实测读数 ℃ | 偏差 ℃ |
|---|---|---|---|---|
| 机箱本地 | 0x2A1F3C | 25.32 | 25.38 | +0.06 |
| 送风管远端 | 0xB47D60 | 23.85 | 23.90 | +0.05 |
| 回风管远端 | 0x993E21 | 26.10 | 26.03 | -0.07 |
| 盘管进水口 | 0xC51A8B | 45.20 | 45.27 | +0.07 |
| 盘管出水口 | 0x0E7F4D | 41.35 | 41.28 | -0.07 |
整体偏差都在 ±0.1℃ 以内,对于 HVAC 控制的精度需求来说完全够用。需要说明的是,这个偏差包含了工业温度计本身的误差,PJ85718DM 实际精度应该更好。
5.2 亲手踩过的三个坑
坑一:上拉电阻选大了。最初在样机里用的 10kΩ 上拉,板内测试一切正常,把远程传感器用 15 米双绞线接上之后,CRC 错误率明显升高,读时序的上升沿在示波器上看已经变成一个缓慢的斜坡。原因是总线电容太大,RC 时间常数超过了读时隙的采样窗口。换成 3.3kΩ 上拉之后,波形恢复,CRC 错误基本归零。
坑二:微秒延时被优化。早期驱动里有一段 for 循环空转延时,开编译优化后温度读数偶尔出现跳变,查了半天发现是延时函数实际执行时间缩水到原来的一半,时序窗口整体偏移。改成 DWT 精确延时之后问题消失。这个坑在单总线这种对时序敏感的外设上特别容易踩,而且不查汇编都发现不了。
坑三:多传感器同时转换时电源跌压。系统挂了 8 颗传感器,全部外部供电,结果发现每次广播转换命令之后,总线上的传感器会有一个短暂的电流尖峰,导致长线末端的供电电压跌落几百毫伏,远程传感器的转换结果偶尔偏低 0.125℃。在每颗传感器旁边补了 10μF 电容,并把转换命令改成两批错开发送,问题就解决了。单总线协议虽然允许广播转换,但电源设计跟不上的时候,分批转换比一次全转更安全。
5.3 现场维护里的小技巧
传感器需要更换时,先读回新传感器的 64 位 ID,更新配置表里的地址项,再插到总线上。不要先接线后测地址,否则新设备一上电就会出现在未登记设备列表里,干扰正常的轮询。另外,我习惯在记录配置表时顺带记录传感器安装位置的物理编号,比如“送风管末端-1号”,这样后期排查问题时,软件里的数据能和现场位置快速对上。
6. 这块方案还能怎么扩展
6.1 从温度采集升级到换热效率计算
多点温度拿到之后,价值不只是看当前温度,还能做简单的设备健康诊断。比如盘管进出口水温差变小,同时送风温度偏离设定值,可能说明盘管水侧结垢或者气侧滤网堵塞。温差、风量、水流量这些参数组合起来,就能估算换热效率的变化趋势。STM32F410RB 的计算能力跑这些浮点逻辑绰绰有余,控制程序里加几个判断条件就能实现预警功能。
6.2 低功耗和无线化改造
如果现场不方便布线,可以把采集端做成电池供电节点。STM32F410RB 进入 Stop 模式后,电流可以压到微安级别,定时唤醒后花 100ms 完成一次总线扫描和温度采集,再把结果通过无线模块发出去。单总线这边的传感器虽然没有真正意义上的休眠指令,但可以通过降低采样频率来等效降功耗。硬件设计时要注意给传感器供电的 LDO 选静态功耗低的型号,否则一整年下来电量不够看。
6.3 产品化还需要补的功课
真正要做成产品,还有几个硬骨头要啃:一是批量校准,虽然芯片标称精度不错,但大批量生产时还是建议贴片前做一次温度标定,把每颗传感器的固定偏差记录进产线数据库;二是电磁兼容,单总线长线部分还要按标准过一遍 ESD 和 EFT 测试;三是固件升级,F410RB 的 Flash 支持在线升级,最好预留一个 Bootloader,温度采集固件更新就不用拆壳了。
最后说点我对这套方案的感受。单总线测温不是什么新技术,但它放在嵌入式控制和 HVAC 场景里,解决的是实实在在的接线成本和维护效率问题。PJ85718DM 这种兼容类单总线芯片,用起来跟老牌的 DS18B20 几乎一样,供货和成本上会宽裕一些。STM32F410RB 做主机,性能和外设都有冗余,既跑得动温度采集,也管得了后续的 PID 和通信。如果你手头的项目也在为多路温度测点发愁,先拿一块开发板,接三颗传感器跑一轮上面的流程,很快就能判断这方案适不适合你。