嵌入式工程师做温度监测,十有八九是从一颗NTC热敏电阻起步的:ADC 读电压、查表、算温度,一套流程下来也就勉强能用。但如果你碰到的是暖通空调(HVAC)这类场景,事情就没这么简单了——光知道控制板自己有没有发烧不够,你还要知道风机盘管回风口、空调送风管、冷水管表面的温度,甚至要把几百米外的室外机温度拉回来一起看。这次的项目就是围绕 PJ85718DM 这颗“本地+远程”双通道数字温度传感器,配合 STM32L433RC 这颗超低功耗 MCU,把板级温度和远端测温点的数据纳入同一套监测体系里。对做空调集控、风机盘管、机房环境监控、嵌入式过热保护这几类产品的工程师来说,这套组合可以直接参考落地。
1. 项目背景与设计思路拆解
1.1 为什么是“本地+远程”双点位监测
很多嵌入式产品只测单点温度,因为单点能回答“现在热不热”这个最基础的问题。但 HVAC 系统里的温度是一个“过程量”:主机启停靠的是回风温度和设定值的差值,除霜靠的是室外换热器管温,风机的调速又和送风温度强相关。这个时候,单点监测就不够了,你需要同时看到两个位置的温度变化曲线,才能知道系统是不是在一个健康的工作区间里运行。
本地温度这一路,通常是放在主控板上,测的是 PCB 板温、机柜内部环境温度或者功率器件附近的温升。它的主要用途是保护:板子温度太高说明散热出了问题,或者负载过流,必须提前告警甚至断电,而不是等元器件烧了才去查。远程温度这一路,才是 HVAC 控制逻辑真正的数据来源:回风温度、送风温度、水管表面温度、室外机盘管温度,都可以通过一根延长线把温度传感器拉到对应位置去测。把这四路数据合在一起看,既不耽误保护,也不耽误控制,这才是“本地+远程”真实存在的意义。
这个双点位设计还有一个隐含收益。本地温度可以作为远程温度的“参考系”:如果远端读数突然偏得离谱,而本地读数正常,那基本可以判断是传感器线缆或者连接器出了问题,而不是整个采集链路都坏了。排查的时候能省下大量的时间。
1.2 芯片选型背后的考量
PJ85718DM 这类器件,本质是把过去需要分立方案才能实现的测温过程整进了一颗芯片里:内部集成本地温度传感器,同时提供外接二极管/P N 结的远端测温通道,最后通过 I2C 兼容接口把两路温度直接以数字量送出去。相比用 NTC 热敏电阻,它的优势非常明显:不需要每颗电阻单独做校准表,不需要做非线性补偿,更不需要在 ADC 前端堆运放和精密电阻。你只需要把传感器焊上去,配置好 I2C,读寄存器,出来的就是有效温度值。
主控这边选择 STM32L433RC 的理由也很直白。这颗 MCU 是 Cortex-M4 内核,主频最高 80MHz,带 FPU,片上 Flash 256KB、SRAM 64KB,放在这种监测项目里完全够用。关键是它属于低功耗系列,Stop 模式下电流可以降到很低,HVAC 设备经常要常年通电运行,低功耗意味着整机发热小、寿命长,也更容易过安规和能效测试。芯片自带两个 I2C 外设、多个 UART 和丰富定时器资源,后续要扩展 Modbus 通信、接继电器控制、挂显示面板,都不用换平台。
两颗芯片的分工很清楚:PJ85718DM 负责把物理世界的温度变成干净的二进制数据,STM32L433RC 负责把所有数据汇总成控制逻辑,对外提供 Modbus、开关量或者串口输出。传感器不占用主控的模拟资源,主控也不用做温度换算的脏活,各管各的,接口清爽。
1.3 这套方案适合谁
我把这个项目整理出来,主要给三类人看。第一类是嵌入式软件工程师,尤其是做单片机应用层开发的,可以直接抄 I2C 驱动和数据处理部分的代码。第二类是暖通/楼宇自控方向的硬件工程师,可能对控制逻辑很熟,但对数字传感器怎么接、怎么读寄存器没那么熟悉,这篇文章可以帮他们把两块拼起来。第三类是做设备运维的人,虽然不需要写代码,但理解了这个系统的数据流转方式之后,现场排查故障点的效率会高很多。
无论你是站在哪一侧,需要记住的底层逻辑只有一条:温度只是一个物理量,真正有价值的是怎么让两个测温点协作起来,服务于系统控制与设备保护。
2. 硬件接口设计与关键注意事项
2.1 传感器的引脚接口与外围电路
PJ85718DM 的引脚定义我按这类双通道数字温度传感器最常见的排布来讲:电源引脚 VDD、地 GND、I2C 数据线 SDA、I2C 时钟线 SCL、告警输出脚 ALERT(部分型号叫 OS 或 INT)、远端二极管正极 D+、远端二极管负极 D-,以及用于选择 I2C 地址的地址引脚 ADD。具体到某一颗料,管脚顺序和功能脚名称可能略有差异,动手画原理图之前一定要对着你手头的规格书逐脚确认。
外围电路非常精简,关键是几个元件的取值别错。VDD 用 3.3V 供电即可,电源入口处放一个 100nF 陶瓷电容,靠近芯片电源脚位置再放一个 10μF 钽电容或者多层陶瓷电容用于滤低频纹波。SDA 和 SCL 必须接上拉电阻,典型值 4.7kΩ,如果总线速率提到 400kHz 或者线上挂的设备多,可以换成 2.2kΩ。上拉电阻接到 VDD,而不是接到主控的 IO 电平,这是很多人容易漏掉的地方——传感器芯片的 I2C 侧电平以它的 VDD 为参考,上拉到主控那边的 3.3V 虽然多数时候也能工作,但信号摆幅不对称会埋下隐患。
我整理了一张最简单的连线参考表,适配多数情况:
| 传感器引脚 | 连接到 | 说明 |
|---|---|---|
| VDD | 3.3V | 并联 100nF + 10μF 去耦电容 |
| GND | 系统地 | 与主控共地,尽量单点连接 |
| SDA | MCU I2C1_SDA | 经 4.7kΩ 上拉到 VDD |
| SCL | MCU I2C1_SCL | 经 4.7kΩ 上拉到 VDD |
| ALERT | MCU 任意 EXTI 引脚 | 可配置为开漏输出 |
| D+ | 远端二极管阳极 | 远离高频走线 |
| D- | 远端二极管阴极 | 与 D+ 构成差分回路 |
| ADD | GND 或 VCC | 决定 I2C 地址 |
提示:如果手头型号的 ALERT 引脚是开漏输出,主控侧需要额外接上拉电阻并配置为输入模式,否则读不到有效的拉低电平。
2.2 主控端的硬件准备与资源分配
STM32L433RC 我建议直接用标准最小系统板开始,把精力先集中在功能验证上,而不是一开始就画整板。最小系统板的基本配置网上有很多现成案例:3.3V 供电、8MHz HSE 晶体、32.768kHz LSE 晶体预留、SWD 调试口预留、BOOT 引脚通过跳帽拉高或拉低。调试串口 UART1 建议优先引出,后续所有打印输出都要靠它,后面调试远程传感器读数稳定性的过程中,你会非常依赖这个串口。
I2C 外设我选了 I2C1,对应的引脚通常可以映射到 PB6/PB7 或者 PB8/PB9,具体以你用的封装和 CubeMX 里的引脚复用量为准。选 I2C1 而不是 I2C2、I2C3 的原因比较实际:I2C1 的引脚映射最灵活,同时和调试串口、PWM 输出的引脚冲突概率最低,布线压力小。ALERT 引脚我接到一个支持外部中断的普通 GPIO 上,后续做报警阈值处理时,可以直接用下降沿触发中断,省掉轮询的开销。
电源方面有一个很容易踩的坑:传感器的 VDD 尽量不要直接从主控的 LDO 输出端拉线,中间串一个 10Ω 电阻,后面再放去耦电容,形成一个小型 π 型滤波。这样开关电源的纹波、主控 IO 翻转带来的地弹噪声都不容易串进传感器的模拟前端,远程温度读数的抖动会小很多。如果板上有开关电源,这个 10Ω 电阻和电容的值要适当加大一些,把电源到传感器之间的阻抗隔离做得更彻底。
2.3 远程测温点布局与 PCB 设计要点
远程测温通道是这个项目里最容易出问题的地方。PJ85718DM 的远程测温原理是利用二极管/三极管 PN 结电压随温度变化的特性,芯片内部通过恒流源激励远端二极管,再测量其正向压降的变化来反推温度。这意味着远端感温元件本身不是传感器芯片,而是一个 PN 结,工程上最常见的是用廉价的三极管把集电极和基极短接当成二极管来用,或者直接用工规的 SOT-23 封装二极管。
理解了这个原理,很多问题就顺着解开了。因为远端信号是毫伏级的模拟量,线上任何噪声都会直接叠加到读数上,所以从 D+/D- 到远端二极管的走线必须按“弱信号差分对”来对待。实际工程里我强烈建议用双绞线连接,如果没有条件,也要保证两根线紧紧并行走线,并远离继电器、变压器、开关电源这些强干扰源。远端二极管的引线长度尽量控制在 1 米以内,超过这个长度就需要考虑加屏蔽层和滤波电容,否则噪声很难压得住。
PCB 上还有一个细节值得强调:D+ 和 D- 之间可以放一个小容值电容做差分滤波,一般取值 1nF 到 2.2nF,不要超过这个范围。电容太大虽然滤波效果好,但会明显改变二极管的工作点,导致测温偏差变大。如果你发现读数整体偏了一个固定值,先检查是不是这个滤波电容取值超标了。
如果应用场景是空调风管或者冷水管表面,还必须考虑防潮问题。风管里常年有凝结水,传感器封装如果没有防护措施,很容易因为结露导致 PN 结漏电,读数瞬间变成几十度。这种情况下,传感器和连接器区域要做三防漆涂覆,或者选用自带防水处理的封装,线缆入口用橡胶圈压紧。这是 HVAC 现场最容易翻车的点,提前处理比事后返工省太多事。
3. 固件实现与代码落地
3.1 驱动层设计:I2C 通信基础
固件我基于 STM32CubeMX 生成工程,用 HAL 库实现,开发效率最高,代码也好维护。CubeMX 里把 I2C1 配置为标准模式(100kHz),7 位地址模式,时钟源选内部或外部晶振都可以,这里对时序要求不算苛刻。首次建议先用标准模式,把通信链路调通之后,再考虑是否切换到快速模式(400kHz),减少不必要的变量干扰。
传感器芯片的 I2C 读写,本质就两类操作:读寄存器、写寄存器。我封装了两个最基础的函数,后面所有逻辑都基于它们:
/* 读多个寄存器:先写寄存器地址,再连续读数据 */ uint8_t pj_read_regs(uint8_t reg, uint8_t *buf, uint8_t len) { uint8_t ret = HAL_I2C_Mem_Read(&hi2c1, (PJ85718_I2C_ADDR << 1) | 0x01, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 100); if (ret != HAL_OK) { /* 记录错误并返回失败状态,上层决定是否重试 */ return 0; } return 1; } /* 写单个寄存器 */ uint8_t pj_write_reg(uint8_t reg, uint8_t val) { uint8_t ret = HAL_I2C_Mem_Write(&hi2c1, (PJ85718_I2C_ADDR << 1) | 0x00, reg, I2C_MEMADD_SIZE_8BIT, &val, 1, 100); return ret == HAL_OK; }HAL 的Mem_Read/Mem_Write函数内部已经帮你处理了“先发寄存器地址,再切换读写模式”的时序,对大多数人来说够用了。如果你特别在意实时性或者想省一点 ROM,也可以自己写裸机 I2C 状态机,但对这个项目来说收益不大,HAL 库足够稳。
通信链路里最容易忽略的是“总线空闲检测”。上电之后芯片可能处于不定状态,主控侧如果监测到 SDA 一直被拉低,说明总线被某个从机卡住了。这时候最有效的恢复手段是手动把 SCL 翻转 9 个时钟周期,强制从机释放 SDA,然后重新初始化总线。这个技巧在我实际调试中救过很多次,建议写成一个i2c_bus_recovery()函数放在上电初始化阶段。
3.2 数据读取与温度值换算
以这一类双通道数字温度传感器最常见的寄存器模型为例,芯片内部会提供独立的本地温度寄存器和远程温度寄存器,每个通道通常占两个字节:高字节存整数部分,低字节的高几位存小数部分。具体地址偏移量不同厂家的片子不一样,但换算逻辑高度相似。
我习惯一次性把两路温度的高低位都读回来,然后统一换算。典型的 12 位温度数据,低位字节的高 4 位有效,LSB 对应的温度步进通常是 0.0625°C,也就是 1/16 度。换算代码如下:
typedef struct { float local_temp; float remote_temp; } temp_data_t; temp_data_t update_temperature(void) { uint8_t raw[4]; temp_data_t result = {0, 0}; int16_t local_raw, remote_raw; if (!pj_read_regs(PJ_REG_LOCAL_TEMP_HI, raw, 4)) { return result; /* 读取失败,返回 0 值由上层处理 */ } /* 12 位数据:高字节整字节,低字节只有高 4 位有效 */ local_raw = (int16_t)((raw[0] << 8) | (raw[1] & 0xF0)); remote_raw = (int16_t)((raw[2] << 8) | (raw[3] & 0xF0)); /* 符号扩展:负温度时高字节最高位为 1 */ local_raw >>= 4; remote_raw >>= 4; result.local_temp = (float)local_raw * 0.0625f; result.remote_temp = (float)remote_raw * 0.0625f; return result; }符号扩展这行代码是整个换算里最容易写错的地方。int16_t类型本身是有符号的,但要先把数据取出来再右移,如果把(raw[0] << 8) | (raw[1] & 0xF0)的结果直接赋给无符号变量,负温度就全变成很大的正数了。我一开始也栽在这上面,调试时读出来零下温度显示成 65472,排查了十几分钟才意识到是类型问题。
3.3 告警阈值与低功耗策略
温度监测不能只靠主控轮询,很多场景要求温度越界时毫秒级响应,比如电机过热保护。PJ85718DM 这类芯片普遍支持可编程的上下限报警寄存器,当实测温度超过上限或低于下限时,ALERT 引脚会被拉低。主控这边把 ALERT 接到一个外部中断输入引脚上,配置为下降沿触发,然后在中断回调函数里读取具体是哪一路报警、是上限还是下限,再决定是只记录日志还是触发继电器动作。
这样做有一个明显的好处:在正常工况下,主控完全可以进入低功耗模式,不需要每秒钟去读一次温度。传感器自己会持续监测,如果温度一直正常,ALERT 引脚不会动作,主控就安心待在 Stop 模式里,用 RTC 定时唤醒做周期性的数据上报和状态刷新。只有当温度真的越界了,ALERT 才会像闹钟一样把主控拽起来处理。
唤醒上报的周期可以按场景调整:风机制冷场景 5 秒唤醒一次足够,机房环境监控 30 秒一次也行。唤醒之后先读温度,再决定是继续睡还是进入完整工作模式。这个策略对常年通电的设备尤其友好,功耗差一个量级,发热和器件寿命也跟着改善。当然,如果产品本身不敏感功耗,直接 100ms 轮询也没问题,取舍完全看你的应用场景。
3.4 与上位机或控制面板的对接
单片机把温度读出来只是第一步,HVAC 系统里这些数据最终要给上位机或者空调控制器用。最常见的方式是走串口转 RS-485 跑 Modbus RTU 协议。STM32L433RC 本身至少有 3 个 UART,用一个 UART 接 RS-485 收发器,把本地温度和远程温度映射到 Modbus 保持寄存器里,上位机组态软件就能直接读。
这里有一个工程惯例值得记住:不要直接上传浮点型温度,底层协议里浮点传输容易踩字节序的坑,不同厂家的上位机解析标准也不同。最稳妥的做法是把温度乘以 10,转成整数上传,也就是精度做到 0.1°C。上位机拿到的 235 就是 23.5°C,简单直接,兼容性最好。换算好之后用uint16_t存,温度范围 -400 到 +1250 完全够用。
如果你想在设备本地看温度,也可以加一块小尺寸 LCD 或者 OLED 显示屏,I2C 接口的屏能直接挂在同一条 I2C 总线上,注意地址不要和传感器冲突就行。显示逻辑没什么特别的,把update_temperature()的结果格式化打印到屏幕即可。这一路不是项目的核心,但样机调试时有一块屏会方便很多,不用每次都对着串口工具看。
4. 常见问题与排查经验
4.1 I2C 通信失败怎么办
I2C 通信失败是我调试这类传感器时遇到最多的问题,而且失败的方式五花八门。最好排查的是上拉电阻没焊或者焊错位置,表现是第一种读寄存器就返回HAL_TIMEOUT。用示波器看 SDA/SCL,两条线全是平的,根本没有波形,基本就是上拉问题。稍微隐蔽一点的是地址配置错误,ADD 引脚如果悬空,芯片内部可能按上拉处理,实际地址和你代码里写的不一样,表现是设备能发出 ACK 但主控始终读不到数据。
出现总线卡死时别急着复位主控,先试总线恢复。我之前说的 9 个时钟周期翻转技巧,配合 SDA 释放检测,绝大多数卡死都能救回来。如果恢复后仍然不正常,把传感器和主控之间的连线断开,单独用逻辑分析仪看主控是不是正常发出了 START 信号和地址帧。逐段缩小范围,比盲改代码有效得多。
另外注意上电时序。传感器芯片的 VDD 和 I2C 上拉的参考电压必须是同一个电源,如果主控先上电、传感器后上电,传感器侧的上拉电阻会把 VDD 反灌给芯片,虽然电流不大,但可能让芯片进入未知状态。设计上让传感器和主控同步供电,或者给传感器增加一个掉电复位电路,能减少很多诡异的偶发故障。
4.2 远程温度读数跳变
远程温度读数像心电图一样跳动,是这个项目里最经典的故障特征。一次能跳好几度,从 25°C 闪到 -40°C 再跳回 30°C,明显不是真实温度变化,肯定是模拟链路被干扰了。第一排查点是远端二极管的接线极性。PN 结是有方向的,D+ 接阳极、D- 接阴极,接反了读数要么是很大的数要么完全乱跳。我见过的很多“传感器坏了”的案例,最后发现就是线序接反。
第二排查点是连接线太长又没有滤波。如果必须走超过 1 米的线,建议用双绞屏蔽线,屏蔽层在传感器端单点接地,不要在两端都接地形成地环路。同时 D+/D- 之间加 1nF 级别的差分电容,把高频噪声滤掉。线缆尽量远离继电器触点、开关电源的功率回路,这些强干扰源靠布置距离就能解决大部分问题。
软件层面也可以做最后的兜底。读取温度时加一个滑动平均滤波,取最近 5 次采样做算术平均,能明显抑制偶发噪声尖峰。但要注意,滤波会带来响应滞后,如果系统对温度变化响应速度有要求,比如除霜控制,滤波窗口要缩短到 3 次,并且把滤波放在报警判断之前,放在控制计算之后。
4.3 本地温度读数偏差过大
本地温度如果比实际环境温度高好几度,先别急着怀疑芯片精度。这类传感器内部测温点通常在封装基板上,和芯片自身的功耗、周围热源都有关系。检查传感器是否靠近发热元件,比如 LDO、MOSFET、大电流走线,距离小于 5mm 就会明显受到影响。把传感器挪远一点,或者中间加隔热槽,效果立竿见影。
如果排除布局原因,剩下的是系统误差。你可以在恒温环境或者用冰水混合点、沸水点做两点校准,然后给本地通道配一个软件偏移量。大部分这类芯片都提供偏移校准寄存器,往里面写一个固定的补偿值,读写出来的温度就自动修正了。这个偏移值的计算很简单:实测温度减标准温度,就是你的补偿量。现场校准的时候注意让传感器充分热平衡,至少静置 10 分钟再读数,否则数据没有意义。
还有一个小细节很多人忽略:传感器自身的自热效应。芯片工作时内部电流会产生热量,虽然很小,但在低风速的机柜里仍然可能让读数偏高一点点。如果你对绝对精度要求很高,可以让传感器降低转换频率,或者采用平均模式减少工作占空比,自热效应会明显降低。
4.4 典型故障速查表
| 故障现象 | 可能原因 | 处理方法 |
|---|---|---|
| 首次通信超时 | 上拉电阻缺失、地址配置错、接线错 | 检查上拉、遍历地址、用逻辑分析仪看波形 |
| SDA 一直被拉低 | 从机进入死锁状态 | SCL 翻转 9 时钟恢复总线后重新初始化 |
| 远程读数剧烈跳动 | 线序接反、线缆过长、受干扰 | 确认极性、改双绞屏蔽线、加 1nF 差分电容 |
| 远程读数整体偏移几度 | 滤波电容过大、PN 结型号不匹配 | 电容降到 2.2nF 以内、选用数据手册推荐二极管 |
| 本地读数偏高 | 贴近发热源、自热效应 | 挪开热源、降低转换频率、加软件偏移 |
| 读数偶发为 0 或极大值 | 读取时序出错、符号扩展写错 | 检查 12 位数据拼接和 int16 类型转换 |
| 上电一段时间后掉线 | 电源纹波大、供电时序异常 | 传感器电源增加 π 型滤波,优化上电顺序 |
| 风管环境读数漂移 | 结露导致 PN 结漏电 | 灌封防护、三防漆涂覆、改善密封 |
5. 实测体会与后续扩展
这套方案我在模拟风柜的控制盒上跑过一版,第一版样机调通 I2C 用了不到半天,真正花时间的是把远端测温读数的稳定性做扎实。最开始我把远端二极管用普通跳线拉出去测,读数跳得没法看;后来换成双绞屏蔽线、接了 1nF 差分滤波电容,再把屏蔽层可靠接地,读数立刻稳了。整个过程下来,我最大的教训是:模拟信号链路的布线规范和数字逻辑一样重要,甚至更重要,因为数字逻辑错了肉眼能看到,模拟信号被污染了你只会觉得“芯片是不是坏了”。
如果后续要继续扩展,我建议从三个方向入手。一是做多路组网,在同一条 I2C 总线上用不同地址挂多个 PJ85718DM,每个传感器对应风机盘管的一个分区,实现真正的“一控多测”。二是叠加湿度采集,用一颗数字温湿度传感器配合现有的温度数据,HVAC 场景里露点计算离不开湿度,有了湿度就能做防凝露控制。三是加数据记录,把温度变化曲线存入 SD 卡或者定时上传到平台,长期积累的数据对压缩机能效分析和设备预防性维护非常有价值,这也是从“能测温度”走向“会用温度数据”的关键一步。