最近在帮一个暖通空调(HVAC)控制项目做温度采集改造,主机用的是STM32F405ZG,传感器选了PJ85718DM这颗单总线数字温度传感器。项目要同时搞定两路需求:一路是主控板本地温度,也就是机柜内部或者回风口附近的环境温度;另一路是分布在管道、风井、各个房间里的远程温度,得靠通讯链路把几十米甚至上百米外的传感器数据汇到主机上。这篇文章就把这套系统的硬件设计、底层驱动、远程通信和实测校准过程完整记下来,给同样在做嵌入式测温方案的朋友一个可以直接抄作业的参考。
先说结论:PJ85718DM 这类单总线传感器用在 HVAC 分布式测温场景里非常合适,成本低、布线简单、一颗主控挂几十个测点毫无压力;STM32F405ZG 做主机也足够从容,外设丰富、跑 RTOS 不费力,整体方案从硬件到软件都能控制在比较清爽的范围内。下面按实际开发顺序展开讲。
1. 项目需求拆解:HVAC场景下“本地与远程”究竟要什么
1.1 本地温度与远程温度的定位差异
做 HVAC 系统的人都知道,温度测点从来不是一个“测一下就完事”的事情。一个完整的风机盘管或者组合式空调机组,至少需要采回风温度、送风温度、混风温度、盘管进出口水温、室外补偿温度这几个关键量。主控板旁边的温度我们叫它本地温度,比如电气盒内部的电子器件工作环境温度、回风口的平均温度;而分布在风管深处、水系统管道上、各个房间里的测点,跟主机之间隔着少则几米多则上百米的线缆,这些就是远程温度。
本地温度直接参与控制逻辑,比如回风温度超过了设定值,控制器就要加大冷阀开度,本地温度采集要求实时性高、数据稳定,通常 1 秒以内刷新一次比较稳妥。远程温度则更多服务于系统能效优化和故障诊断,比如判断风管送风温度是否异常、各房间温度是否均衡、换热器进出水温差是否合理,刷新周期放到 5 秒甚至 10 秒都能接受。两种温度的性质不同,对采集频率、通讯可靠性、数据精度的要求也就不一样,这是设计整套方案之前必须先想明白的。
另外一个容易踩坑的地方是“远程”这个词的歧义。标题里说的远程温度监测,我理解包含两层:一层是传感器距离主机远,需要通过 RS-485、无线模块这类物理链路把数据拉回来;另一层是主机本身的数据要能被运维人员远程查看,也就是把数据上送到上位机或者云平台。这两层在本项目里都要做,缺一个都算不上完整的远程监测。
1.2 为什么选 PJ85718DM 搭配 STM32F405ZG
先说传感器。PJ85718DM 是典型的单总线数字温度传感器,和 DS18B20 属于同类方案,三根线(电源、地、数据)就能工作,数据线上可以直接挂多个器件,每个器件都有全球唯一的 64 位 ROM 编码,主机通过这个编码就能区分谁是谁。HVAC 现场最头疼的就是布线和抗干扰,单总线这种“一条数据线串一串传感器”的方式,线缆成本低、施工复杂度小,简直是为分布式测温量身定做的。具体到这颗芯片的工作电压范围 2.7V~5.5V,测温范围 -55℃~+125℃,12 位分辨率下精度能做到 ±0.5℃(-10℃~+85℃),对 HVAC 这种温度区间来说绰绰有余。不同批次的器件在高温段线性度会有细微差异,但工程应用上通过软件做一点校准就能满足绝大多数场景。
再来看主控。STM32F405ZG 属于 Cortex-M4F 内核,主频跑到 168MHz,自带 1MB Flash 和 192KB RAM,USB OTG、多个 USART、SPI、I2C、CAN 这些外设齐全。有人可能会问,做个温度采集用得着这么高端的 MCU 吗?我的看法是,得看项目的长期规划。单纯采集温度确实 8 位机也能干,但这个项目后续还要扩展阀门控制、故障报警、触摸屏交互、远程升级这些功能,现在选一个资源充裕的平台,后面就不用推倒重来。STM32F405ZG 跑 FreeRTOS 很轻松,USB 虚拟串口、Modbus 协议栈、MQTT 通信这些活儿都能同时干,不会出现“CPU 占用率到了 80% 还在担心任务卡顿”的窘境。
对比一下其他方案。用 PT100 铂电阻加 ADC 的方案精度高,但每个测点都要配恒流源和调理电路,多测点成本高、接线复杂。I2C 接口的数字传感器比如 SHT30 性能确实好,但 I2C 总线的传输距离有限,长线场景基本不适用,而且多节点需要地址扩展芯片。从灵活性、成本、施工难度三个维度综合来看,单总线方案在 HVAC 场景里的综合性价比最高。
2. 硬件搭建:PJ85718DM 与 STM32F405ZG 的接线要点
2.1 PJ85718DM 关键参数与接线规范
这里有一个比较重要的提醒:PJ85718DM 虽然市场上的货很多,但不同品牌、不同批次的数据手册在电气参数上可能稍有出入,动手画板之前务必要以你手头那颗料对应的数据手册为准。我下面写的参数是基于这类单总线传感器的通用规格,并且在项目里实际验证过的,供参考。
PJ85718DM 典型接法很简单:VCC 接 3.3V 或 5V,GND 接地,DQ 数据引脚接主控的一个 GPIO,同时数据线上要接一个上拉电阻到电源。上拉电阻建议从 4.7kΩ 起步,如果是几百米的长线,换成 2.2kΩ 甚至 1kΩ 效果更好。这个电阻不是随便选的,因为单总线协议靠的是“释放总线让上拉电阻把电平拉高”来传输数据,如果上拉电阻太大,加上线缆本身的寄生电容,上升沿会变得很缓,主机读到的电平就可能出错;如果上拉电阻太小,功耗又太大,影响低功耗场景。简单估算,RC 时间常数等于电阻乘以总线电容,几百米的屏蔽线电容大概每米 100pF 左右,200 米线加上 4.7kΩ 上拉,时间常数接近 0.1ms,用来跑单总线的 60μs 级时序就有点悬了,这时候就得换上拉电阻或者缩短分支。
GPIO 在软件上要配置成开漏输出模式,这样才能实现“主机拉低”和“释放总线”这两种状态。有些开发者偷懒用推挽输出代替,短线上可能侥幸能跑,但长线或者挂多个传感器时会出现总线竞争问题,强烈不建议这么做。接线顺序建议手拉手的菊花链方式,一根主线从头串到尾,每个传感器在就近位置引出很短的分支,而不是把所有传感器都接到主机一个点,后者会造成严重的信号反射和不规则延时。
2.2 多点传感器布线与地址识别
单总线系统最爽的一点就是不用给每个传感器单独编地址,芯片出厂自带的 64 位激光 ROM 编码就是它的身份证。主机通过单总线协议里的 Search ROM 命令,可以在一条总线上把所有设备识别出来。实际项目里我们挂 8 个远程测点,有的在风管保温层里,有的在水管阀门井里,安装顺序混乱没关系,只要在调试阶段逐个读出 ROM 编号,然后在程序里做个“ROM 编号-物理位置”映射表就行。比如28 01 4B 8C 9E 7A 01 00这个编号对应的是三楼东区回风管,改写映射关系就能完成测点重新分配,完全不需要动线缆。
远程传感器的安装细节直接影响数据可靠性。风管上的测点要把探头插到气流核心区,而不是贴着管壁,否则测到的是管壁温度而不是空气温度。水管上的测点要配合导热硅脂和保温棉,保证探头和管壁充分接触并且不受环境温度干扰。阀门井、设备夹层这种潮湿环境,接线端子要做好防水处理,我习惯在接头处灌胶或者用热缩管双重密封,不然用不了几个月就会出现 CRC 校验错误频发的问题。
2.3 通讯链路与电源设计
远程数据回传,本项目用了双通道方案:一条是 RS-485 总线,负责把分布在设备间的远程温度节点数据汇总到主机;另一条是 WiFi 模组,主机把温度数据上传到物联网平台,让运维人员在手机上就能看到全局温度分布。两条链路互备,485 断了还有 WiFi,WiFi 断了本地 485 还能工作,可靠性翻倍。
RS-485 部分用典型的收发器芯片实现 A/B 差分总线,两个终端节点各并一个 120Ω 终端电阻,这个电阻不是摆设,它能匹配线缆阻抗、消除反射,长线通信没有它会偶尔出现乱码。HVAC 现场设备启停频繁,电机、变频器干扰很大,所以 485 总线和传感器总线我都建议用屏蔽双绞线,屏蔽层单端接地。如果成本允许,主控侧的 485 收发器加个数字隔离,把现场和主控的电气部分隔开,能有效避免地环路和浪涌损坏主机。
电源这块是 HVAC 场景最容易翻车的环节。现场很多东西是用 24V AC 供电的,你给主控板供的 3.3V 如果来自普通的降压模块,电网波动和电机启停很容易把电压拉出毛刺。我们实际用了隔离 DC-DC 模块把 24V DC 变成隔离 5V,再通过 LDO 给传感器和逻辑电路供电,实测在工作电压波动±20% 的情况下系统依然稳定。主控板电源入口要加 TVS 管和共模电感,这些防护器件成本不高,但能避免很多现场返修。
3. 软件实现:从单总线时序到多节点轮询
3.1 单总线驱动:时序与CRC
单总线协议对时序要求比较严格,好在 STM32F405ZG 跑 168MHz,用延时函数实现微秒级时序完全够用。整个协议的核心就几个基础操作:复位脉冲、存在脉冲、写 0、写 1、读 0、读 1。写时序的关键是主机先把总线拉低,然后延时一段时间再释放,通过这个时间长度来区分写 0 还是写 1,具体参数在数据手册里有明确区间,建议取中间值避免误差。读时序则是主机拉低总线后迅速释放,然后在极短的时间窗口内采样数据线上的电平。
GPIO 模式切换是单总线驱动里容易忽略的性能瓶颈。如果在每次写位和读位时都去调用库函数切换推挽/开漏模式,再嵌套延时,一次温度读取跑下来要花不少时间。优化做法是:把 GPIO 配置成开漏输出,开漏模式本身既能拉低又能释放,内部没有上拉时释放状态就是高阻,正好配合外部上拉电阻实现高电平。这样从头到尾不需要切换模式,软件逻辑清爽很多。当然,如果你用的引脚内部有可配置的上拉,也可以利用内部上拉省掉外部电阻,但 HVAC 长线场景还是用外部上拉稳妥。
数据校验方面,PJ85718DM 返回的 9 字节数据里最后一个是 CRC 校验字节,算法用的是 8 位多项式 x^8+x^5+x^4+1,也就是常见的 CRC-8/MAXIM 算法。写驱动的时候别自己拍脑袋写校验,直接用查表法或者按标准的初始值 0x00 来计算,实测几百次读取没有误判过。数据手册里的示例代码虽然能用,但建议自己把时序部分重新组织一下,用状态机实现,后面上 RTOS 会舒服很多。
3.2 传感器识别与温度读取流程
完整的温度读取流程可以拆成三步:第一步,复位总线并检测存在脉冲,确保总线上有设备在线;第二步,发 ROM 命令,如果只有一个传感器,用 Skip ROM 命令跳过地址匹配直接操作,如果挂了多个传感器,就必须用 Match ROM 命令加上目标传感器的 64 位 ROM 编码来选中某个设备;第三步,发功能命令,比如 Convert T 启动温度转换,然后等转换完成,再用 Read Scratchpad 把 9 字节数据读出来。
这里有一个效率优化点。多个传感器轮流做“启动转换-等待-读取”,每个都要等转换完成,如果分辨率设成 12 位,单次转换时间最长 750ms,8 个传感器就是 6 秒,太慢了。正确做法是:先广播一条 Skip ROM + Convert T 命令,让总线上所有传感器同时开始转换,然后统一等待 750ms,之后再用 Match ROM 逐个读取每个传感器的温度。这样 8 个传感器的完整采集周期也只有 750ms 左右,效率提升了接近一个数量级。
等待转换完成的 750ms 里,CPU 完全不用傻等。在 FreeRTOS 里让采集任务调用vTaskDelay(pdMS_TO_TICKS(800)),这期间调度器会切换到其他任务干活,什么串口收发、按键扫描都不耽误。有一点要注意:单总线时序的关键部位不能被中断打断,否则时序偏差会导致读回来的数据全是 0xFF 或者 CRC 错误。我通常的做法是进入临界区(taskENTER_CRITICAL())来保护完整的字节读写操作,因为一个字节的位操作时长很短的,临界区占用时间可以忽略,不会影响系统的实时性。
3.3 FreeRTOS 任务划分与调度设计
这个项目我建了三个任务:温度采集任务、通信任务、状态监控任务,优先级从高到低排列。温度采集任务优先级最高,因为它是整个系统的数据源头,默认 1 秒周期执行;通信任务负责把温度数据通过 USB 和 WiFi 发出去,5 秒周期就够;状态监控任务处理告警、LED 指示这些杂活,周期放宽到 200ms。
任务间通信用 FreeRTOS 队列来做。采集任务把本地温度和 8 路远程温度组成一个结构体,xQueueSend()发给通信任务,通信任务收到后再格式化发送。这一步看起来简单,但能避免多个任务同时访问全局变量导致的互斥问题。就算某些任务偶尔卡顿,队列也能把数据暂存起来,不会丢数据。栈空间分配上,温度采集任务里面用了局部缓冲区存储 ROM 编码和数据,我给了 512 字节,实测跑 8 路传感器采集没有溢出,如果后续扩展到 16 路传感器,栈空间要相应加大到 1KB。
滤波算法是软件设计里性价比最高的环节。HVAC 系统的温度变化本身就很慢,完全没必要对原始数据全盘信任。我在采集任务里做了个简单的滑动平均滤波,每路温度保留最近 5 次有效读数,去掉最大值和最小值,剩下的取平均。效果立竿见影:原本传感器自身 0.5℃ 的随机波动,滤波后能稳定在 0.1℃ 左右,而且对系统的阶跃响应延迟也可以忽略。
4. 数据上云与远程监测:两种远程通道的落地
4.1 本地监控:USB-CDC 虚拟串口
STM32F405ZG 自带 USB OTG 外设,可以很方便地做成 USB 虚拟串口(USB-CDC),调试阶段直接插电脑,用串口工具就能看到实时温度数据,不需要额外的 USB 转串口芯片。这个功能对 HVAC 项目的现场调试帮助特别大,工程师拿着笔记本往主机 USB 口一插,所有测点的温度、传感器在线状态、最近一次 CRC 校验结果全都看得到。
虚拟串口的数据格式我建议用自定义的 ASCII 帧,比如一行TEMP,local,23.45,ok表示本地温度,TEMP,remote_01,24.10,ok表示远程 1 号测点。这种格式人和机器都能读,排查问题的时候肉眼扫一眼就知道哪个测点异常。当然,如果你要对接的是现成的组态软件,那直接输出 Modbus RTU 协议更合适,USB-CDC 底层是全双工串口,Modbus RTU 主站模式可以直接跑。
有一点经验想特别说一下:USB-CDC 在 Windows 上会被识别成 COM 口,但 STM32 的 USB 描述符里如果设备名称取得太随意,多个设备同时插电脑时会让人分不清谁是谁。我习惯在描述符里把产品名改成类似HVAC_TEMP_MASTER这样的名称,几百台设备同时调试的时候省下很多找端口的功夫。
4.2 远程监控:WiFi模组 + MQTT
远程监控部分,我在主控板上预留了一个 USART 接口接 WiFi 模组,模组跑 AT 固件,主控通过串口 AT 指令控制它连接 Wi-Fi 和 MQTT 服务器。为什么不直接在主控上跑 TCP/IP 协议栈?也不是不行,但能少碰网络的底层细节就少碰,AT 指令方案开发周期短、出问题的概率小,对 HVAC 这种非 IT 核心业务的场景来说更务实。
MQTT 协议在这里是首选,因为它是发布订阅模式,多个客户端可以同时订阅同一个主题,调试人员用手机、工程师用电脑、云平台用后端解析,都能同时收到温度数据。主题设计我用了两层结构:hvac/{device_id}/temperature发布整体温度 JSON 数据,hvac/{device_id}/status发布设备在线状态和告警信息。这样下游系统根据自己的需要订阅对应主题,不用一股脑收全部数据。JSON 格式类似这样:
{ "device": "MASTER_01", "ts": 1719200000, "local_temp": 23.45, "remote": [ {"id": 1, "temp": 24.10}, {"id": 2, "temp": 25.02} ] }MQTT 的 QoS 等级选了 0,可能有朋友觉得这不靠谱,温度数据不是应该用 QoS 1 吗?我的实际考量是:温度数据本身就有时效性,下一条数据 5 秒后就来了,丢一条对整体监控毫无影响;QoS 0 的通信开销最小,不会给 WiFi 链路增加太大压力。真正重要的告警信息,比如传感器断线、温度越限,放在 status 主题里我一定会用 QoS 1,确保重要事件不丢失。
WiFi 模组的断线重连是远程监控稳定性的关键。主控侧要做一个定时链路检测,比如每 30 秒发一次 PING 指令检查模组是否仍在网络里,如果连续三次无响应就主动重启。实际测试中,WiFi 模组偶尔会假死,AT 指令不回复,只有硬件复位才能恢复,所以主控板上模组的 RESET 引脚要接到一个 GPIO 上,实现“软复位解决不了就硬复位”的策略。
4.3 数据帧格式与周期
本地温度和远程温度的刷新周期不一样,我在软件里把它们统一了上报时机:每 1 秒采集一次并更新本地缓存,每 5 秒把所有缓存数据打包成一条 JSON 上送。这个折中方案的考量是:1 秒上送一次对 MQTT 服务器和手机 APP 来说太频繁,而且 HVAC 温控本来就不是秒级响应的系统;5 秒上送一次又能保证用户在手机上看到的温度变化足够连续,温差跳变不会显得突兀。
RS-485 链路上的数据帧格式则不同,走的是 Modbus RTU,主机做从站,上位机或者触摸屏定时来读保持寄存器。寄存器分配是这么规划的:地址 0 存本地温度,地址 1~8 存 8 路远程温度,温度值统一放大 10 倍存储,23.45℃ 就存 234。这样做的目的是避免浮点数在 Modbus 协议里传输的麻烦,整数传输高效而且不容易有精度误解。每路温度旁边还配了一个状态寄存器,0 表示正常,1 表示离线,2 表示 CRC 错误,上位机可以根据这个状态值做显示优化,而不是把离谱的错误温度当成真数据用。
5. 实测定标:温度误差修正与常见问题排查
5.1 温度校准实操
传感器出厂精度虽然标了 ±0.5℃,但实际项目里为了把温度控制做得更精确,我会做一次简单的两点校准。校准条件很朴素:一个保温杯装冰水混合物做 0℃ 参考,一个恒温水浴锅加热到 60℃ 做高温参考,把传感器探头浸入其中,等读数稳定后记录实际值和测量值。每个传感器都做一遍,然后算偏移量和增益系数,存到主控的 Flash 里,读取温度时做线性修正。
如果测点数量多,逐个校准的工作量确实不小。我在项目现场的简化做法是:抽 5 个传感器做校准,然后把修正参数的平均值应用到同批次的其余传感器上,实测这批传感器一致性还是不错的,校准后整体误差能压到 ±0.2℃ 以内。对 HVAC 应用来说这个精度已经超过控制需求了,冷水盘管出水温度控制的死区也就 0.5℃ 左右。
校准数据要设计成可以现场修改的。我在程序里放了一条特殊串口命令,调试人员通过 USB 虚拟串口输入CAL 1 0.2 0.998,就能修正 1 号测点的偏移量和斜率,不用重新烧录固件。这个细节在设备安装初期特别有用,因为传感器装好以后探头位置可能会有微小差异,现场微调比回厂重新校准省事得多。
5.2 异常现象与排查速查表
现场调试过程中会遇到各种奇奇怪怪的现象,我整理一个速查表,覆盖了单总线传感器在 HVAC 环境下的典型故障模式,每个问题都值得实际跑一遍验证。
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 读取温度恒为 85℃ | 传感器处于复位后的默认状态,疑似总线通信没成功 | 检查接线和上拉电阻,用示波器看波形 |
| 所有测点读回 0xFF | 总线上没有设备响应,或主机 GPIO 配置错误 | 用万用表量 DQ 电平,确认开漏模式 |
| 单个测点间歇性 CRC 错误 | 该节点的接线接触不良,或分支线太长 | 重新压接端子,缩短分支,加屏蔽 |
| 温度跳变超过 10℃ | 电源噪声干扰,或传感器引脚进水短路 | 检查供电纹波,节点做好防水密封 |
| 挂 8 个传感器时偶发丢数据 | 总线驱动能力不足,或初始化后未正确等待 | 上拉电阻改小,检查复位时序 |
85℃ 这个现象特别有代表性。很多单总线传感器在上电后温度寄存器默认值是 0x0550,也就是 85℃,如果主机一直没有成功发出转换命令或者读取命令,传感器就会一直保持这个值。所以看到 85℃ 首先不是怀疑传感器坏了,而是先查通信链路。另外,挂在总线末端的传感器如果线缆预留太短、硬拽着测量,受力会导致瞬断瞬接,这类物理接触问题示波器都不一定能立刻捕捉到,最有效的办法是徒手波动导线看读数有没有跳动。
5.3 实际踩过的坑
第一坑:上拉电阻选太大。项目早期我用的是 10kΩ 上拉,短距离测试一切正常,结果传感器拉到现场 100 米左右的线缆后,读回来的数据开始频繁出错。后来算了一下,线缆电容让上升沿严重变缓,主机在采样窗口读到的电平还在爬升阶段,逻辑判断就会出错。换成 2.2kΩ 上拉后问题立刻消失,这个案例也印证了长线必须重选上拉电阻的判断。
第二坑:转换等待时间不足。采用广播转换优化后,我在等待环节只延时了 500ms,结果总有一部分传感器读回的是上一次的旧值。看数据手册才发现 12 位分辨率下最大转换时间是 750ms,气温低时转换时间还会略长,稳妥起见统一等 800ms。从那以后我特别提醒自己:任何涉及到“最大时间”的参数,都应该留 20% 以上的余量。
第三坑:在中断里做单总线延时。早期调试时我用定时器中断做时基,结果中断里嵌套了单总线位操作,时序忽快忽慢,系统卡死。后来把时序操作全部挪到任务里做,并用临界区保护,再也没出过同类问题。嵌入式开发里“中断就该短平快”这句话,是在踩过坑之后才真正理解的。
第四坑:485 终端电阻在短线上成了耗电大户。调试台上两根 50cm 短线就并了 120Ω 终端电阻,收发器芯片在静止状态也持续跑电流,主机稍热。后来明确终端电阻只加在长线两端的物理末端,短线测试时不加,问题解决。做硬件调试,规则是死的,现场条件才是决定怎么用的关键。
最后补一句个人经验:这种多测点测温系统,一定要在主机端做一个“测点在线状态”的可视化界面,红绿灯显示每个远程传感器最近一次通信是否成功。没有这个界面,现场运维人员会在 8 路测点里挨个猜哪一路出了问题,有了它,故障点 10 秒就能定位。这套系统交付之后,运维反馈最实用的功能反而不是精确到 0.1℃ 的测温,而是那个简单到不行的在线状态灯——这大概就是做过现场项目的人才懂的朴素需求。