STM32与PJ85718DM协同实现本地及RS485远程温度监测系统
1. 项目概述与整体架构1.1 本地与远程温度到底指什么搞嵌入式开发的人特别是碰过暖通空调HVAC项目的基本都绕不开温度采集。这次用 STM32F103RC 和 PJ85718DM 做了一套本地与远程温度监测方案标题里的“本地温度”指的是直接挂在 MCU 板子上的温度传感器读数距离近、走线短驱动简单“远程温度”则是通过 RS485 总线把分布在现场各处的温度节点数据汇聚回来比如机房、风管、冷站里的传感器跟主机隔了几十米甚至上百米。两个数据源合在一起才能在控制器里做完整的温度监控和逻辑判断。这套方案比较适合两类读者刚入门嵌入式、想搞清楚 MCU 怎么跟数字温度传感器配合的人以及正在做 HVAC 或楼宇自控项目需要快速搭一套温度采集原型验证的工程师。PJ85718DM 这类一线总线数字温度传感器优点是接线少、成本低、精度基本够用STM32F103RC 则是老牌工业级 Cortex-M3 芯片外设丰富、资料多做这种中小型监控节点非常顺手。我这次搭的结构是单主机加多个从机节点的星型加总线组合。主机板上放一颗 PJ85718DM 做本地基准温度从机节点也各带一颗 PJ85718DM通过 RS485 挂在同一条总线上。主机定时轮询所有从机读取温度寄存器的值再跟本地温度合并处理最后通过串口输出到调试终端或上位机软件。HVAC 场景里的冷冻水供水回水温度、送风回风温度、室内温度补偿等都可以按这套逻辑套进去。1.2 主机为什么是 STM32F103RC 而不是更小的 MCUSTM32F103RC 是 64 引脚的 LQFP 封装内置 256KB Flash 和 48KB RAM主频最高 72MHz。放在这个项目里Flash 和 RAM 都有大量余量但这恰恰是我选它的理由之一做 HVAC 监控节点鬼知道后面甲方会加什么需求。今天只是采集温度和跑 485 通信明天可能就要加 OLED 显示、继电器控制、故障记录、远程升级这个时候 64KB Flash 的芯片就很紧张而 256KB 版本基本不用太抠空间。另外 STM32F103 的 USART 带 DMA 支持多路串口同时跑 485 轮询和调试输出不会互相阻塞配合定时器中断做超时判定协议栈写起来很干净。就算最终产品想上 FreeRTOS这块芯片也带得动。实际项目中我见过不少人用小容量芯片做原型需求一变就推倒重来选型阶段留足余量后面能省很多事。1.3 HVAC 场景里温度数据从哪里来HVAC 系统的温度监测点通常分成几层一是设备层的送风温度、回风温度、盘管出风温度这些数据要进控制器做 PID 调节二是水系统的冷冻水供水温度、回水温度用来判断冷机效率和负荷三是环境层的室内温度、室外温度用于新风补偿和节能策略。这些传感器有的离控制器近有的分布在风管、机房、水管附近物理距离差异很大。我这个项目里的“本地温度”就对应控制器机柜内部或面板附近的温度作为设备运行环境的辅助参考“远程温度”则对应分布在现场的各个温度点通过 RS485 总线统一上报。这么设计的好处是传感器只负责采集不需要每个节点都有复杂的人机界面主机集中处理后统一显示和转发。实际施工时远程节点可以做成很小的电路板带一颗 PJ85718DM 和一颗 485 收发器用 24V DC 供电放进风管或管道保温层外面非常方便。2. PJ85718DM 传感器解析与本地温度采集实现2.1 读懂 PJ85718DM 的接口信号与电气接线PJ85718DM 我按典型单总线1-Wire 类数字温度传感器来处理。这类传感器的数据引脚只有一根既是输入也是输出主机通过严格的时序来控制读写。拿到芯片先看数据手册确认供电电压范围、测量精度、温度寄存器地址和时序参数不同厂家的版本会有细节差异但整体思路是一致的。硬件接线方面数据线 DQ 必须接一个 4.7kΩ 左右的上拉电阻到 VCC因为单总线协议是开漏输出主机和从机都是靠拉低电平来传输数据的没有上拉电阻总线根本拉不回来高电平。传感器 VCC 和 GND 之间加 100nF 高频去耦电容。如果传感器和 MCU 之间距离超过 1 米建议用屏蔽双绞线上拉电阻可以减小到 2.2kΩ 以补偿线缆电容带来的上升沿变缓。特别注意我实际测试时发现用很长的普通杜邦线连接传感器时序波形边沿会被电容拖得很平导致读回来的数据全是 0xFF。换成双绞线并降低上拉电阻后问题消失。布线走线尽量远离继电器、接触器和电机驱动线HVAC 现场这些东西多干扰很现实。2.2 本地温度读取时序复位、写指令、读暂存器单总线设备的读取流程可以拆成四步复位、发送 ROM 命令、发送功能命令、读取数据。先说复位。主机先把总线拉低 480 到 640 微秒然后释放总线传感器会在一小段等待后把总线拉低 60 到 240 微秒作为应答脉冲。主机检测到这个应答脉冲就说明总线上有传感器在工作。这一步是整个时序的基础很多读取异常都出在复位波形不对。复位之后如果总线上只挂了一个传感器可以直接发送跳过 ROM 命令 0xCC省去匹配 64 位序列号的过程。然后就发温度转换命令 0x44命令发出后传感器开始内部 ADC 转换12 位分辨率典型转换时间是 750 毫秒。在这段时间内不能读暂存器否则读到的还是上一次的结果。转换完成后再复位一次发跳过 ROM 命令接着发读暂存器命令 0xBE就可以连续读出 9 个字节其中前两个字节就是温度值的低位和高位。驱动代码用 GPIO 模拟时序就可以STM32F103 的主频很高微秒级延时用 SysTick 或者简单的空循环都能实现。下面是我验证过的最小驱动片段#define DQ_PIN GPIO_PIN_9 #define DQ_PORT GPIOB void DQ_SetInput(void) { GPIOB-CRH ~(GPIO_CRH_CNF9 | GPIO_CRH_MODE9); GPIOB-CRH | GPIO_CRH_CNF9_0; // 输入浮空 } void DQ_SetOutputLow(void) { GPIOB-CRH ~(GPIO_CRH_CNF9 | GPIO_CRH_MODE9); GPIOB-CRH | GPIO_CRH_MODE9_1; // 推挽输出 2MHz DQ_PORT-BRR DQ_PIN; } void DQ_SetOutputHigh(void) { DQ_PORT-BSRR DQ_PIN; DQ_SetInput(); } uint8_t ow_reset(void) { uint8_t presence 0; DQ_SetOutputLow(); delay_us(500); DQ_SetInput(); delay_us(60); presence (GPIOB-IDR DQ_PIN) ? 1 : 0; delay_us(480); return presence; } void ow_write_bit(uint8_t bit) { if (bit) { DQ_SetOutputLow(); delay_us(6); DQ_SetInput(); delay_us(60); } else { DQ_SetOutputLow(); delay_us(60); DQ_SetInput(); delay_us(6); } } uint8_t ow_read_bit(void) { uint8_t bit 0; DQ_SetOutputLow(); delay_us(1); DQ_SetInput(); delay_us(8); bit (GPIOB-IDR DQ_PIN) ? 1 : 0; delay_us(55); return bit; }写函数和读函数都必须在严格的时隙内完成主中断如果嵌套太深时序容易跑偏。我建议在读取 P J85718DM 的过程中关中断或者把这段操作放到定时器中断里按状态机处理实测更稳。2.3 温度换算与防抖滤波处理读出暂存器的前两个字节后温度值要按位拆解。假设 12 位分辨率下原始寄存器值的高 5 位是符号扩展位实际温度数据占 12 位。把两个字节拼成一个 16 位有符号数再乘以 0.0625 就是摄氏度因为单总线传感器内部 ADC 的分辨率是 1/16 摄氏度。比如原始值是 0x0191换算出来就是 25.0625℃。注意负数温度的情况要先把无符号拼出来的数转成 int16 再计算否则变负数会出错。代码很简单uint8_t lsb read_byte(); uint8_t msb read_byte(); int16_t raw (int16_t)((msb 8) | lsb); float temp_c raw * 0.0625f;温度读出来的原始值可以通过再次读取配置寄存器确认分辨率如果是 9 位或者 10 位模式转换系数不同复位后默认一般是 12 位。项目里为了保证读数稳定我做了两层滤波连续采 5 次先取中值剔除毛刺再做一次滑动平均。HVAC 的温度变化很慢1 秒采一次完全够用5 次滤波窗口不会造成明显滞后。现场如果靠近蒸汽管道或者电机发热源传感器探头本身要做热隔离否则读到的不是介质温度而是设备热辐射。3. 远程温度采集RS485 总线与 Modbus RTU 工程实践3.1 远程温度用 RS485 而不是 WiFi/CAN 的现场理由我在 HVAC 项目里更倾向 RS485。一是现场距离风管和水管旁的传感器跟控制柜经常隔几十米到几百米RS485 最远能跑 1200 米两芯屏蔽线很便宜二是抗干扰工业现场电机启停、变频器工作会产生大量电磁干扰RS485 是差分信号共模抑制能力比单端 UART 强很多。WiFi 在金属管道密布的环境里信号衰减严重部署 AP 的成本和维护量都不小CAN 虽然抗干扰也好但楼宇自控和 HVAC 领域里 Modbus RTU 才是事实标准上位机、DDC 控制器、组态软件都认这个协议。Modbus RTU 的好处是协议简单帧结构固定CRC 校验能覆盖全部数据错误帧基本都会被过滤掉。嵌入式端实现起来也不难一个串口加定时器就能搞定主从轮询。对温度这种变化缓慢的模拟量来说9600bps 的波特率都嫌快一帧请求加一帧响应的时间非常短一个周期轮询几十个节点毫无压力。3.2 RS485 半双工硬件电路怎么接最稳STM32F103RC 的 USART2 用来跑 RS485引脚接 PA2TX和 PA3RX外部加一颗 SP3485 或者 MAX3485 芯片转成差分信号。方向控制用另一个 GPIO 接 DE 和 RE发送时拉高接收时拉低。485 芯片的 A、B 两端之间要接 120Ω 终端电阻但只接在总线的最远两端不是每个节点都装只在首尾各装一个。A、B 线上可以加 TVS 管做浪涌防护屏蔽层选择单端接地避免多点接地形成地环路。硬件上有个很常见的坑DE 和 RE 是分开的但实际使用可以把它们短接成一个引脚控制发送前置高发送完再置低。关键是置低之前一定要确认发送移位寄存器已经清空否则最后一个字节会被截断。我习惯在串口发送完成中断里判断TC标志置位后再延时 10 微秒切回接收模式实测不会丢尾字节。3.3 主机轮询与从机应答的 Modbus 帧设计Modbus RTU 请求帧结构是从机地址 1 字节功能码 1 字节起始寄存器地址 2 字节寄存器数量 2 字节CRC16 低字节在前高字节在后。读取温度用功能码 0x03 读保持寄存器。比如主机要读取地址 1 的从机温度寄存器请求帧是01 03 00 00 00 02 C4 0B其中01是从机地址03是读保持寄存器0000是起始寄存器地址0002表示读两个寄存器C4 0B是 CRC16 校验。从机正常应答帧是01 03 04 00 0A 00 14 xxx04表示后续 4 个字节数据这里00 0A表示 1000 14表示 20按协议约定解释为温度值。我自定义的寄存器映射是地址 0x00 存温度整数部分地址 0x01 存小数部分用度 百分之一度的定点方式表示避免浮点数在 Modbus 寄存器里传输的字节序麻烦。RS485 是从机多、主机一从机不能主动发数据只能等主机来问。从机收到完整帧后核对地址是自己、CRC 正确就解析功能码并回填寄存器数据。所有从机都在同一根总线上所以任何从机空闲时都要保持接收状态绝对不能主动发任何数据否则会把总线搅乱。3.4 超时、重试与从机离线判定轮询必须考虑两个超时帧间隔超时和从机响应超时。RTU 协议规定帧内字节间隔不能超过 3.5 个字符时间超出就认为一帧结束可以用定时器在串口接收中断里判断字符间隔来实现。从机响应超时我通常设 100 毫秒主机发出请求后进入等待超过 100 毫秒没收到完整应答帧就记录该从机离线然后继续轮询下一个从机。温度数据实时性要求不高没必要为一个离线节点卡住整条总线。离线判定逻辑不能只看一次失败我设连续 3 次轮询无响应才标记离线避免瞬时干扰造成误报。恢复判定也类似连续 2 次成功响应就恢复在线。这个状态要同步到上层后续报警和本地显示都依赖它。轮询周期方面单从机请求加响应的实际耗时通常不到 50 毫秒9600bps10 个节点串下来不到 1 秒完全满足 HVAC 温度监控需求。4. 嵌入式软件架构与数据融合4.1 裸机时间片调度还是 FreeRTOS这个项目功能复杂度中等我选择了裸机时间片加状态机的方式。不是说 RTOS 不好而是当前任务只有温度采集、485 轮询、串口输出三块都是慢速任务用状态机完全不会有逻辑混乱。如果以后要叠加多个 PID 调节回路、文件系统、以太网协议栈那时候再迁移到 FreeRTOS 也来得及。裸机调度我做了三个时间片1 毫秒的定时器基准10 毫秒处理串口超时和 485 状态机推进1 秒触发一次本地温度采集和温度滤波更新。SysTick 中断里只做计数不干重活业务逻辑全部放到主循环里按标志位执行。这样做的优点是每个任务都有明确的时间边界中断占用时间极短PJ85718DM 的 1-Wire 时序不会被打乱。主循环结构大致是while (1) { if (flag_10ms) { modbus_poll_step(); } if (flag_1s) { read_local_temp(); update_filter(); check_alarm(); } uart_output_display(); }代码看起来简单但实际调试时很舒服任何一路异常都可以通过断点或者串口打印快速定位不像 RTOS 里要切换线程看栈。4.2 本地远程数据汇总与告警逻辑数据结构我定义了一个温度节点表每个节点包含节点编号、温度值、健康状态、最后更新时间。本地传感器固定编号 0远程从机节点从 1 开始编号。每次轮询结果直接更新到对应节点表项显示层和报警层统一读这个表不直接操作传感器外设。告警逻辑做的是双阈值温度上限和温度下限每项都带回差。比如设定回风温度上限 30℃一旦超过 30℃ 触发报警但温度降到 27℃ 才解除防止临界点反复抖动导致继电器频繁动作。实际 HVAC 控制里如果直接用单阈值设备会在设定值附近来回开关机很伤压缩机。这个回差参数我建议做成可配置项通过串口命令修改。4.3 提升系统可靠性的 3 个细节第一485 方向切换延时。发送完成后必须等移位寄存器完全输出完毕再切回接收模式否则最后一个字节的停止位会被打断从机会认为帧不完整。第二独立硬件看门狗。STM32 的独立看门狗IWDG在低温环境下依靠内部 RC 振荡器工作精度一般但用于防死机足够。主循环里每次状态机推进后喂狗一旦程序卡死在某个外设等待里看门狗会自动复位。调试阶段可以通过调试器暂停喂狗功能免得一断点就复位。第三串口接收用 DMA 加空闲中断。485 收数据时先用 DMA 搬到缓冲区串口空闲中断指示一帧结束然后统一在缓冲区内做解析CPU 不用一个字节一个字节地处理中断长时间跑下来负载很低也不容易丢数据。5. 常见问题与排查技巧实录5.1 传感器读回 85℃ 或 0℃ 的常见原因很多单总线数字温度传感器在上电瞬间暂存器里的默认值是 0x0550换算出来正好是 85℃。如果你读到的温度一直卡在 85℃说明传感器已经复位成功但温度转换命令可能没执行或者转换还没完成就调了读暂存器。先检查发出 0x44 转换命令后是不是延时够了 750 毫秒再看读流程前面有没有正确复位和跳过 ROM。我遇到过一种情况复位时序里应答脉冲判断写反了不识别的传感器反而被视为正常导致后续命令全部对牛弹琴。示波器挂上 DQ 脚对比手册时序图检查是最快的办法。还有一种情况是读回 0℃ 或负值异常这常常是上拉电阻悬空或者接触不良总线上的寄生电容让读时序里出现了错误的 0 电平。把线缆接头重新压接上拉电阻改小问题就能定位。5.2 RS485 乱码、丢帧、A/B 接反RS485 调试最先确认的是 A/B 是否接反。收发器输出端 A 对应非反相端B 对应反相端接反了表现为能收到数据但是全是乱码。USB 转 485 的模块上有标识现场施工线色五花八门建议用万用表量一下模块端电压确认极性。还有个隐蔽问题有的 485 芯片是自动流控的接在主机的 TX 脚不需要额外方向控制但误接了 DE 或 RE会导致发送永远使能收到一堆自己发出去的字节。丢帧方面一般优先查终端电阻。总线上如果只有一台从机主机这端和从机那端都应该各有一颗 120Ω 电阻我只在主机端装了一颗从机端没装总线反射导致最后一两个字节经常被 CRC 判错。两端都装上后连续跑 24 小时一条错误帧都没有。如果总线节点很多还要注意每个节点的 A、B 线上如果并接了保护二极管漏电流叠加可能会超出 485 芯片的共模范围这种高负载场景要选用带更高共模电压范围的收发器。5.3 温度跳变漂移与单总线冲突如果本地温度明明环境很稳定读数却上下跳个 0.5℃先看传感器探头是不是放在了风口或者柜内散热器旁边。排除环境因素后检查总线上是不是挂了多个传感器但驱动里没做 ROM 匹配。单总线多个传感器时直接发跳过 ROM 命令会让所有传感器同时响应读回的数据互相叠加结果就是乱跳。解决办法是先执行读 ROM 命令 0x33把所有传感器的 64 位序列号枚举出来存表后续每次读温度都用匹配 ROM 命令 0x55 选定具体设备。温度漂移还有一个容易被忽略的点传感器供电电压偏低。有些单总线传感器对供电有最低电压要求电压不足时转换结果会偏高甚至不稳定。尽量用 3.3V 以上供电如果 MCU 的 GPIO 是 3.3V 电平而传感器用 5V 供电数据线要考虑电平配合问题简单的办法是数据线串一颗 1.5kΩ 电阻或者用电平转换芯片。5.4 现场强电干扰引起的复位与数据错乱HVAC 现场最恼火的是变频器和电磁阀导致的电源毛刺。MCU 一复位485 从机地址回到默认值主机就找不到设备了。排查方法是观察复位原因寄存器如果发现复位源是欠压复位那就是电源跌落。对策很简单给整个控制板加一个可靠的 DC-DC 隔离电源传感器供电单独用 LDO 从主电源取电再加 TVS 和共模电感。别小看这几毛钱的器件在实际项目中能省掉大量半夜去现场排查的工夫。针对数据错乱软件层可以做最后一层保护。Modbus CRC 本来就是兜底机制但如果从机端收到非法功能码或者异常地址不要做任何响应等待主机超时重发。主机端把 CRC 错误的帧直接丢弃连续错误计数达到阈值就报警提示线路故障不要静默处理。6. 实测记录与扩展方向6.1 实验室联调数据与偏差记录我在实验室用恒温水槽做了一轮验证选了三个温度点做对比一组数据供参考。本地传感器直接接在 MCU 板上远程节点通过 30 米双绞线挂 RS485 总线轮询周期 1 秒。测试点参考水槽温度本地传感器读数远程节点读数偏差低温点5.00℃5.06℃5.12℃0.06~0.12℃常温点25.00℃24.94℃25.08℃-0.06~0.08℃高温点60.00℃59.95℃60.03℃-0.05~0.03℃偏差在传感器标称精度范围内。远程节点和本地传感器之间的微小差异主要来自线缆电阻和传感器个体一致性实际工程中可以针对每个节点做软件修正在写入寄存器前加一个偏移量或者由主站根据历史比对自动补偿。6.2 从 2 个节点扩展到 16 个节点的轮询测算有人问这套方案最多能带多少个节点。理论是 247 个实际受限于轮询周期和响应速度。以 9600bps 算一字节传输时间约 1.04 毫秒一般请求帧 8 字节应答帧 9 字节左右加上间隔和响应延时单节点一轮约 50 到 80 毫秒。如果按 80 毫秒算16 个节点一轮也就 1.28 秒对 HVAC 温度监控完全够用32 个节点约 2.5 秒仍然可以接受。要更快就把波特率拉到 19200单节点耗时可以缩短一半。节点地址分配我建议用拨码开关来设拨码开关本身便宜可靠现场维护人员拆开外壳就能改地址不需要额外工具。如果没有拨码需求从机端可以用三个 GPIO 组合成 1 到 8 的地址或者用 24C02 这类存储芯片保存地址但多一个器件就多一个失效点。6.3 低成本上位机曲线可视化嵌入式端只是完成了数据采集和转发想看温度曲线我通常把主机的串口输出格式化成 CSVtimestamp, local_temp, node1_temp, node2_temp, node_online然后接一个 USB 转 TTL 模块到电脑用 Python 脚本读串口并实时画曲线。写个几十行的脚本就够了不需要购买商业组态软件。如果项目要求正式交付再考虑对接现有的楼宇自控平台Modbus RTU 协议就是为这种对接准备的把主机从站地址设好把保持寄存器映射表交给上位机厂家对方直接用标准 Modbus 驱动就能读到全部温度点。我个人在实际操作中的体会是这套方案最大的价值不是芯片选型而是把“本地实时采集”和“远程总线汇聚”两条链路理清楚的同时留足了扩展余地。温度监控看着简单真正跑起来要面对的往往是电源质量、通信误码、现场布线这些细节问题。PJ85718DM 和 STM32F103RC 的组合性价比高资料好找很适合作为这一类项目的起步模板。最后再分享一个小技巧调试 Modbus 时先把从机地址写成 0xFF、功能码用 0x03如果不通就换 0x04 再试很多国产设备对保持寄存器和输入寄存器的实现并不完全规范这是最容易卡住人的地方。