入行十多年,我做过的最常见的设备之一,就是用 PJ85718DM 这种双通道温度芯片搭配 STM32F303RE,同时监测本地机箱温度和远程风道温度,应用场景基本都落在 HVAC 控制柜和嵌入式设备热管理里。去年在调一套 HVAC 控制器样机时,客户提了一个很具体的要求:既要测控制盒内部空气温度,又要实时监测装在风道深处的风温,两路都要稳定、可重复,还不能占太多单片机算力。当时我第一反应是两颗 NTC 加 ADC,可一算远距离引线、分压非线性和批量标定成本,果断放弃。最终定下来的方案,是用一颗 PJ85718DM 双通道数字温度芯片,配合 STM32F303RE 用 I2C 读取本地和远程温度,再通过 RS485 把数据送进上位机。下面把选型、原理、电路、固件到调试踩坑的整个过程记下来,给正在做嵌入式测温或 HVAC 监控的同行一份能照抄的参考。
1. 项目思路与方案选型拆解
1.1 两路测温到底解决什么问题
HVAC 机组里最常见的需求有两类,一类是本地温度,一类是远程温度。
本地温度指控制箱内部的空气温度。这个点直接反映 PCB 附近的热环境,关系到电源模块、电解电容、主控芯片的寿命。以前有人在箱内随便挂一个传感器,安装位置差一点,读数就差很多,尤其是靠近发热器件和靠近通风口的位置,温差能到五六度。
远程温度指风道、回风口、冷媒管路或者电机表面的温度。这类位置离主板远,而且环境可能潮湿、有凝露、有振动,不适合把一块带 MCU 的电路板放过去。我见过不少工程队图省事,远程点直接拉两根长线接 NTC,结果每天中午阳光晒到风管时读数就跳。原因不复杂:长线本身就是天线,NTC 是高阻器件,哪怕有一点耦合噪声都会被 ADC 采进去。
用一颗芯片同时做本地和远程,逻辑上比两套独立方案干净得多。
- 两个通道共用一个 ADC、一个参考源、一个电源,通道间一致性由厂家出厂保证,不用自己做匹配。
- 远程传感器只是一颗几毛钱的贴片三极管(PN 结),可以随便塞进狭窄的管道缝隙。
- 远端信号是低压二极管压差信号,不是小信号电阻分压,抗干扰能力根本不在一个量级。
1.2 主流测温方案对比
我在选型时把常见方案都摆出来比了一遍,列成一张表看得很清楚。
| 方案 | 测温点形态 | 精度潜力 | 线长容忍度 | 成本 | 适合场景 |
|---|---|---|---|---|---|
| NTC 热敏电阻 + MCU ADC | 需分压电阻,长线抗干扰差 | ±1~2°C(标定后) | 短,1m 以内 | 最低 | 板内或近距离点温 |
| PT100/PT1000 + 调理电路 | 需桥路或恒流源,电路复杂 | ±0.1~0.5°C | 中,需三线/四线制 | 较高 | 工业过程高精度测量 |
| 热电偶 + 冷端补偿 | 冷端补偿麻烦 | ±0.5~1°C | 很长 | 中 | 高温、超低温场景 |
| DS18B20 单总线 | 每个点一颗芯片 | ±0.5°C | 中,时序要求严格 | 中 | 多点数字测温 |
| PJ85718DM 这类本地+远端二极管传感器 | 远端仅一颗三极管 | 本地 ±1°C / 远端 ±1~2°C | 中等,加滤波可更长 | 低到中 | 嵌入式热管理与 HVAC |
最后选最后一类,原因很实际:HVAC 控制器的 PCB 面积有限,我没地方放第二个传感器模块;两个通道必须在同一时间基准上做比较,本地和远程用同一个芯片读,时序天然对齐。而且这颗芯片的本地量和远程量可以同时进同一个转换周期,后续做温差控制时不至于引入时间差。
1.3 系统结构总览
这套系统的数据流大概是这样的:风道、管道等远端测温点是一颗贴片三极管的 PN 结,通过两根线连回主板上的 PJ85718DM;芯片通过 I2C 把本地和远程温度交给 STM32F303RE;MCU 处理后通过 UART/RS485 把结果发给 HVAC 主控或者中控屏。ALERT 引脚额外接一条中断线,温度越限时可以绕过主控直接触发保护动作。
风道/管道远端测温点(贴片三极管 PN 结) │ D+/D- ┌─────────┴────────────┐ │ PJ85718DM │ │ SCL/SDA ←→ I2C │ │ ALERT ←→ EXTI │ └─────────┬────────────┘ │ STM32F303RE(Cortex-M4F, 72MHz) │ UART/RS485 上位机 / HVAC 主控我特意没把设备接到 TCP/IP 网络上,因为这类控制器大多部署在配电箱、风道井这些位置,现场几乎没有稳定的有线网络,RS485 在暖通行业里仍然是最皮实的通信方式。这个结构还有个好处:每一层都能单独替换和单独调试,先用串口直接看温度,再决定要不要上完整协议栈。
2. PJ85718DM 这颗芯片的测温原理
2.1 本地通道:带隙基准在干脏活
芯片内部有一个带隙基准源,本地测温靠的就是它。晶体管的 Vbe 大约以 -2mV/°C 的速度随温度下降,而两个不同电流下的 ΔVbe 又呈正温度系数上升。把这两部分按比例叠加,就能得到温度几乎不随 T 变化的基准电压,同时也能把 PTAT 电压反推成温度值。
这块测的是芯片自己的结温,也就是本地温度,量程大概在 -40°C 到 +125°C,典型误差 ±1°C。它和 MCU 内部温度传感器的原理一样,但它是独立做在测温芯片里、出厂做过校准的,精度和一致性比 MCU 内置的那种强得多。在 HVAC 这种长期运行的设备里,本地温度通道主要用来监控控制柜的热积累,防止器件老化引发隐性故障。
2.2 远程通道:一颗三极管怎么当传感器用
这是这类芯片最让人迷惑、也最精妙的地方。远端测温点不接热电偶、不接 NTC,而是接一颗短接的晶体管,或者干脆就是一个 PN 结。PN 结正向导通时满足:
Vbe = (kT/q) ln(Ic / Is) + Rs·Ic
k 是玻尔兹曼常数,q 是电子电荷,T 是绝对温度,Is 是饱和电流,Rs 是串联电阻。Is 随工艺、批次、个体差异变化非常大,如果只测一个 Vbe 想反推 T,精度基本没法看。芯片内部会用两个不同的电流 I1、I2 分别激励远端管,测出对应的 Vbe1、Vbe2 再算差值:
ΔVbe = Vbe1 - Vbe2 = (kT/q) ln(I1 / I2)
这里 Is 被消掉了,串联电阻项在理想情况下也被差分掉了。ΔVbe 正比于绝对温度 T,只要电流比 I1/I2 已知且稳定,就能解出温度。这就是为什么一颗普通三极管的 PN 结能当高精度温度传感器用。
实际偏差主要来自几个地方:晶体管的理想因子 η 不是严格 1,一般落在 1.00 到 1.01 之间,芯片默认按某固定值折算,所以选管时要挑型号匹配的;三极管基极电流会造成分流,一般要求 β 大于 50;走线上的串联电阻也需要芯片内部的补偿功能来处理。好在多数这类芯片已经把串联电阻消除、失配校准做进去了,留给用户的只剩选管和布线。
2.3 寄存器结构与数据解读
这类芯片的寄存器风格大同小异,PJ85718DM 也不例外。典型结构如下,我以手上这颗为例,具体引脚和寄存器位请以数据手册为准。
| 寄存器 | 作用 |
|---|---|
| 0x00/0x01 | 本地温度高字节 / 低字节 |
| 0x02/0x03 | 远程温度高字节 / 低字节 |
| 0x04 | 状态寄存器(开路/短路/超温标志) |
| 0x05 | 配置寄存器(分辨率、转换模式) |
| 0x06 | 转换速率设置 |
| 0x07~0x0B | 本地/远程高低温报警阈值 |
| 0x0C | 单次转换触发 |
数据格式一般是 12 位或 13 位。以 12 位、0.0625°C/LSB 为例,原始值左对齐,低 4 位是无效位,右移 4 位再乘 0.0625 就是摄氏温度:
温度 = (int16_t)((MSB << 8 | LSB) >> 4) × 0.0625
如果觉得分辨率不够 0.01°C 就焦虑,大可不必。对 HVAC 控制来说,0.1°C 的重复性都绰绰有余,真正影响体验的是漂移和迟滞,不是小数点的位数。
3. STM32F303RE 硬件接口设计
3.1 I2C 总线的接线与上拉电阻计算
STM32F303RE 的 I2C1 默认在 PB6(SCL)、PB7(SDA),速率开到 400kHz 快速模式足够。两个引脚配置成复用开漏输出,外部必须加上拉电阻,因为 I2C 是靠设备拉低总线通信的,没人拉低时总线需要一个确定的高电平。
上拉阻值不能瞎选。总线上升时间和总线上电容量 C_b 近似满足:
t_r ≈ 0.8473 × R_p × C_b
快速模式 400kHz 一般希望上升时间低于 300ns。板内走线电容大约每厘米 0.8 到 1pF,加上芯片引脚电容,如果整条总线只有 20 到 30cm,总电容大约 50 到 80pF,这时选 4.7kΩ,计算一下:t_r ≈ 0.8473 × 4700 × 80e-12 ≈ 318ns,刚好贴边。如果再接一段 1m 的排线,总线电容会多出 100pF 以上,t_r 直接飙到 600ns 以上,通信就开始不稳,这时候得把上拉降到 2.2kΩ,甚至 1kΩ。
务实建议是:先按 4.7kΩ 焊上去实测,如果示波器看到 SCL 上升沿明显变缓、I2C 偶发报错,再换成 2.2kΩ。别一上来就上 1kΩ,上拉越小静态电流越大,低功耗设备上不划算,而且关机状态下会产生反灌电流。
3.2 远程测温端的电路与布线要点
这是整个项目最容易翻车的地方。远端测温点的接法,以 PNP 三极管为例,比如常见的 2N3906 或贴片 MMBT3906:
- D+(Remote+)接三极管的发射极
- D-(Remote-)接三极管的基极和集电极,两者短接
为什么用三极管而不是普通二极管?普通二极管也有 PN 结,但它的饱和电流和串联电阻离散性更大。三极管把 BC 短接后,相当于一个集电区面积更大的二极管,工艺一致性更好,在大电流密度下理想因子也更接近 1。芯片也能驱动 NPN,接法相反,具体看数据手册里的驱动极性。
布线这几条是必须遵守的:
- D+/D- 两根线尽量等长、并行走,不要从高频开关电源底下穿过。
- 远端离主板超过 30cm 时,在线材末端跨接一个 100pF 差模电容滤共模噪声。电容不能太大,太大会拖慢 PN 结激励换向,导致读数偏低。
- 有条件就给每根线串一个 2.2kΩ 电阻,让芯片内部的串联电阻消除功能补偿掉,长线场景这功能几乎是救命用的。
- 三极管要贴在被测物表面,用导热胶固定,不要悬空装在空气里,否则测的是它自己外壳的温度。
ALERT 引脚是开漏输出,同样要接一粒 10kΩ 上拉到 3.3V,再连到 STM32F303RE 的一个 EXTI 引脚。我在 PA0 上做下降沿触发中断,芯片报警时 MCU 不用轮询。
3.3 供电与整体连接
PJ85718DM 供电我按 3.3V 设计,和 STM32F303RE 同用一路 3.3V 电源,中间加磁珠隔离数字噪声。芯片电源引脚旁边放 0.1µF 陶瓷电容,尽量贴近引脚,外围再放一颗 1µF 做低频储能。SDA/SCL 走线长度控制在 5cm 以内,避免两根线长短不一致造成建立时间不够。
| PJ85718DM 引脚 | 方向 | 连接目标 |
|---|---|---|
| VCC | - | 3.3V(经磁珠) |
| GND | - | 系统地 |
| SCL/SDA | I/O | STM32F303RE PB6/PB7,各接 4.7kΩ 到 3.3V |
| ALERT | 输出 | STM32F303RE PA0(EXTI0,10kΩ 上拉) |
| D+/D- | 输入 | 远端三极管发射极 / 基极集电极 |
| A0/A1(地址选择) | 输入 | 按地址需求接 GND 或 3.3V |
4. 固件实现:从寄存器到稳定温度
4.1 CubeMX 初始化配置
工具链用的是 STM32CubeMX 生成工程骨架加 HAL 库。关键配置如下:
- RCC:HSE 8MHz,系统时钟 72MHz
- I2C1:Fast Mode 400kHz,7 位地址模式,关闭内部上拉
- PB6、PB7 自动复用成 I2C1_SCL、I2C1_SDA
- PA0 作为 GPIO_EXTI0,下降沿触发
- USART1 可选:115200/8N1,调试打印
这里有个小坑:CubeMX 默认把 I2C 的时钟拉伸打开,很多温度传感器芯片并不支持完整的光时钟拉伸。PJ85718DM 实测没遇到问题,但建议先开着,万一有设备拖住 SCL 导致 HAL 通信超时,再关掉不迟。
4.2 读取温度的完整驱动
HAL 读写其实就是 Mem_Read 从寄存器地址读两字节,封装成函数很直接:
#define PJ85718DM_ADDR (0x5A) #define PJ85718DM_REG_TA (0x02) /* 本地温度 */ #define PJ85718DM_REG_TR (0x04) /* 远程温度 */ #define PJ85718DM_REG_STATUS (0x06) static int16_t pj85718dm_read_raw(uint8_t reg) { uint8_t buf[2] = {0}; HAL_I2C_Mem_Read(&hi2c1, PJ85718DM_ADDR << 1, reg, I2C_MEMADD_SIZE_8BIT, buf, 2, 50); return (int16_t)((buf[0] << 8) | buf[1]); } float pj85718dm_read_local(void) { int16_t raw = pj85718dm_read_raw(PJ85718DM_REG_TA); return (raw >> 4) * 0.0625f; } float pj85718dm_read_remote(void) { int16_t raw = pj85718dm_read_raw(PJ85718DM_REG_TR); return (raw >> 4) * 0.0625f; }HAL_I2C_Mem_Read 最后一个参数是超时毫秒数。理论上 400kHz 下读两个字节不到 1ms,但第一次读的时候芯片可能正好处于转换过程,所以我习惯把超时放到 50ms,宁可慢一点,也不要刷新挂住。
4.3 数据滤波与软件补偿
原始读数直接拿去用,通常会有几个 LSB 的抖动。对这种慢变温度信号,不需要一上来就上卡尔曼滤波,一个 8 点的滑动平均足够:
#define T_BUF_SIZE 8 static float tbuf[2][T_BUF_SIZE]; static uint8_t tidx; float pj85718dm_filtered(uint8_t channel, float new_value) { tbuf[channel][tidx % T_BUF_SIZE] = new_value; float sum = 0; for (int i = 0; i < T_BUF_SIZE; i++) { sum += tbuf[channel][i]; } tidx++; return sum / T_BUF_SIZE; }如果数据偶尔出现离谱尖峰,比如突然跳到 85°C 又落回来,多半是 I2C 读错或受到干扰,滑动平均会被尖峰带歪。我加了离群值剔除:新值和上次有效值差超过 5°C 直接丢弃,连续 3 个异常值才更新。工程上叫中值前滤波,对付 HVAC 现场故障最管用。
报警阈值直接在芯片里设置,让芯片自己比较,MCU 只在 ALERT 引脚触发时才介入。阈值写入时先写高字节,再把低字节置零。要注意芯片报警比较有迟滞参数,一般放在配置寄存器里,别设成 0,否则临界点附近会反复抖动触发。
5. 精度标定与误差分析
5.1 系统误差从哪里来
用表把主要误差来源和应对措施整理出来,调参时按表排查,比凭感觉试要快得多。
| 误差来源 | 影响量级 | 对策 |
|---|---|---|
| 芯片自身精度 | 本地 ±1°C / 远端 ±1~2°C | 出厂值,做系统标定补偿 |
| 远端三极管 β 不足 | 读数偏高 | 选 β>50 的管子,BC 短接 |
| D+/D- 走线阻抗不匹配 | 读数偏移 | 等长并行走线,差分电容滤噪 |
| 长线串联电阻 | 读数偏低 | 启用芯片串联电阻消除补偿 |
| 电磁干扰耦合 | 读数跳变 | 屏蔽线或双绞线,加滤波电容 |
| 安装热阻 | 测的是导热路径上温度 | 导热胶填缝,避免悬空 |
5.2 两点标定实操
标定不算高深。我用恒温槽,或者退一步用一个保温杯装冰水混合物做 0°C 参考,再用沸水或恒温烙铁加热平台做第二个参考点。把远端三极管放进参考环境,等读数稳定后记录:
- 参考点 1:T_ref1 = 0.0°C,芯片读出 T_raw1
- 参考点 2:T_ref2 = 100.0°C,芯片读出 T_raw2
然后算增益和偏移:
G = (T_ref2 - T_ref1) / (T_raw2 - T_raw1)
O = T_ref1 - G × T_raw1
修正后的温度 = G × raw + O。把 G 和 O 存进 MCU 的 Flash,上电时读出来应用。我在 HVAC 项目里实测标定后,远端通道从出厂 ±2°C 压到了 ±0.3°C 左右。
注意:标定必须在真实热平衡状态下做。把三极管扔进冰水,要等 10 分钟以上让整个管壳完全到 0°C 再读,因为管壳和导热路径都有热容。急着读出来的数只能骗自己。
还有一个取舍建议:如果控制逻辑只关心温差和趋势,不关心绝对精度,标定可以完全省掉。很多 HVAC 控制算法用的是送风和回风的温差,出厂一致性已经足够,这个结论帮我省掉了后续两个产品批次的标定工位。
6. 调试中遇到的实际问题
6.1 I2C 总线挂死
第一版板子连续跑两天后出现一个怪问题:SCL 正常,SDA 被拉死,HAL_I2C_Mem_Read 一直返回 HAL_BUSY。排查下来是现场干扰把 SDA 打到错误状态,设备在地址阶段应答完之后没释放总线。
软件优先做总线恢复,在每次通信前检查:
void i2c_recover_bus(void) { // 先把 SCL/SDA 临时切成普通推挽输出 // 若 SDA 为低,则对 SCL 连续产生 9 个时钟脉冲 for (int i = 0; i < 9; i++) { HAL_GPIO_WritePin(SCL_GPIO_PORT, SCL_PIN, GPIO_PIN_RESET); delay_us(5); HAL_GPIO_WritePin(SCL_GPIO_PORT, SCL_PIN, GPIO_PIN_SET); delay_us(5); } // 重新初始化 I2C 外设 }这个序列的作用是给总线上的设备 9 个时钟,让它们内部状态机复位,释放 SDA。硬件上把上拉电阻减小、加 ESD 防护器件,能大幅降低这类问题发生率。
6.2 远程读数反复跳变
有一块板子远程读数在 ±3°C 范围内乱跳,示波器一测,D+/D- 上叠着 20MHz 左右的开关噪声。原因是远端走线恰好穿过了 DC-DC 电感下方。解决办法是把走线绕开,加 100pF 差分电容,再把芯片转换速率从 8 次/秒降到 2 次/秒,叠加 8 点滑动平均后,峰峰值从 ±3°C 压到 ±0.1°C。
这类问题一开始布线就应该规避,不能指望软件滤波兜底。软件滤波只能让数字好看,实际控制用的还是被污染的物理量。
6.3 ALERT 上电误报
板上电瞬间,PJ85718DM 的 ALERT 引脚输出低电平触发了 MCU 中断,误判为温度超限。查状态寄存器发现上电时转换未完成,内部阈值比较器把温度默认当成非法值。解决方法是 MCU 上电后先读一次状态寄存器,把 ALERT 中断使能延迟到第一次有效转换完成之后,同时把迟滞设置好,比如高温报警 50°C、恢复 45°C。
6.4 远程开路与短路误判
有客户反馈某台设备远程温度固定显示 -128°C。原因是远端三极管虚焊,或者插头松动导致 D+/D- 开路。芯片把这个异常检测出来,映射成了一个大负数。我在驱动里加了一层合法性判断:远程温度小于 -40°C 或大于 +150°C 就直接标记为传感器故障,而不是当正常数据上报。HVAC 现场最怕的就是假数据进入控制逻辑,把风机停了或者把压缩机烧了。
7. HVAC 场景的扩展玩法
7.1 通过 RS485 变成远程监测节点
项目落地时,客户要求把 4 台机组的 8 个温度点汇总到一个中控屏。我在 STM32F303RE 上接了 RS485 收发器,跑 MODBUS RTU 从站协议,把本地温度和远程温度映射到保持寄存器:
| 寄存器地址 | 内容 | 类型 |
|---|---|---|
| 40001 | 本地温度 ×10 | uint16 |
| 40002 | 远程温度 ×10 | uint16 |
| 40003 | 报警状态字 | uint16 |
温度乘 10 再传,是为了避免在 MODBUS 协议里搬运浮点数,上位机那边除 10 就能还原。波特率设 9600,两线制半双工,终端电阻 120Ω。现场实测 1200m 走屏蔽双绞线没有任何丢包。
7.2 控制逻辑的工程化处理
温度数据最终要喂给控制逻辑,我总结了几条工程经验:
- 温度阈值带迟滞,比如送风温度高于 30°C 开风机、低于 25°C 才关风机,防止临界点反复启停。
- 报警加延时滤波,连续 5 秒超限才算报警,环境瞬时波动不会误报。
- 远程温度和本地温度联动,比如远程风道堵塞导致温度异常升高,本地控制盒也要降载保护。
- 数据落日志,用 Flash 记录最近 100 条温度事件,方便售后回查。
我还把 PJ85718DM 的 ALERT 引脚接进 MCU 的硬件停机逻辑,一旦芯片报出硬件级超温,直接切断风机或加热器电源,完全不依赖上位机响应。这个设计在两次现场突发里把整个系统保住了,口碑就是这么攒下来的。
做这个项目,我最深的体会是:测温度看起来是最简单的事,真正难的从来不是读寄存器,而是把传感器放到对的位置、把噪声滤干净、把异常数据识别出来。PJ85718DM 加 STM32F303RE 这套组合,用一颗芯片解决了本地和远程两路测温,在成本、体积、可靠性之间找到了平衡。如果以后你要做类似的嵌入式或 HVAC 测温项目,建议先把远端三极管的接法和布线想明白,那个地方省不了几分钟,但能帮你省掉后面无数个加班的夜晚。最后再分享一个小技巧:画板子时给 D+/D- 留一对测试点,用 0402 电阻把三极管和芯片之间断开,标定和排查故障时不用动烙铁,效率直接翻倍。