
做嵌入式和 HVAC 控制器的人早晚都会遇到同一个需求板子上的温度和远处测温点的温度都想覆盖手头却可能只剩一路空闲 I2C。我最近一块试验板就是这个情况——板卡环境温度要盯着远处风道里的温度也要盯着最开始我想上两颗独立温度传感器最后发现一颗 PJ85718DM 就能同时管住本地和远程两路温度配合 STM32F071VB 的 I2C 外设整个温度采集模块占的 PCB 面积小得可以忽略。这篇文章把方案背景、硬件接线、固件写法以及我在 HVAC 强电磁环境下踩过的坑一次说清楚适合正在做温控器、嵌入式板卡监控、环境监测这类项目的开发者参考。1. 为什么本地远程双通道测温比两个单点传感器更省心——背景与方案选型1.1 嵌入式和 HVAC 应用里到底要测哪些温度先明确一个容易误解的点这里说的本地和远程不是指手机 App 远程看温度而是一颗传感器芯片内部有两个测温单元。本地通道测的是芯片封装自己的温度通常用来当板卡环境温度或控制器机箱温度远程通道测的是芯片外一个二极管探头的温度探头可以放在芯片几十厘米甚至一米以外的地方。所以这个项目标题里的本地与远程温度指的是芯片内、外两路测温点。嵌入式应用里最常见的测温点有三个板卡环境温度、功率器件附近温度、处理器结温。前两者用普通数字传感器或 NTC 都能凑合但处理器结温基本只能用热敏二极管来测而这正好是双通道传感器远程通道的强项。把远程通道接到处理器内部的 thermal diode 引线上就能实时读到结温不需要在芯片上额外贴探头。HVAC 应用的测温点就更分散了回风温度、送风温度、盘管温度、控制器自身温度。盘管或风管离控制板少说几十厘米多则一两米。如果每个点都放独立传感器成本、走线和维护都麻烦如果只用一个本地传感器那测到的只是板卡附近温度和真正的盘管温度差得远。一颗带本地和远程双通道的传感器正好一个通道管板上一个通道管远处的物理测点。1.2 常规方案对比双芯片、NTC、双通道传感器做方案选型时大多数人第一反应是一个测温点放一个传感器。这样确实直接但问题也明显BOM 多一颗料、PCB 多一组走线、I2C 地址位不够分。用两片单通道数字传感器时如果芯片地址可选位少总线上再挂其它 I2C 器件就容易冲突。用 NTC 加 ADC 的方案也常见但 NTC 的阻值-温度关系不是线性的需要查表或多项式拟合分压电阻的温度漂移还会影响精度长期稳定性更差一些。双通道传感器方案把两路测温收敛到一颗芯片上本地通道和远程通道共用同一个寄存器组读起来就是两个相邻寄存器的事。校准时也只要对一颗芯片做一次修正温度一致性天然比两片独立芯片好。我在下表里整理了几种常见方案的差异方便对照选型。方案远程测点多远I2C资源占用额外硬件校准复杂度单通道数字传感器 NTC看NTC线长1个地址ADC通道、分压电阻、查表NTC查表分压电阻漂移两片单通道数字传感器看传感器探头线长2个地址两颗料、两组走线两片分别校准双通道温度传感器半导体探头可达数十厘米以上1个地址几乎无一颗芯片一组offset当然双通道方案也不是没有前提远程通道要求测点是一个 PN 结而不是任意一个电阻温度探头。好在有些场景本身就具备这个条件有些场景我们可以主动把探头做成二极管形式后面章节会详细说。1.3 STM32F071VB 的资源为什么够用STM32F071VB 是一颗 Cortex-M0 内核、最高 48MHz 主频的 MCU片上有 128KB Flash、16KB SRAMI2C 外设有两个还有 CAN、USART、SPI 这些常用接口。放在这个项目里读温度只是它很小的一个任务剩下的算力可以用来跑 PID、Modbus 协议、继电器时序控制甚至做简单的记录存储。它的工作电压是 2.0~3.6V在 HVAC 控制板上和 3.3V 仪表电源搭配很自然LQFP100 封装也提供了足够的 IO 去点数码管、读按键、驱动继电器。简单说选它不是因为必须用它测温度而是因为这类控制器本来就需要一颗 IO 丰富、通信接口齐全又不贵的 MCU测温只是顺带把它用得更满。2. PJ85718DM 的测温原理与硬件接线——双通道传感器的正确打开方式2.1 本地测温单元带隙基准与片内 ADCPJ85718DM 本地通道的测温核心是一个带隙基准加一个模数转换器。温度变化会引起硅片上晶体管 Vbe 的电压变化带隙基准把这个电压变化稳定地转换成可以量化的信号ADC 再把它变成数字温度值写到寄存器里。这类芯片出厂前都会做温度校准所以本地通道的典型精度可以做到 ±1℃ 到 ±2℃ 左右对大多数环境监测和 HVAC 控制已经足够。平时使用中本地通道不需要接任何外部元件芯片上电后连续转换温度值会周期性地刷新到数据寄存器里。2.2 远程测温单元二极管测温的两个关键点远程通道测温的本质是测外部 PN 结的正向压降随温度的变化。PN 结正向压降会随温度升高近似线性下降温度系数大约是 -2mV/℃这是一个非常规律的关系。但直接用绝对压降解温度会踩坑PN 结的绝对压降跟工艺离散度关系很大同一型号的两个二极管在相同温度下 Vbe 可能差几十毫伏对应温度误差能达到好几摄氏度。更关键的是探针引线本身有电阻线阻上的压降会和二极管真实压降混在一起。所以芯片内部不是简单地测一次压降而是用两个不同电流先后激励同一个二极管测两次压降取差值来算温度。因为 ΔVbe 正比于 ln(I1/I2)串联电阻在两次测量里产生的压降是相同的做差之后就消掉了。这就是为什么远程通道明明测的是二极管却能给出比较可靠温度的原因。对于做嵌入式的小伙伴这个原理同样解释了为什么 CPU 内部的 thermal diode 引线很细很长还能测温——芯片驱动的测量方法本身对串联电阻不敏感。远程探头的典型形态有两种一种是直接把目标芯片自带的 thermal diode 接到 D、D- 两个引脚上另一种是用一支小信号三极管把基极和集电极短接当作一个二极管探头使用。具体用哪种管子、哪个极性要按 PJ85718DM 数据手册的参考电路来不同芯片对二极管类型的要求不完全一样。2.3 硬件接线清单与常见错误下面是这次试验板上最常用的一组连接照着搭一般都能跑通引脚接到哪里说明VDD3.3V电源传感器与MCU共用电源域GND电源地与MCU严格共地SCLSTM32F071VB PB6I2C1时钟线SDASTM32F071VB PB7I2C1数据线A0/A1接GND按手册定义组成I2C地址D远程二极管阳极探头正极走线要短/平行D-远程二极管阴极探头负极与D尽量等长SCL 和 SDA 上需要各放一个上拉电阻阻值通常选 4.7kΩ。如果总线上还挂了其它 I2C 设备上拉阻值要按整条总线的总电容重新算不能照抄。远程探头走线要尽量靠近地线或包地避免从继电器、变压器、高频开关电源下面穿过。探头端如果有空间可以在 D 和 D- 之间并联一个小电容滤除射频干扰一般在 1nF 左右具体上限以手册为准电容太大反而会让芯片激励电流的翻转时间变长读取结果异常。这里有个特别容易犯的错把远程探头接反。二极管接反之后芯片读回来的远程温度要么是极其离谱的数值要么会直接抛出开路/短路错误标志。所以第一块板子上电后发现远程温度在 -100℃ 附近跳或者一直报错先别急着怀疑固件用万用表量一下 D、D- 之间的二极管压降方向多半能找到问题。3. STM32F071VB 端 I2C 采集程序——从寄存器读取到稳定温度值3.1 I2C 初始化与首次通信用 HAL 库初始化 I2C1 最省事。打开工程把 PB6/PB7 复用为 I2C_SCL/I2C_SDA时钟设置成 100kHz 或 400kHz。我的建议是先用 100kHz 标准模式把链路验证通再上快速模式HVAC 这种温度数据本身刷新不快100kHz 完全够用。初始化之后第一件事是读一次配置寄存器或设备 ID 寄存器确认芯片真的在线避免后面读出来的全是 0xFF 还在找原因。3.2 配置、读取与温度换算温度传感器的寄存器布局通常很规律本地温度高字节、低字节、远程温度高字节、低字节外加一个状态寄存器和一个配置寄存器。配置寄存器里最常用的是分辨率和运行模式两项我把分辨率设为 12 位、连续转换模式这样每次读取读到的是最新转换结果。读取温度时要注意高低字节的一致性。如果先读高字节芯片内存的转换结果却在两个字节之间更新了一次那拼出来的值就是错乱的。多数传感器会用锁存机制避免这个问题只要你读了高位字节低位字节就被冻结直到你把它读走。所以我在代码里习惯用一次 burst read 连续读两个字节而不是分开读两次。代码框架大概是下面这样#include main.h #define PJ85718_ADDR (0x48 1) /* 8位I2C地址A0A10时常见 */ #define REG_LOCAL_H 0x00 /* 本地温度高字节示例偏移*/ #define REG_LOCAL_L 0x01 #define REG_REMOTE_H 0x02 /* 远程温度高字节示例偏移*/ #define REG_REMOTE_L 0x03 #define REG_STATUS 0x04 #define REG_CONFIG 0x05 static int16_t read_high_low(uint8_t reg_hi) { uint8_t buf[2]; if (HAL_I2C_Mem_Read(hi2c1, PJ85718_ADDR, reg_hi, I2C_MEMADD_SIZE_8BIT, buf, 2, 100) ! HAL_OK) { buf[0] 0xFF; buf[1] 0xFF; } return (int16_t)(((uint16_t)buf[0] 8) | buf[1]); } float read_local_temp(void) { int16_t raw read_high_low(REG_LOCAL_H); raw 4; /* 12位数据右对齐时丢弃低4位按手册调整 */ return raw * 0.0625f; /* 12位分辨率LSB0.0625℃ */ } float read_remote_temp(void) { int16_t raw read_high_low(REG_REMOTE_H); raw 4; return raw * 0.0625f; }代码里的寄存器偏移是我手头这类双通道传感器很常见的布局PJ85718DM 的真实偏移一定要以你的数据手册为准。不少新手就栽在这一步抄了别人代码里的偏移结果读出来的温度要么跳变要么恒定其实只是地址对不上。3.3 温度换算里的符号位陷阱负温度处理是最容易被忽略的细节。12 位分辨率下温度寄存器里存的是有符号数温度 0℃ 以上时二进制最高位为 00℃ 以下时最高位为 1。如果直接把两个字节拼成 uint16_t-5℃ 会被算成 251℃。所以读回来一定要用 int16_t 来组合还要根据寄存器对齐方式决定是否移位。代码里 raw 4 是因为我把数据当作 12 位右对齐来处理的如果你的芯片是左对齐格式移位方向就不一样了。这里的判断方法很简单数据手册里一定会给温度与寄存器值的对应关系找一个已知温度点比如 0℃ 的冰水读一遍看代码输出对不对就知道要不要调整对齐方式了。3.4 软件滤波与状态检查温度读取本身很快几百毫秒读一次都算频繁。但原始值里偶尔会混入噪声毛刺尤其是 HVAC 这种带继电器的环境。我只做两步一阶低通加限幅。一阶低通就是 y y k * (x - y)k 取 0.1 到 0.2采样周期 500ms 时相当于 2 秒左右的时间常数既能把毛刺滤掉又不会让温度响应显得迟钝。限幅则是说如果新读到的值和上次有效值相差超过 10℃直接丢弃这次数据连续丢弃超过 N 次才把当前值强制更新为最近读回的异常值这样探头真正坏掉的时候至少能报警而不是永远卡在旧值。每次读取后我还习惯性地看一眼状态寄存器远程探头开路或者短路都会在状态位上反映出来。读取失败时也不要把 buf 里的 0xFF 当真HAL_I2C_Mem_Read 返回非 HAL_OK 就要走错误处理分支。工程上我一般维护两个全局变量current_temp 和 status_last_error测点报警逻辑只认这两个变量不会去碰原始寄存器值。4. HVAC 盘管探头与嵌入式结温监测两种场景的实测差异4.1 嵌入式场景远程通道盯结温本地通道当机箱温度嵌入式场景下远程通道最迷人的用法是接处理器 thermal diode。我在试验板上模拟过一套嵌入式散热监控主控芯片的 thermal diode 引脚直接接到 PJ85718DM 的 D/D-本地通道贴在板卡空闲区域。跑个压力测试低负载时本地 27.6℃、远程 41.8℃满载时本地 30.2℃、远程 77.5℃。远程通道反映的是硅片内部温度可以提前感知到芯片快过热了风扇提速可以早半拍这个提前量对被动散热的设备很重要。相比之下本地通道测的是机箱内环境温度长年累月记录之后还能反过来判断散热风道是不是堵了。4.2 HVAC 场景把二极管探头做成风管里的测温点HVAC 里常见的做法是拿一支小信号三极管 BC 短接套进防水套管里做成探头放在送风口或盘管位置。探头引出线一般用双绞屏蔽线屏蔽层在控制器端单点接地D/D- 在探头端并联一个电容。这样做的效果我在一套空调箱控制器样机上验证过本地通道测电控盒内温度约 33℃里面元件发热明显远程通道测送风管温度约 12.8℃两个点相差 20℃ 以上要是当初只放一个本地传感器控制器永远以为环境 30 多度防冻逻辑根本没法做。HVAC 的另一个特点是温度变化比嵌入式慢得多。嵌入式结温可能几秒钟就跳好几度风管温度可能十几分钟才动 1℃。所以 HVAC 这边的滤波时间常数可以放得更长甚至直接用每秒读一次、一分钟平均值的方式稳定性会更好也不会给 PID 控制器带来噪声。4.3 强电磁环境下的读值跳动压制流程HVAC 控制板里最讨厌的干扰源是继电器和接触器的线圈。触点一吸合几毫秒的电流尖峰能沿着电源线、地线甚至板上的走线耦合进传感器引脚。我这边远程通道的读数在继电器动作瞬间跳过 8~10℃排查过程是按顺序做的先在探头端并 1nF 电容无效再给 3.3V 电源增加 10uF 电解电容加磁珠减轻了一些最后把传感器换了个位置让 D/D- 走线远离继电器下方的高压区同时把屏蔽层单端接在控制器地读数才真正稳定下来。这个案例给到大家的经验是屏蔽线、差分布线、电源滤波、软件滤波四层防护一层都不能省但也不必一次全上。先找出干扰耦合路径再对症下药比盲目堆滤波电容高效得多。下表是我对两个场景的参数差异汇总方便以后直接套用维度嵌入式场景HVAC场景远程探头形态处理器thermal diode引脚外接三极管封装探头探头到芯片距离几厘米到十几厘米几十厘米到一米左右主要干扰源功率开关、时钟、总线噪声继电器、接触器、压缩机型负载推荐滤波时间常数0.2~1秒1~10秒主要用途结温保护、风扇调速防冻保护、送风温度PID、除霜判断5. 我踩过的坑与最终校准确认——远程二极管、I2C 与负温度5.1 坑一远程探头随便用个普通二极管读数高十几度第一版样机我想省钱直接用手里现成的 1N4148 当远程探头结果读回的温度比真实值高 12℃ 左右。原因是 1N4148 的漏电流和串联电阻特性跟芯片驱动的期望不匹配远程探针模型需要一个漏电流小、BC 短接的小信号三极管比如 2N3904 或 MMBT3904 这类。换上 BC 短接的 MMBT3904 之后远程通道读数瞬间恢复正常。所以远程探头不是随便一颗二极管都能用的第一次做这类方案的朋友最容易在这个细节上栽跟头。5.2 坑二I2C 上拉电阻 10k400kHz 下面通信抽风我习惯总线上挂多个 I2C 器件上拉电阻随手用了 10kΩ。100kHz 标准模式下一切正常改成 400kHz 快速模式后就开始偶尔读到 0xFF。示波器一看SCL 的下降沿变得平缓明显是总线电容和 10kΩ 上拉不匹配上升沿时间超过了规范。把上拉换成 4.7kΩ 后问题消失。这个例子说明I2C 上拉阻值不是随便选的它会直接影响信号完整性尤其总线走线较长、节点较多的时候必要时要根据总线电容算一算。5.3 坑三负温度符号位没扩展-5℃ 显示成 251℃冷冻类应用里最容易碰到这个问题。12 位有符号温度数据如果代码里用 uint16_t 拼字节-5℃ 的补码会被当成 251℃。我的修正方式就是前面代码里写的用 int16_t 拼装必要时做符号扩展后再右移。调试技巧是拿一盆冰水混合物校零把远程探头浸进去看代码输出是不是 0℃ 附近不是的话先怀疑符号位和对齐方式别急着调 offset。5.4 坑四HVAC 继电器板上的周期性温度跳变这个问题我前面提过再展开说。现象是远程温度每 5 秒跳一下跳变幅度 6℃ 左右和继电器动作完全同步。排查链路是先用示波器勾 D/D- 两条线发现继电器动作瞬间有振荡毛刺然后在探头端并联 1nF 电容毛刺明显变矮但没消除再检查走线发现 D/D- 从继电器下方穿过高压侧和低压侧距离太近最后把走线移到远离继电器的板边加上屏蔽层单点接地读数稳定。整个过程说明这种问题不是单一手段能根治的布局才是一切的根源。5.5 两点校准没有恒温箱也能做没有专业恒温箱时我用冰水混合物和一台恒温烙铁做了两点校准。先把本地探头和远程探头都放进冰水混合物里记录各自读数再贴上恒温烙铁的一个已知温度点记录第二组数据最后在固件里做线性修正 y a * x b。a 用来补偿增益偏差b 用来补偿偏置。修正系数烧录到 Flash 里上电时读取。远程探头校准时要保证探头和校准点充分接触不然修出来的系数比没修还差。至少两点才能校正斜率和截距单点校正只能拉平偏置。6. 个人体会和扩展玩法做完整套温度监测之后我的直接体会是这种双通道传感器方案最舒服的场景是点数少、距离远。板上一个点远程一个点一颗芯片、一个 I2C 地址、一组 offset 校准很干净。但如果测温点一次要超过四个那还是老老实实上多通道 ADC 或者分布式总线传感器吧双通道传感器一个一个堆反而贵。后面我把这套读数逻辑拓展了一下把本地温度和远程温度换算成 Modbus RTU 寄存器通过 RS485 让上位机轮询。对 HVAC 的控制器来说两个温度点正好够一个简单的 PID 回路用——比如盘管出水侧温度和送风温度。如果你手头正好也在做类似的嵌入式或 HVAC 测温项目建议第一件事永远是翻手册确认寄存器偏移、分辨率对齐方式和远程探头极性第二件事才是照着我上面的代码套路往下走。我踩过的这些坑希望你能少走一遍。