做嵌入式和 HVAC 项目的朋友,几乎都会遇到温度采集这件事。一类是设备本地的温度,比如控制板安装环境温度、压缩机表面温度;另一类是远程的温度,比如风道末端、送回风口、房间内部这些布线距离比较远的位置。前阵子我配合某设备厂商做温控节点,核心就是标题里的两个器件:PJ85718DM 数字温度传感器,加一颗 STM32F205RB 控制芯片,把本地温度直接读出来,远程温度再通过 RS485/Modbus 汇总上报。这套方案不算复杂,但把采集、滤波、通信、校准整个链路走通,里面值得记录的细节非常多。
这篇文章会把整个项目从头到尾拆开讲。不管你是在做 HVAC 温控器、嵌入式数据采集终端,还是想学怎么用 I2C 温度传感器加 Modbus 做多点测温,都能直接照着搭。我会把选型原因、硬件接线、驱动代码、滤波处理、远程通信、实测数据、坑点排查全部写出来,尽量让没做过的人也能一次落地。
1. 先从需求说起:嵌入式和 HVAC 到底要测哪里的温度
1.1 本地温度和远程温度分别解决什么问题
很多刚接触 HVAC 项目的人会有个误区:温度采集不就是把传感器插到设备里,读个数值吗?实际上本地温度和远程温度是两类完全不同的问题。
本地温度通常指控制器自身周围、压缩机壳体、换热器管壁、配电柜内部这些位置。它距离 MCU 很近,导线长度通常不超过几十厘米,用 I2C 接口的数字传感器非常合适。好处是传感器直接输出数字量,不需要额外搭放大电路或做冷端补偿,抗干扰能力也强。这类温度主要用来做设备保护,比如压缩机排气温度超过设定值就降载甚至停机,或者根据管壁温度判断除霜时机。
远程温度则是安装在风道、回风口、房间温控面板、室外补偿温度探头这些需要长距离布线的位置。这些地方离主控板可能隔着十几米甚至上百米,I2C 在这种距离下根本跑不起来,信号完整性会非常差。所以远程测温的常规做法是:在每个远端位置放一个小节点板,节点板上有 MCU 和传感器,采集到温度后通过 RS485 总线传回主控。RS485 是差分信号,抗干扰能力强,标准应用下能到 1200 米,这正好覆盖 HVAC 项目的典型布线距离。
所以标题里“本地与远程温度”不是一句废话,它代表了两条完全不同的信号链路。一个项目里同时处理好这两条链路,才能做出真正能用的温控系统。
1.2 这个项目适合谁参考
我建议下面几类人重点看这篇文章:
- 做 HVAC 控制器、楼宇自控、工业温控箱的嵌入式工程师,这套方案可以直接落地。
- 正在学习 STM32 和 I2C 传感器驱动的开发者,可以拿这个项目当完整练手案例。
- 想了解 Modbus RTU 从机怎么实现、怎么和触摸屏或组态软件对接的人,第 4 章有代码拆解。
- 经常被温度数据跳变、通信不稳定折磨的现场调试人员,第 5 章和第 6 章的实测与排查记录应该能帮上忙。
我自己当年做类似项目时,最头疼的不是读温度,而是读出来的温度不可信、传不出去、或者现场跑几天就漂了。这些问题大多不是芯片本身的问题,而是电路细节、滤波策略和通信时序没处理好。下面这些内容,就是我踩过坑之后总结出来的。
2. 选型与整体方案:为什么是 PJ85718DM + STM32F205RB
2.1 传感器选型对比,为什么不用 NTC 和 PT100
温度传感器这块,市面上最常见的是 NTC 热敏电阻、PT100 铂电阻、以及各类数字温度传感器。我帮客户做方案时,通常先问三个问题:测量范围多少、精度要求多少、现场有没有强干扰。回答完这三个问题,再决定用哪种。
NTC 成本最低,很多家电温控器都在用。但 NTC 是非线性的,而且同一批次之间的阻值离散性也不小。要做好更换不标定的产品,基本必须用电桥加编程校准表,B 值一变整个曲线就偏了。我并不是否定 NTC,在温度范围窄、精度要求不高、单点定标能接受的产品里它仍然是最经济的选择。但如果我们做的是对精度和一致性有要求的 HVAC 节点,我不想把大量生产校准成本转移到温控逻辑里。
PT100 精度高,能到 ±0.1℃ 甚至更高,但需要恒流源或电桥电路、仪表放大器、ADC,还要考虑导线电阻补偿,硬件成本一点都不低。PT100 适合做实验室级或者工业级标准温度计,用在分布式测温节点上,每个节点的物料成本和 PCB 面积都会失控。
PJ85718DM 这种数字温度传感器就适合在这两者之间找到一个平衡点。它本身集成了温度敏感元件和 ADC,直接通过 I2C 输出数字量。单芯片方案,不需要外围放大,精度一般能做到 ±0.5℃ 以内,分辨率可以达到 0.0078125℃。对 HVAC 环境监测来说,这个精度绰绰有余,风道和房间温度的控制精度通常在 ±1℃ 到 ±2℃,传感器本身的误差远小于控制回路的动作阈值。
另外要说明的是,PJ85718DM 这个型号可能不像 NTC 那样人人熟悉,我拿到实物后的理解是:它是一颗 I2C 接口、16 位温度寄存器输出的数字传感器,工作电压 3.3V 或 5V 都能兼容,默认 7 位地址是 0x48 那一档(实际以你手里的数据手册为准)。如果你拿到的批次寄存器映射稍有差异,不影响整体架构,只需要把驱动里读寄存器的地址改一下就行。
2.2 STM32F205RB 扮演什么角色
STM32F205RB 是 Cortex-M3 内核,主频最高 120MHz,Flash 是 1MB,RAM 是 128KB。对于温度采集这种任务来说,这个配置其实是富余的。那为什么还要选它?因为我在这个项目里不只是读一个温度,还要同时管理 RS485 通信、看门狗、状态指示灯、本地存储,甚至以后可能加 Ethernet 或 USB 转串口升级。
F205 这颗芯片有几个点很对我的胃口。第一,它有多个硬件 I2C 和 USART 外设,I2C1 接传感器,USART2 接 RS485 收发器,外设之间不冲突,代码也不需要在同一个中断里来回切换。第二,它支持 DMA,USART 的收发可以用 DMA 搬运,配合 Modbus RTU 的帧收发,可以明显降低 CPU 占用。第三,它有足够的 IO 和定时器资源,后期哪怕要加两路 PWM 控制风机转速,也不至于重新选型。
有人可能会问,用一颗更低端的芯片是不是也行?比如 F103 系列。答案是看项目定位。F103 当然能跑同样的逻辑,但 F205 在 I2C 外设的稳定性、SRAM 容量、DMA 通道数量上都要好一些。如果你做的是批量 HVAC 控制器,追求极致成本,F103 也完全撑得住;但如果你做的是高端楼宇自控节点,需要更充裕的资源跑后续功能,F205 是更稳妥的选择。我这次用的正是 F205RB。
2.3 系统布局:本地采集、远程上报的整体链路
整个系统的结构可以理解为三层。
第一层是本地采集层。STM32F205RB 通过 I2C 总线直接读 PJ85718DM 的温度数据。这里 I2C 线的长度建议控制在 30cm 以内,传感器尽量靠近主控板。如果传感器模块需要延长,用屏蔽线并在传感器端加 0.1uF 去耦电容,否则在变频器附近很容易读到毛刺。
第二层是远程节点接入层。每个远端测温点放一块小从机板,板上有 STM32 最小系统、PJ85718DM、RS485 收发器。从机板负责采集本地温度,并在收到主站请求时把温度数据返回。这里的“本地”是相对于从机板而言的,实际上它也是远程节点。
第三层是集中管理上报层。主控板作为 Modbus 主站,通过 RS485 总线轮询各个从机节点。主站把各点温度汇总后,可以传给触摸屏、组态软件,或者再通过以太网上传到楼宇管理平台。这样客户在值班室就能看到每个风道口、每个房间的温度变化。
说句实在话,这种“从机采集 + 主站轮询”的结构是 HVAC 行业最通用的方案,因为 Modbus RTU 几乎被所有触摸屏和组态软件原生支持,不需要写私有协议,也不用装额外驱动。项目验收时,客户用一根 USB 转 485 线就能看到数据,说服力很强。
3. 硬件设计:从传感器到 MCU 再到远程总线的完整链路
3.1 传感器接线与电源设计
PJ85718DM 的引脚不多,通常包括 VDD、GND、SDA、SCL,有些封装还会引出一根 AD0 地址选择脚。VDD 直接接 3.3V,GND 就近回地,SDA 和 SCL 各接一颗上拉电阻到 3.3V。
上拉电阻的取值很有讲究。我刚开始做的时候用 10kΩ 上拉,结果 I2C 时钟一提高到 400kHz 就经常传输出错,波形上升沿太慢。后来换成 4.7kΩ,问题立刻消失。如果你的板子比较紧凑、走线较短,1.8kΩ 也可以,但不要低于 1kΩ,否则灌电流偏大,对芯片 IO 不太友好。
电源方面要特别注意,传感器尽量不要直接从 MCU 的 VDD 引脚上再拉一根线供电。MCU 引脚电流变化比较大,尤其是驱动继电器或者指示灯时,电源噪声会耦合到温度数据里。我的习惯是用一个小功率 LDO 单独给传感器供电,或者至少在传感器 VDD 引脚旁边并联一颗 1uF 陶瓷电容和一颗 0.1uF 高频去耦电容,两者配合使用。多次实测下来,这样做能把温度数据的随机跳变减少一半以上。
另外,如果传感器模块是成品模块,板上一般自带滤波电容和上拉电阻。自己做板子时,PCB 布线要保证传感器底下不要铺大块地铜,避免杂散电容影响 SCL 波形。传感器尽量靠近 MCU 引脚,让 I2C 走线保持短、直、左右有地线包裹。
3.2 RS485 远程通信电路要点
RS485 收发器我用的是 SP3485 这类 3.3V 供电的芯片,直接和 STM32F205RB 的 USART 引脚连接,不需要电平转换。收发器的 A、B 引脚接到接线端子,对外走双绞线。
这里有几个细节特别容易翻车,我逐个说一下。
第一,A、B 之间要并接 120Ω 终端电阻。这个电阻放在总线最远端,作用是吸收反射信号。如果总线上只有两块板而且距离很短,不接也能跑,但距离一超过几十米或节点一多,不接就会偶发帧错误。终端电阻的计算很简单:双绞线的特征阻抗通常在 120Ω 左右,终端电阻等于特征阻抗时反射最小。
第二,收发器的 DE 和 RE 引脚要连到一起,由 MCU 的一个 GPIO 控制。发送数据前拉高,发送完再拉低切回接收。切换时机如果不对,最后几个字节会出现截断。我一般发完最后一个字节后,延时 1 个字符时间再拉低,或者干脆用发送中断里加个延时。你也可以用硬件自动收发电路,但我觉得用 GPIO 控制更直观、排查问题更方便。
第三,A、B 线上要加 TVS 管,防止现场长线感应雷击或电机启停造成的浪涌损坏收发器。HVAC 现场经常有变频器、风机、水泵,这些设备启停瞬间会产生很强的干扰,这个问题我在第 6 章还会单独说。
RS485 总线布线建议用屏蔽双绞线,屏蔽层单端接地。如果是多节点,总线要手拉手连接,绝对不能搞成星形,星形会产生大量反射。这是个老生常谈的问题,但现场施工时几乎每回都要强调一遍,因为很多施工队习惯就近接线。
3.3 样机焊接与布局注意事项
如果是自己打样调试,焊接传感器之前先确认引脚定义,最好用万用表量一下 VDD 和 GND,防止反接把芯片烧掉。PJ85718DM 这类贴片芯片很小,焊接时焊台温度控制在 350℃ 左右,时间要短。我见过有人焊了太久,温度传感器被烙铁烤偏了零点,常温下读数比正常高了 2 度,后来怎么软件校准都不稳定,最后换了一颗芯片才好。
MCU 核心板的选择上,如果不想自己画板,直接买带 STM32F205RB 的最小系统板也行,但一定要确认 3.3V 电源干净。有些开发板的 LDO 纹波比较大,在做温度采集时表现出来就是读数最后一位不停跳动,换一块供电稳定的板子立刻就好。
板子上如果有继电器、蜂鸣器这些感性负载,它们的电源要和传感器供电分开走线,地线尽量采用单点接地。这样做能减少地弹噪声对 ADC 和 I2C 的影响。虽然是“经验之谈”,但在现场调试时往往就是这些细节决定成败。
4. 软件实现:驱动、滤波与远程通信代码拆解
4.1 I2C 驱动读取温度
软件上第一步是把传感器读出来。我用 STM32 的 HAL 库,打开 I2C1,主机模式,时钟设成 100kHz 先跑通,后面再提速到 400kHz。读温度寄存器的逻辑很简单:先发送寄存器地址,再读两个字节,把两个字节拼成 16 位整数,最后按分辨率系数转成温度值。
#define PJ85718DM_ADDR 0x48 #define REG_TEMP 0x00 int16_t read_temp_raw(void) { uint8_t reg = REG_TEMP; uint8_t buf[2]; if (HAL_I2C_Master_Transmit(&hi2c1, PJ85718DM_ADDR, ®, 1, 100) != HAL_OK) { return INT16_MIN; // 返回无效值,交给上层处理 } if (HAL_I2C_Master_Receive(&hi2c1, PJ85718DM_ADDR, buf, 2, 100) != HAL_OK) { return INT16_MIN; } return (int16_t)((buf[0] << 8) | buf[1]); } float read_temp_celsius(void) { int16_t raw = read_temp_raw(); if (raw == INT16_MIN) { return -999.0f; // 用明显异常值表示读取失败 } // 这里以 16 位分辨率 0.0078125℃/LSB 为例 return raw * 0.0078125f; }这段代码有几个地方要解释一下。第一,I2C 地址为什么是 0x48 左移位之后的格式?HAL 库里的地址参数是 8 位地址,也就是包含读写位的格式,7 位地址 0x48 需要左移一位变成 0x90,才能和 HAL 库的读写位拼接正确。我上面的写法会有人直接用宏,不同人的写法可能差一位,这点特别容易坑。你写代码时用数据手册里的地址,配合 HAL 库时记得左移,或者直接查你手上例程的定义。
第二,读到的 raw 值是带符号的,因为宽温度范围下负温会出现最高位为 1 的情况。如果我把它定义成 uint16_t,零下温度转换出来会变成很大的正数,后台显示一团乱麻。所以用 int16_t 接收,再转 float。
第三,为什么温度转换系数是 0.0078125?因为分辨率是 1/128 = 0.0078125,也就是 16 位温度寄存器的最低位表示 0.0078125℃。如果你的芯片数据手册写明精度是 12 位、13 位或 14 位,转换公式要按实际有效位调整,不能盲目套。
读取周期方面,HVAC 温度变化慢,我把读温度放在 1s 定时中断里触发。如果需要更快响应,可以缩短到 200ms,但对大多数风道、房间温度来说没必要,反而会让滤波更难做。
4.2 温度数据处理:均值滤波与防毛刺
直接读出来的温度值往往不能直接用,因为现场有变频器、开关电源这些干扰源,原始数据偶尔会冒出一个明显偏大的尖峰。我在项目里用了两重处理:第一重是滑动平均滤波,用来平滑随机噪声;第二重是限幅判断,用来剔除明显毛刺。
滑动平均的代码可以直接看下面的实现:
#define FILTER_SIZE 8 static float temp_hist[FILTER_SIZE]; static uint8_t temp_idx = 0; static uint8_t temp_count = 0; float temp_sliding_avg(float new_sample) { float sum = 0.0f; uint8_t i; temp_hist[temp_idx] = new_sample; temp_idx = (temp_idx + 1) % FILTER_SIZE; if (temp_count < FILTER_SIZE) { temp_count++; } for (i = 0; i < temp_count; i++) { sum += temp_hist[i]; } return sum / temp_count; }用 8 个样本做平均,1s 读一次的话就是 8s 平滑窗口。对 HVAC 来说这个响应速度完全足够,而且曲线会非常漂亮。如果你要做压缩机保护这种对快速响应有要求的场景,可以把窗口缩到 4 个样本,或者干脆不做平均,直接用原始值,只是再叠加一道限幅滤波。
限幅滤波的核心思想是“上次合法值和这次采样值之差超过一个阈值,就认为是毛刺”。比如正常温度变化在 0.5℃/s,那设置变化阈值 2℃ 已经足够宽松。一旦检测到突变,直接丢弃这次采样,用上次值代替。
float temp_anti_glitch(float new_sample) { static float last_valid = 25.0f; const float max_step = 2.0f; if (fabsf(new_sample - last_valid) > max_step) { return last_valid; } last_valid = new_sample; return new_sample; }实际调试时,我先把毛刺日志全部打出来,看是周期性的还是偶发的。如果是周期性尖峰,多半是周围有开关动作和 I2C 时钟重叠;如果是偶发,可能是电源抖动。命令先抓波形再改参数,“拍脑袋”加滤波参数只会掩盖问题,不会根治问题。
4.3 Modbus RTU 从机通信实现
远程节点上跑的协议,我直接用 Modbus RTU。它是 Modbus 协议在串行链路上的标准实现,帧格式是地址 + 功能码 + 数据 + CRC16。每个从机节点有一个唯一的地址,主站发请求帧,从机地址匹配后回响应帧。
我用功能码 03 读保持寄存器来开放温度数据。保持寄存器规划如下:
| 寄存器地址 | 内容 | 说明 |
|---|---|---|
| 0x0000 | 本地温度整数部分 | 例如 23℃ |
| 0x0001 | 本地温度小数部分 | 例如 0.450℃ |
| 0x0002 | 设备状态 | bit0 传感器在线,bit1 通信正常 |
| 0x0003 | 固件版本 | 例如 0x0101 表示 v1.01 |
温度用整数加小数两部分存,是为了避免浮点数在 Modbus 寄存器里表示时的字节序争议。上位机拿到两个寄存器后,拼成 23.450℃。这种方法最直观,也最容易和第三方组态软件对接。
Modbus 从机核心处理逻辑可以写成这样:
uint16_t crc16_modbus(uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; uint16_t i; while (len--) { crc ^= *data++; for (i = 0; i < 8; i++) { if (crc & 0x0001) { crc = (crc >> 1) ^ 0xA001; } else { crc >>= 1; } } } return crc; } void modbus_rtu_poll(void) { uint8_t rxbuf[32]; uint16_t rxlen = 0; uint16_t crc_rev, crc_cal; // 这里假设 USART 中断已经按帧接收到 rxbuf,并解析出 rxlen if (rxlen < 8) { return; } // 检查从机地址 if (rxbuf[0] != SLAVE_ADDR) { return; } // 校验 CRC,注意 Modbus RTU 的 CRC 低字节在前 crc_rev = (uint16_t)(rxbuf[rxlen - 1] << 8) | rxbuf[rxlen - 2]; crc_cal = crc16_modbus(rxbuf, rxlen - 2); if (crc_rev != crc_cal) { return; } // 功能码 03,读保持寄存器 if (rxbuf[1] == 0x03) { uint16_t start_reg = (rxbuf[2] << 8) | rxbuf[3]; uint16_t reg_count = (rxbuf[4] << 8) | rxbuf[5]; uint8_t resp[64]; uint8_t pos = 0; uint16_t crc_send; resp[pos++] = SLAVE_ADDR; resp[pos++] = 0x03; resp[pos++] = (uint8_t)(reg_count * 2); for (uint16_t i = 0; i < reg_count; i++) { uint16_t reg_val = get_holding_register(start_reg + i); resp[pos++] = (uint8_t)(reg_val >> 8); resp[pos++] = (uint8_t)(reg_val & 0xFF); } crc_send = crc16_modbus(resp, pos); resp[pos++] = (uint8_t)(crc_send & 0xFF); resp[pos++] = (uint8_t)(crc_send >> 8); // 发送响应 usart_send_bytes(resp, pos); } }CRC16 的实现网上有很多版本,我每次都是拿一个已知帧去验证,比如请求帧01 03 00 00 00 02 C4 0B,如果算出来的 CRC 等于 C4 0B,说明算法正确。这个习惯非常重要,因为 CRC 写错会导致所有从机都不响应,而排查 CRC 问题非常费时间。
从机地址、波特率这些参数,我习惯用配置项放在程序开头,方便现场更改。波特率固定用 9600 或者 19200。HVAC 项目对速度要求不高,9600 反而抗干扰更好,因为同样一帧数据占的时间更长,抗瞬时干扰能力更强。
5. 实测过程与数据表现
5.1 本地测温精度测试
硬件焊接完、驱动跑通后,要做系统的温度准确性验证。我把节点板和一颗经过计量的标准铂电阻温度计放在同一个恒温环境里,分别测了三个温度点:冰水混合物 0℃、室温 25℃、恒温槽 50℃。
实测结果大概是这样:
| 标准温度(℃) | PJ85718DM 读数(℃) | 偏差(℃) |
|---|---|---|
| 0.00 | 0.25 | +0.25 |
| 25.00 | 24.80 | -0.20 |
| 50.00 | 50.15 | +0.15 |
单颗芯片的偏差在 ±0.25℃ 左右,满足 HVAC 环境监测的 ±1℃ 要求。如果你碰到偏差比较固定且大头在同一方向的,可以先做软件校准。校准方法很简单,就是两点校正:冰水混合物是天然的 0℃ 标准器,再把传感器放到一个稳定的温水槽里,用高精度温度计记录实际温度,然后算出增益系数和偏移量。
公式就是标准的线性修正:
修正值 = 原始读数 * 增益 + 偏移先测到两个点的原始读数和标准温度,解二元一次方程组就能算出增益和偏移。校准参数存到 STM32 的 Flash 里,每次上电读取,这样传感器一致性就能拉平。
5.2 远程读取验证
远程通信这块,我用 USART 转 USB 线把主站和从机连起来,先用 PC 上的串口工具直接发 Modbus 帧测试。给从机 1 发读取帧:
请求:01 03 00 00 00 02 C4 0B
从机响应:01 03 04 00 17 00 0F (CRC)
解析一下数据:寄存器 0x0000 是 0x0017,十进制 23,表示 23℃;寄存器 0x0001 是 0x000F,十进制 15,表示 0.015℃?这里要看你小数部分是存多少位。如果约定小数部分存的是千分位,那 0x000F 就是 0.015℃,不太对,应该等于 0.450℃。
这里我特意提醒一下:两个寄存器拼接时,必须确认高位在前还是低位在前,以及小数位数的约定。我在项目里习惯用“整数部分 + 千分位小数部分”,比如 23.450℃ 就存成寄存器 0x0000 = 23,寄存器 0x0001 = 450。这样既不会丢精度,上位机拼接也简单。测试时如果发现温度差得离谱,先查是不是这里拼接错了,而不是怀疑协议写错。
5.3 现场安装位置与热耦合技巧
HVAC 项目里测温点位选在哪,对控制效果的影响比传感器精度还大。我的体会是“测得到”不等于“测得准”。比如回风温度传感器如果离送回风口太近,测到的其实是混合后的温度,而不是房间典型温度;风道温度传感器如果贴在外壁上,测量的其实是管道壁温度,跟空气温度差好几度。
安装时一般要把传感器的感温部分尽量伸进气流中间,或者紧贴管壁并加导热硅脂。如果传感器探头装在铝制套管里,套管和传感器之间一定要填充导热材料,空气层的导热系数很低,会导致响应变慢、读数滞后。
另一个容易忽略的是太阳辐射。室外补偿温度探头如果被阳光直射,读数会偏高好几度,必须加遮阳罩。我见过一个项目,室外探头装在屋面,中午比实际温度高 5℃,导致系统不停地调节,最后排查了半天才发现是太阳直射的问题。
6. 常见问题与排查技巧实录
6.1 读不到温度或温度跳变
症状是 I2C 读回的数值要么一直是 0xFF,要么值明显乱跳。排查顺序我建议按电流从外到内走。
先用示波器或万用表量传感器 VDD 电压,确认是 3.3V 而不是 2.8V 之类。接着量 SCL 和 SDA 的波形,看有没有上下拉电阻。如果没有上拉,I2C 总线根本拉不高,读取必然失败。
如果波形正常但读值乱跳,多半是电源噪声。在传感器 VDD 和 GND 之间焊一个 10uF 电解电容和一个 0.1uF 陶瓷电容,跳变问题通常能消除大半。还要看一下你的 I2C 时钟是不是在传感器允许的范围内。有些传感器最高只支持 400kHz,你把它挂到 1MHz 就觉得“反正都是 I2C 应该能跑”,这是不行的。
6.2 远程通信不稳定
RS485 通信不稳定是我在 HVAC 现场遇到最多的问题。典型表现是主站偶尔收不到响应,或者收到的数据 CRC 校验失败。
第一步检查 A、B 两根线是不是接反了。接反了通常完全不通,但如果收发器内置偏置电阻,有时也能通,只是信号余量很差,距离一长就周期性丢包。这种情况我用万用表量 A、B 间的电压,正常空闲时在 0.2V 到 0.5V 左右,如果接近 0V 或者负值,基本判定接反或者缺少终端电阻。
第二步检查终端电阻。挂两个节点以上,且距离超过 50m,总线两端必须各并一个 120Ω 电阻。注意是“两端”,不是每个节点都接。如果每个节点都接了 120Ω,总线上并联电阻会太小,收发器负载过重,信号幅度被拉低,照样丢包。
第三步是检查接地。RS485 总线最好用屏蔽双绞线,屏蔽层在主机侧单点接地。如果现场噪声特别强,比如旁边有变频器,可以考虑在 A、B 之间加 TVS 管,同时串联两个 10Ω 电阻限制电流尖峰。
6.3 校准方法与系统联调建议
校准出来的参数要能覆盖传感器本身的分散性。生产线批量校准时,不能只校单点,因为单点校准只能修正偏移,没法修正斜率。两点校准时,第一点尽量靠近实际工作范围的下限,第二点靠近上限,这样全量程内误差最小。
系统联调时,我建议先做“最小系统调试”:一块主控板、一块从机板、一个传感器,把从 I2C 读到最终 RS485 返回的全链路跑通,再逐步加节点。一次就把 10 个节点全挂上去,出了问题很难定位。
另外,给每块从机板刷写地址时,最好用一个独立的小工具。地址重复是 Modbus 通信中非常隐蔽的问题,两个相同地址的从机同时响应,主站收到的数据就是乱的,而且 CRC 校验时好时坏,排查起来极其痛苦。我现在的习惯是在板子出厂前写好地址标签,并在软件里做一次地址冲突检测,每次上电时从机在总线上收到比自己地址低的所有节点都无响应后才允许进入工作状态。这个逻辑虽然简单,但能省掉很多现场问题。
还有个细节:不要忽视看门狗。HVAC 现场偶尔会有非常强的电源跌落或电磁干扰,导致 MCU 跑飞。没有独立看门狗的话,从机可能一直死在那里,主站怎么叫都不应。我将独立看门狗超时设成 3 秒,正常工作状态下不停喂狗,一旦死机就自动复位。这个功能看似不起眼,但整个项目在客户现场运行半年后,你就会知道它有多重要。
说到这,我再分享一点自己的体会:这类测温项目,真正体现功夫的地方往往不在芯片有多高级,而在于把底层细节弄扎实。I2C 上拉、滤波窗口、CRC 校验、终端电阻、安装位置,每一处看起来都是小事,但每一处都可能在现场放大成故障。先保证数据采集可信,再谈通信稳定,最后做精度修正,按这个顺序去做,项目的成功率会高很多。
如果你也正在做类似的嵌入式或 HVAC 温度监测方案,建议第一版先按我这套思路搭起来,跑通之后再去优化成本和尺寸。后面你还可以在这个基础上扩展更多功能,比如增加两路 PWM 控制风机、加入在线校准、对接以太网网关等。等这些点都补齐了,它就不仅仅是个温度采集节点,而是一个完整的 HVAC 边缘控制器了。