前阵子接了个楼宇自控相关的项目,控制柜要同时盯两处温度:机柜内部靠近回风口的环境温度,以及楼顶新风管道深处的送风温度。以前遇到这种场景,我的第一反应是上热电阻加调理电路,但这次我换了思路,用 PJ85718DM 这颗数字温度传感器配合 STM32F373VC 主控,把本地和远程温度监测一起做掉了。
这篇文章主要是把这套方案的选型理由、接线方式、驱动代码和联调中踩过的坑记录下来,给准备做嵌入式温控、HVAC 监控或者其他需要“多测点、低成本、精度够用”的开发者一个参考。如果你手头也有一颗这样的数字温度传感器,想快速挂在 MCU 上跑起来,这篇文章可以直接照着抄。
1. 需求落地:一个控温任务里“本地”与“远程”到底怎么接线
1.1 “远程”不是无线,而是布点距离与数据链路的组合
先明确一个概念:标题里的“本地与远程温度”,在实际 HVAC 项目里通常不是指 Wi-Fi 或无线传输,而是指测点物理位置的远近。
我这次的项目场景很典型:控制柜在配电间,新风管道在走廊吊顶深处,两者直线距离不到二十米,但中间隔着墙体、桥架和一堆强电电缆。本地温度用来监控控制柜内部散热是否正常,防止夏天柜内过热导致主控死机;远程温度则要参与新风阀开度联动,送风温度一旦低于某个阈值,就要触发防冷风保护。
所以项目里其实有两条信息链路:一条是传感器到主控的采集链路,另一条是主控到上位机或触摸屏的通信链路。前者解决“远端测点怎么把数据传回来”,后者解决“本地测得再准,上位机怎么知道”。
1.2 两条 PJ85718DM 挂在同一条 I2C 总线上的拓扑
传感器选了 PJ85718DM,这颗芯片是 I2C 接口的数字温度传感器,典型测量范围能覆盖 -55 到 +125℃,日常 HVAC 场景里的 0 到 70℃ 区间完全够用。它内部直接完成温度到数字量的转换,主控只要通过 I2C 读寄存器就行,不需要额外搭运放和 ADC。
两个测点的接线方式我一开始就定了:本地传感器和控制板在同一个小空间内,直接四根线连;远程传感器放在风管探头里,通过屏蔽双绞线拉过去。两颗传感器挂在同一条 I2C 总线上,通过地址引脚区分。
我买的是支持两到三个可配置地址的版本,实际配置成两个地址即可:
| 传感器 | 地址引脚状态 | 挂载位置 | 用途 |
|---|---|---|---|
| PJ85718DM 第一颗 | A0 接地 | 控制柜内 | 本地温度 |
| PJ85718DM 第二颗 | A0 接 VCC | 新风管道探头 | 远程温度 |
这里有个细节:地址引脚不能浮空,宁可多花一秒钟把引脚焊到确定的电平,也不要偷懒不接。浮空状态下地址不稳定,总线偶尔枚举不到设备,排查起来非常痛苦。
1.3 供电和上拉的工程设计
电源方面,系统里有 24V 开关电源,先降到 3.3V 给主控和传感器供电。我建议给传感器供电单独加一个 100Ω 磁珠和一两个 0.1μF 陶瓷电容,把主控数字电路的高频噪声隔开。HVAC 控制柜里常有变频风机和压缩机启动,电源上的一点点毛刺都可能让传感器输出抖动。
I2C 上拉电阻我选了 4.7kΩ,默认 400kbps 模式下工作正常。这个阻值是常规选择,但如果你把传感器拉到十米以外,就要重新算一下,后面第 4 节我会专门聊这件事。
还要注意,I2C 总线上的 SDA、SCL 不是随便两根杜邦线就能乱飞的地方。HVAC 柜内空间拥挤,信号线尽量远离继电器、交流接触器的线圈端子,至少保证 10mm 以上的距离,或者直接走屏蔽双绞线,屏蔽层单端接地。
2. 选型逻辑:数字传感器配带片内放大器的 MCU,省掉的不只是运放
2.1 直接数字输出,把模拟链路的坑都填平了
很早以前做温度采集,我习惯用 NTC 热敏电阻,因为它便宜到几乎不要钱。但 NTC 的问题是:电阻-温度曲线高度非线性,而且个体一致性差。同一个型号的一批货,在 25℃ 时阻值能差出百分之几,这意味着每个通道都要单独校准,否则精度根本没法看。
后来换成 PT100,精度确实上去了,但配套的调理电路也上来了:恒流源、仪表放大器、冷端补偿、外部 ADC,PCB 面积和 BOM 成本翻着倍涨。而且这些模拟环节里任何一处不干净,最终误差都会叠加到温度值上。
PJ85718DM 这种数字传感器把整个模拟链路都省掉了。芯片内部自带温度感测单元和 ADC,数字量通过 I2C 直接送进 MCU。我要做的只是初始化总线、读寄存器、换算单位,再处理一下滤波和校准。对于 HVAC 这种“精度在 ±0.5℃ 左右就能接受”的场景,数字传感器就是效率和成本的平衡点。
2.2 STM32F373VC 真正加分的是集成 PGA 和高精度 ADC
有人会问:既然 PJ85718DM 已经自带 ADC 了,那主控选一颗普通 STM32F1 不就行了,为什么偏要选带 F373 后缀的?
我的理由是:要给后续扩展留余地。STM32F373VC 这颗主控,除了 Cortex-M4F 内核、72MHz 主频、256KB Flash 这些常规资源之外,比较特别的地方是内部集成了可编程增益放大器(PGA)和 16 位 Σ-Δ ADC,还有多个独立的 I2C 外设。
在实际项目里,这套组合的价值非常明显。我这次虽然只用了数字传感器,但后期客户可能临时要求增加一路 PT1000 测水管温度,或者接一个 0-10V 输出的室外温湿度变送器。如果主控本身带 PGA 和高精度 ADC,我就直接在片内做信号调理,不用重新改版加运放和外部 ADC 芯片。散热上,F373 的工作温度范围也覆盖工业级区间,放在控制柜里不用额外加风扇。
当然,不会所有项目都需要用到片内 PGA,这只是一个选型上的“保底选项”。真正常用的反而是 F373 的多路 I2C 外设:我把本地传感器挂在 I2C1 上,远程那条挂在使用 GPIO 模拟的慢速 I2C2 上,两条总线互不干扰,比共用一条总线省心得多。
2.3 对比传统方案,成本和调试时间差在哪里
我不喜欢空谈选型,更习惯用一张表格做决策:
| 方案 | 传感器成本 | 外围电路 | 校准成本 | 精度表现 | 适用场景 |
|---|---|---|---|---|---|
| NTC + 运放 + MCU ADC | 低 | 中等 | 每通道校准 | 一般,受温漂影响大 | 消费类、对精度要求不高的温度监测 |
| PT100 + 仪表放大器 + 外部 ADC | 高 | 高 | 需要精密电阻匹配 | 高 | 实验室、工控高精度场景 |
| PJ85718DM + STM32F373VC | 低 | 极低 | 只需两点校准即可 | 满足 HVAC 需求 | 暖通、嵌入式多测点监测 |
实际做下来,数字传感器方案的 BOM 里基本就是传感器、两个上拉电阻、磁珠和电容,外加主控本身就有的 I2C 外设。调试时间也明显缩短,我不需要拿示波器去盯放大器的输出漂移,只要确认总线波形干净、寄存器读数稳定就行。
3. 驱动与读数:寄存器配置、单次转换和温度值的符号处理
3.1 初始化代码:先把总线和寄存器摸清楚
PJ85718DM 的寄存器映射不同批次可能略有差异,我建议拿到芯片先做两件事:读数据手册确认设备地址和寄存器地址,然后用一个空工程把“能读到设备 ACK”作为第一目标。
下面是我基于常见映射写的初始化代码,具体寄存器数值以你手头芯片的手册为准:
#define PJ85718DM_ADDR_LOCAL 0x4C // 本地传感器地址 #define PJ85718DM_ADDR_REMOTE 0x4D // 远程传感器地址 #define REG_TEMP_H 0x00 #define REG_TEMP_L 0x01 #define REG_CONFIG 0x04 void bsp_temp_sensor_init(void) { // I2C1 400kHz,地址模式 7bit HAL_I2C_InitWithConfig(&hi2c1, 400000, I2C_ADDRESSINGMODE_7BIT); // 典型配置:测量分辨率最高、单次触发模式 uint8_t cfg = 0x80; // 具体值请参考数据手册 CONFIG 寄存器 HAL_I2C_Mem_Write(&hi2c1, PJ85718DM_ADDR_LOCAL, REG_CONFIG, 1, &cfg, 1, 100); HAL_I2C_Mem_Write(&hi2c1, PJ85718DM_ADDR_REMOTE, REG_CONFIG, 1, &cfg, 1, 100); }很多初学者会卡在 I2C 设备地址的“8 位”和“7 位”换算上。HAL 库里的地址参数一般填 7 位地址,但实际发送时总线上的首字节是地址左移一位后再拼读写位。如果驱动库要求填 7 位,你填 0x4C;如果要求填 8 位写地址,那就是 0x98。别把两个混了,否则设备永远 ACK 不了。
3.2 读温度和负数处理:不只是一个乘 0.0625 的事
温度寄存器是 16 位宽度,高字节在前。标准做法是把高字节左移八位后和低字节合并,再判断符号。分辨率常见的是 12 位,即 LSB 对应的温度值为 0.0625℃。
下面这段读取函数我比较常用:
static float read_temp(uint16_t dev_addr) { uint8_t buf[2]; int16_t raw; if (HAL_I2C_Mem_Read(&hi2c1, dev_addr, REG_TEMP_H, 1, buf, 2, 100) != HAL_OK) return -100.0f; // 返回一个明显无效值 raw = (int16_t)((buf[0] << 8) | buf[1]); // 12 位数据,高 4 位是符号扩展位 raw = (raw >> 4); // 右移对齐 if (raw & 0x0800) // 12 位符号位 raw |= 0xF000; return (float)raw * 0.0625f; }负数温度在 HVAC 里可能不常见,但新风混风段在严寒地区偶尔也会到零下,符号扩展这一步不能省。如果直接把两个字节拼成 int16_t 再乘 0.0625,在 12 位有效数据的芯片上会出现负温度变成巨大正数的问题。我见过不止一次有人在这里栽跟头。
3.3 单次转换模式的选择:总线占用低,数据更可控
这颗传感器支持连续转换和单次转换两种模式。我最终用了单次转换模式,原因是:在主循环里按固定周期读温度,同一时刻总线上只有我一个主设备在发起读取,逻辑非常清晰。
单次转换流程是:写 CONFIG 寄存器触发一次转换,等待转换完成,然后读温度寄存器。转换完成的判断有两种,一种是查询状态位,另一种是简单延时等待,我习惯用延时等待,典型转换时间在几十毫秒量级,具体看手册。主循环每 500ms 触发一次读取,功耗和实时性都兼顾了。
如果你的项目要求低功耗,比如电池供电的无线温度标签,单次转换模式更是唯一选择:平时让传感器休眠,定时唤醒读数,读完再睡,平均电流可以压得很低。
3.4 软件滤波:让空调机组的风机噪声不再干扰读数
刚调通的时候,我发现本地温度在 25.2℃ 和 25.5℃ 之间小幅跳动,虽然幅度不大,但传到上位机上曲线很难看。原因不难猜:柜内开关电源纹波、压缩机启动瞬间的电流冲击,都可能在传感器电源引脚上造成毫秒级的毛刺。
此时硬件上再加电容的意义不大了,我在软件里做了两层滤波:
- 三取中值滤波:连续采三个值,取中位数,用来扛单点毛刺;
- 滑动平均滤波:中值滤波结果再进一个长度为 5 的环形缓冲区算平均,用来平滑噪声。
#define MED_BUFFER_SIZE 3 #define AVG_BUFFER_SIZE 5 static float temp_med_buffer[MED_BUFFER_SIZE]; static float temp_avg_buffer[AVG_BUFFER_SIZE]; static uint8_t med_idx = 0, avg_idx = 0; static float temp_filtered = 0.0f; float temp_filter(float new_raw) { temp_med_buffer[med_idx] = new_raw; med_idx = (med_idx + 1) % MED_BUFFER_SIZE; // 简单排序取中值,数组只有 3 个元素,直接硬排 float a = temp_med_buffer[0], b = temp_med_buffer[1], c = temp_med_buffer[2]; if (a > b) { float tmp = a; a = b; b = tmp; } if (b > c) { float tmp = b; b = c; c = tmp; } if (a > b) { float tmp = a; a = b; b = tmp; } temp_avg_buffer[avg_idx] = b; avg_idx = (avg_idx + 1) % AVG_BUFFER_SIZE; float sum = 0.0f; for (int i = 0; i < AVG_BUFFER_SIZE; i++) sum += temp_avg_buffer[i]; temp_filtered = sum / AVG_BUFFER_SIZE; return temp_filtered; }这段代码运行在 72MHz 主频上,几乎不占 CPU 时间,但效果非常明显。加上滤波之后,温度曲线稳定在 ±0.1℃ 以内,触摸屏上不再出现“忽高忽低”的观感。
3.5 两个测点的调度思路
两个传感器共用一个 I2C 外设时,最忌讳在主中断和主循环里同时调用 I2C 读取函数,这会造成总线冲突。我的做法很简单:在 while(1) 主循环里设置一个 500ms 的软件定时器,定时到之后先调用本地温度读取和滤波,再调用远程温度读取和滤波,两个传感器轮询式读取,互不排斥。
不要在 I2C 读取时用 HAL_I2C_Mem_Read 的默认 HLE 阻塞太久,一定要传超时时间参数。I2C 设备如果物理连接虚焊,阻塞式读取会把主控死死卡在那里。超时时间我给的是 100ms,读不到就返回一个无效温度值,上位机那边看到这个值就报传感器故障,而不是显示一个吓人的温度。
4. 远程通道:短距离 I2C 延长与长距离 Modbus RTU 子站
4.1 十米以内的低成本延长:屏蔽双绞线加慢速模式
远程传感器离主控十米以内的话,可以直接把 I2C 总线延长。但 I2C 本身是为板内通信设计的,布线电容一大,信号上升沿就变慢,通信频率就得降。
我第一次把传感器装在五米远的塑料探头里,用普通网线跑 400kHz,结果读数时好时坏。拿示波器一看,SCL 高电平上升沿已经变成一条缓坡,逻辑门限附近反复跳变。
解决办法有两个方向:
- 把总线速率从 400kHz 降成 100kHz 甚至 50kHz,同时把上拉电阻从 4.7kΩ 换成 1kΩ,增强上拉能力;
- 在远端传感器座附近加一颗 I2C 总线缓冲器,把长线电容和本地总线隔离开。
实测下来,五米到十米以内的距离,用 100kHz 加 1kΩ 上拉就能稳定工作,没必要多花钱加缓冲器。如果你要拉二十米以上,我更建议直接用下面的 RS485 子站方案。
4.2 远距离和工业现场:给 PJ85718DM 搭配 RS485 从站
二十米、三十米的场景,I2C 延长再怎么做都不划算。我在现场用的是更稳妥的方案:远端放一个带 RS485 收发器的小采集节点,节点上挂 PJ85718DM,采集到的温度通过 Modbus RTU 协议回传给 STM32F373VC。
这样做的稳定性比 I2C 延长高一个量级:RS485 是差分信号,抗共模干扰能力强,能跑几百米;双绞线哪怕贴着电缆桥架走,也不太会受变频器噪声影响。
STM32F373VC 在这套结构里扮演的角色有两个:一是本地主控,负责读控制柜内的温度传感器;二是 Modbus 主站,通过 RS485 轮询远端节点。如果你愿意,也可以让 F373 当 Modbus 从站,把两个温度值同时上传给楼宇上位机,看现场需求。
4.3 Modbus RTU 报文设计:温度值用 0.1℃ 整数传输
和上位机对接的时候,浮点数直接丢在 Modbus 报文里容易出问题,CRC 计算、字节序、小数位处理都很麻烦。我习惯把温度放大十倍,用整数传输,比如 25.4℃ 就传输 254,上位机收到后自己除十。
保持寄存器规划如下:
| 寄存器地址 | 内容 | 单位 | 示例值 |
|---|---|---|---|
| 0x0000 | 本地温度 | 0.1℃ | 252 表示 25.2℃ |
| 0x0001 | 远程温度 | 0.1℃ | 270 表示 27.0℃ |
标准读保持寄存器指令是这样的:
- 主站请求:
01 03 00 00 00 02 C4 0B - 从站响应:
01 03 04 00 FC 01 0E B8 A1
其中00 FC是 252,01 0E是 270,最后两个字节是 CRC16。这套报文用市面通用的 Modbus 调试工具就能直接解析,省得自己写蹩脚的上位机协议。
4.4 RS485 侧的防护:隔离和 TVS 不能省
工业现场的 RS485 总线会伴生地电位差和浪涌,尤其是暖通项目里变频器多,总线上的共模电压可能高达几十伏。我的建议是:
- RS485 收发器电源用隔离 DC-DC,信号侧用数字隔离器隔开;
- A/B 线之间加 TVS 管,串联 PTC 自恢复保险丝,避免高压直接打坏收发器;
- 屏蔽层单端接地,不要在两端都接地,防止地环路电流。
这些措施看起来多花了几十块钱,但能避免现场调试时烧一片接口芯片的窘境。我一个同事就因为在柜内直接接了双绞线没做隔离,变频器一启动,上位机通信就掉线,最终排查了半天才发现是共模干扰。
5. 联调复盘:波形异常、凝露浸泡和两段式校准
5.1 排查链路:先怀疑程序,然后发现是线缆电容的问题
项目调试到第二周,远程温度出现了一个很诡异的故障:每天早上开机正常,运行一小时后通信中断,重新上电又恢复。
按常规流程我开始排查:
- 先用示波器抓 I2C 波形,发现波形正常时通信正常,波形畸变时通信失败,先确认是硬件问题;
- 再用替换法,把远程传感器线缆换成短跳线,故障消失,锁定是长线缆引入的问题;
- 最后用万用表/电容测试仪确认,二十米网线的 SDA 与 SCL 之间的分布电容太大,加上线缆被桥架里的强电感应出噪声,导致信号恶化。
最终的修复方案不是换更好的线,而是改架构:远程传感器不再直接用 I2C 硬扯二十多米,而是换成 RS485 子站。如果早知道现场布线条件这么差,我一开始就该上子站方案。
这个复盘告诉我们:用 I2C 拉长线这件事,在实验桌上很美好,到了工地现场就是灾难现场。遇到超过十米的测点需求,直接上 RS485,别犹豫。
5.2 远程探头凝露,读数漂移的真实教训
还有一个坑发生在安装阶段:远程探头做成了不锈钢螺纹管,直接拧在风管盲端,探头和冷风接触,运行几天后读数缓慢漂移,最终比真实值低了近 8℃。
拆开检查发现,探头内部已经有冷凝水珠,PJ85718DM 的 PCB 引脚都泡在水里了。数字传感器本身防护等级并不高,栅格焊盘附近有水膜时,漏电流会明显改变内部测量结果。
解决方法是:把传感器 PCB 整体用环氧树脂灌封在探头内部,只露出供电和 I2C 引线;探头安装方向保持“插头朝上、感温头朝下”,让冷凝水自然流走,不往 PCB 方向积。后来我又给探头加了一层透气膜,维持大气压力平衡,防止密封腔体内外压差过大导致水汽渗入。
5.3 两段式校准:零点和五十度点
虽然 PJ85718DM 出厂有校准,但你要想读出“可信赖”的绝对温度,还是建议做一次两点校准。方法是:
- 零点:把传感器探头放进冰水混合物里,等读数稳定后记录偏差;
- 高温点:用恒温水槽或精密温控箱,设定 50℃ 或 80℃,记录偏差;
- 在固件里存两组 offset 和 gain,对换算后的温度做线性修正。
校准公式很简单:修正温度 = 原始温度 × gain + offset。两颗传感器分别存一份校准参数,上位机对应显示修正后的数值。
我手头的两颗传感器,冰点和 50℃ 点的偏差都在 ±0.3℃ 以内,做完修正后整体一致性很好,跨传感器温差控制在 ±0.15℃ 以内,完全满足 HVAC 温控要求。
5.4 常见问题速查表
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| I2C 总线 ACK 失败 | 地址引脚浮空、地址换算错误、线缆过长 | 固定地址电平;核对 7 位/8 位地址;改用慢速模式 |
| 温度读数跳变 10℃ 以上 | 探头进水/凝露、电源毛刺、接插件氧化 | 探头灌封;传感器电源加磁珠;重新插拔并检查端子 |
| 通信偶尔中断 | 线缆电容过大、总线上有强干扰 | 降低速率、减小上拉电阻;改用 RS485 子站 |
| 上位机读到无效温度 | 超时时间设置不当、传感器掉线 | 超时返回无效标志;增加看门狗恢复机制 |
| 负温度显示为 65535 附近 | 符号扩展忘了处理 | 按 12 位数据做符号扩展后再乘 LSB |
以前做多测点温控,我总喜欢把所有传感器都拉到一个主控下面,节省成本,结果布线复杂,调试周期也被拉得很长。现在我的习惯变了:短距离、少测点,I2C 直接连;长距离、多测点,果断拆成总线节点。PJ85718DM 配上 STM32F373VC,非常适合“本地一个点、远端一个点”这种中小规模方案,精度够、成本低、扩展性好。
最后再分享一个实用的细节:PJ85718DM 这类数字传感器在第一次上电时,如果 I2C 波形不稳,偶尔会进入异常状态,表现为一直 NACK。我在初始化里固定加了一段“总线恢复”流程,把 SCL 连续拉出九个时钟脉冲再重新枚举设备。这个动作成本几乎为零,但能让产品在现场复杂环境下少很多莫名其妙的问题。