在HVAC和嵌入式项目里聊温度监控最容易被忽略的就是“测温点选在哪”。机箱里风扇吹出来的风和CPU核心下的散热器表面可以差出十几度空调回风口测到的空气温度和蒸发器盘管表面的温度也完全是两个概念。我最近做的一台环境监测网关用的是PJ85718DM这颗本地/远程双通道数字温度传感器搭配PIC18F86K22这款8位MCU做主控把嵌入式和HVAC场景里最常用的两个测温需求——机柜内环境温度与远端发热体温度——放到同一条I2C总线上一起解决。这篇文章就把我这套方案的硬件连接、寄存器操作、校准方法和调试过程完整摊开来讲给正准备做类似温度监控的人一个可以直接抄作业的参考。1. 双通道测温不是炫技单点方案在嵌入式和HVAC里都有盲区1.1 本地和远程温度到底分别测的是谁先说清楚“本地”和“远程”这两个词的含义。PJ85718DM这类数字温度传感器本地通道测量的是传感器芯片自身的结温或内部热敏单元感受到的温度也就是传感器所在PCB附近那个点的空气或导热路径温度。远程通道则完全不一样它不靠芯片自身感受温度而是通过D和D-两个引脚连到一颗外部的感温二极管上测量那颗二极管所在位置的温度。二极管可以放在离主控板几厘米甚至几十厘米以外的发热源附近。嵌入式场景里最常见的远程测温目标是CPU或者GPU芯片内部那颗PN结测温二极管。处理器核心温度和机箱环境温度是两个完全不同的话题把这两个数据同时拿到手风扇调速策略才能做对。HVAC场景则是另一套逻辑回风口气流温度适合放本地传感器而压缩机的壳体温度、蒸发器盘管表面温度、电机定子绕组的温度这些位置都不好塞一个带探头的传感器进去用一颗贴片二极管贴在发热体表面再引回主控板远程通道就派上用场了。如果你只用一个普通的单点数字温度计比如常见的18B20系列或者各种本地测温芯片那你就只能在它附近测温度想测远处就得把传感器或探头拉过去。这个“把探头拉过去”的做法在实验室里没啥问题到了现场就麻烦探头本身要暴露在强电磁环境下引线长了误差大而且很多探头根本没有办法贴到细小的功率管或压缩机表面。1.2 为什么不能光靠NTC和热电偶解决很多人第一反应是用NTC热敏电阻。NTC便宜、灵活、探头可以定制但它的问题也不少模拟输出长线传输会引入压降和噪声ADC参考电压稍有漂移读数就不准B值本身就是拟合出来的在宽温度范围内非线性比较明显想做到±1℃以内必须在固件里做多项式校正每次采样还要给分压电阻消耗电流低功耗场景也不友好。热电偶是另一种思路测温范围可以做得很大但它需要冷端补偿还需要专用的调理芯片或者自己搭放大电路成本和复杂度一下就上去了。而且热电偶在0到100摄氏度这个HVAC经常工作的区间里精度优势根本发挥不出来属于把牛刀用在杀鸡的活上。PJ85718DM这种器件的聪明之处在于它把本地测温、外部二极管接口、数字通信、报警输出全部集成在一颗芯片里。本地通道负责环境温度远程通道负责热点温度两个数据都是数字量直接进I2C总线不用做ADC校准也不用担心长线压降。对以PIC18F86K22这类8位MCU为主控的嵌入式设备来说这套方案在成本、可靠性和开发速度之间找到了一个非常好的平衡点。2. 器件选型与最小电路这两颗芯片配合起来的硬件基础2.1 PJ85718DM的核心能力与使用前提我手上这批PJ85718DM是3.3V供电的版本工作电压范围大概在2.7V到3.6V之间兼容I2C和SMBus协议支持标准100kHz和快速400kHz。本地温度测量范围约-40℃到125℃远程通道因为外接二极管能测到更宽的范围。以我们这批样品手册上的指标为例本地通道在0℃到70℃之间精度标称±1℃远程通道在同样区间标称也能到±1℃左右极端温度区间的误差会放宽到±3℃。除了基本测量这颗芯片还带了ALERT输出脚可以在温度超过上下限时主动拉低通知主控不用主控一直轮询。这个特性对HVAC控制器尤其有用因为这类设备经常要求温度异常时能快速响应比如压缩机过温就要立刻降频或停机。有个使用前提必须说明远程通道必须外接正规的PN结二极管不能用普通的整流二极管随便代替。最稳妥的做法是用小信号三极管的BC结比如常见的2N3904或8050把集电极和基极短接发射极作为另一个极。PN结的面积、反向漏电流、封装热阻都会影响远程测温的精度HVAC现场如果要在压缩机壳体上贴二极管建议选SOT-23封装的三极管并用导热胶固定引线越短越直越好。2.2 PIC18F86K22在主控侧的资源分配PIC18F86K22是一颗80引脚的8位MCU资源在这个场景里属于相当充裕的。它的内部振荡器加PLL可以跑到64MHz8位机做到这个主频跑温度轮询、滤波、串口上报这些任务完全是绰绰有余。我选择它还有一个原因它的MSSP模块可以配成I2C主模式EUSART模块做串口上报再加上PWM模块驱动风扇一套代码就把温度监控、数据输出和控制执行全部覆盖了。我手上的项目里主控的资源分配大概是这样的表格功能模块使用的MCU外设说明读取温度传感器MSSP1配置为I2C主模式总线速率先用100kHz调稳了再上400kHz串口调试与数据上报EUSART11152008N1输出CSV和Modbus帧本地显示外接I2C LCD1602不占并行口SCL/SDA与温度传感器分时复用风扇控制CCP模块PWM输出根据远程通道温度自动调占空比温度超限中断INT0外部中断接PJ85718DM的ALERT输出脚其实80引脚对这个项目来说有点奢侈换40引脚或28引脚的芯片也完全能跑。不过我当时考虑到后续还要扩展多路传感器、加显示屏、预留Modbus从机功能选大一点省得后面为资源发愁。如果你只是做一个纯温度采集模块完全可以往下选型。2.3 最小系统连接I2C上拉、供电和远程二极管接法PJ85718DM和PIC18F86K22之间只需要两根线SCL和SDA。I2C总线是开漏结构必须要加上拉电阻。我这里SCL和SDA各接了一个2.2k欧姆电阻到3.3V板内走线比较短的情况下这个阻值很稳。如果传感器和主控板之间用排线拉开了一段距离比如超过20厘米建议把上拉电阻降到1k欧姆或者用1k和2.2k组合再实测一下这个后面踩坑部分会详细说。供电方面温度传感器和MCU都跑3.3V芯片电源引脚旁边放了两个去耦电容一个大容量钽电容10uF一个高频陶瓷电容100nF。PJ85718DM的VDD引脚对电源纹波不算特别敏感但它毕竟有ADC和远程电流源电源不干净最终读数就会有小幅跳动。远程二极管的D和D-引脚我做了滤波处理各接了一个几十皮法的小电容到地具体值要参考手册不能瞎加大太大会影响远程激励电流的建立时间导致转换结果变慢甚至漂移。地址引脚用来确定从机地址。PJ85718DM一般有A0和A1两个地址引脚通过接VDD或GND可以组合出多个不同地址这样一条I2C总线上可以挂多片传感器分别测不同区域。我只挂了一片两个地址引脚都接地对应手册里的默认地址。PCB布线时注意把地址引脚附近的配置电阻画清楚打样之前对照手册把地址表确认一遍不然软件里写错了从机地址排查起来非常痛苦。3. 驱动程序的关键逻辑从I2C时序到温度数值换算3.1 寄存器模型配置、通道切换与状态位PJ85718DM这类芯片的寄存器模型大同小异核心是几个16位数据寄存器加一个配置寄存器。我们先说最简单的读取流程再说配置。这类芯片通常有一个命令指针寄存器你要读哪个寄存器就先把对应的寄存器地址写给芯片然后再发读命令。以我参考的手册为例寄存器分配大致是这样0x00是本地温度数据寄存器0x01是远程温度数据寄存器0x02是状态寄存器0x03是配置寄存器。状态寄存器里值得关注的是远程传感器断路和短路标志位每个温度周期转换完后主控读一下状态寄存器就能知道远程二极管有没有接错或者掉线。有些情况下我没提勘误说白了如果HVAC现场的接线端子松了或者线断了你能从状态寄存器里直接看到异常比通过温度读数异常再猜要快得多。配置寄存器里主要设置转换模式、转换速率、警报极性这些东西。项目初始化时我写了一条配置连续转换模式转换速率为默认速率ALERT脚低电平有效。连续转换模式的好处是主控随时去读都能拿到最新结果不用先触发转换再等。缺点是芯片一直处于工作状态功耗稍微高一点。低功耗场景下可以改成单次转换模式或者叫关断模式唤醒后再主动触发一次转换。3.2 读温度数据的完整调用流程读取一个温度数据的完整I2C流程如下先发START然后写从机地址加写位接着写寄存器地址0x01远程温度再发STOP。紧接着发START写从机地址加读位然后读两个字节最后发STOP。两个字节里第一个字节是高字节第二个是低字节。这里有个细节读第二个字节之后主机要回一个NACK表示不再有后续数据否则从机会以为你还想继续读。很多人在调试时容易犯一个错误就是在写完寄存器地址之后没有先STOP再次START就直接读取。这取决于芯片是否支持重复起始条件repeated START。PJ85718DM是支持重复START的但直接用“STOP再START”也没问题而且容错率更高。我实际是在I2C驱动里写了一个通用函数先写寄存器地址再发起读操作中间用repeated START代码里加了个超时保护防止芯片不响应时主控死等。我在这个项目里实际使用的读取函数简化后大概是这个思路static uint8_t i2c_read_temp_reg(uint8_t reg, uint16_t *value) { uint8_t buf[2]; if (i2c_write_byte(PJ85718DM_ADDR, reg) ! I2C_OK) { return 1; } if (i2c_read_bytes(PJ85718DM_ADDR, buf, 2) ! I2C_OK) { return 2; } *value ((uint16_t)buf[0] 8) | buf[1]; return 0; }从机地址这里有个字节地址和写入位的问题一般I2C库会自己处理但如果你用位操作自己拼地址记得左移一位加上读写位。我见过太多人在这里写错直接把0x4C当成了从机地址结果每次通信都失败查了半天其实是地址匹配不到。3.3 把裸寄存器值换算成人类可读的温度PJ85718DM的温度数据是12位有效数据LSB对应0.0625摄氏度也就是一个计数等于1/16摄氏度。拿到16位的原始值后先判断符号如果是正温度高字节的高位是0如果是负温度整个寄存器是按二进制补码表示的直接把16位值当成有符号数除以16就是带符号的温度值。因为我在PIC18F86K22上用C语言编程int16_t天然就是补码所以计算非常简单int16_t raw (int16_t)raw16; // 把读到的16位值当作有符号数 float temp_c (float)raw / 16.0f;举个例子读到的值是0x0190十六进制转成十进制就是400除以16得到25.0摄氏度这正是室温附近的值。如果是负温度比如-10摄氏度寄存器值应该是0xFF60对应int16_t是-160除以16就是-10.0。换算本身没什么难度难度在于确定LSB到底是多少。我一开始默认按有些芯片的0.0625来算结果和参考温度计差了整整一倍。回去翻手册确认这颗芯片在12位模式下LSB是0.0625°C但在兼容某些SMBus命令时可能会按11位格式返回低三位无效。稳妥的做法是在初始化时把配置寄存器里的分辨率设置读一下明确知道当前处在多少位模式再决定右移几位。4. 远程通道的校准与PCB实践精度是靠细节撑起来的4.1 远程二极管测温的原理与误差放大器远程温度测量的本质是测量PN结正向压降随温度变化的程度。二极管的正向压降本身受制造工艺影响很大同一个温度下不同二极管的绝对Vbe可以差出几十毫伏换算成温度就是好几摄氏度。所以这类芯片不使用绝对Vbe而是用两个不同电流分别驱动二极管测量两次Vbe的差值。具体来说芯片内部的电流源先给二极管通一个小电流I1测一次Vbe再通一个大电流I2再测一次Vbe。如果串联电阻是R那么两次测到的压差就包含了串联电阻上的压降差。PN结的物理模型告诉我们ΔVbe等于(kT/q)乘以电流比的对数这个公式跟二极管的绝对工艺参数基本无关只跟温度T和电流比值有关。用这个ΔVbe反推温度就能把串联电阻的影响抵消掉所以远端引线有几十欧姆电阻问题不大。但这里有一个前提D和D-两个引脚的漏电流路径要尽量对称。如果一根引线旁边是高频开关电源的走线另一根引线旁边是安静的模拟地两边的耦合噪声不一样那ΔVbe就会被污染。PCB上D和D-走线间距尽量保持一致长度尽量接近别跨过太大的干扰源这是远程通道实测精度能不能保住的根本。4.2 我在现场实测得到的一组校准数据装到设备里做实测时我用一支高精度铂电阻温度计当参考同时记录本地通道、远程通道的读数。远程感温二极管我用的是小信号三极管BC结用导热胶贴在电机外壳上引线长度大约25厘米。下面是其中一个温度点的实测记录参考温度本地通道读数远程通道读数本地误差远程误差15.2℃14.9℃14.6℃-0.3-0.622.8℃23.0℃22.3℃0.2-0.531.5℃31.4℃31.0℃-0.1-0.545.0℃44.8℃44.6℃-0.2-0.460.3℃60.5℃60.1℃0.2-0.2这台设备实测下来远程通道基本有一个大约 -0.5℃的系统性偏低趋势。我在固件里给远程通道统一加了一个0.5℃的偏移量之后整体误差基本控制在±0.4℃以内对于HVAC应用完全能够接受。如果对精度要求更高可以做一个分段校准表放到MCU的Flash或EEPROM里按读到的数值查表修正。分段校准在0到50摄氏度范围内每隔10度取一个点做线性插值就够了。4.3 长线布局、滤波电容与地线处理的注意事项远程二极管引线超过20厘米后环境中的共模噪声会明显增大。我的做法是D和D-两条线单独走尽量平行靠近不要和大电流的PWM风扇线并排更不要绕一圈占变压器附近的空间。传感端到传感器芯片之间如果不得不跨过电源区域把两条线从底层走上面铺一层完整的地平面做屏蔽。远端二极管引脚旁边的小电容也很关键。手册上通常建议在D和D-上各接一个10pF到100pF的电容到地用来滤掉高频噪声但数值不能太大。我第一版布板时随手放了一个1nF结果远程温度读数变得非常不稳定后来查资料才明白大电容使得电流源在每个激励周期内都需要额外建立时间芯片内部的ADC采到的点可能还没稳定。换成100pF之后读数一下就稳了。最后是地线问题。温度传感器芯片的GND引脚必须和MCU的GND形成良好的星形接地不要让传感器回流电流经过大电流模块的回路。冷凝水、结露这些HVAC现场的实际情况也要考虑最好在远程传感器引线连接器附近涂覆三防漆防止湿气导致漏电。5. 固件状态机与上报逻辑把温度数据变成可用的监控流5.1 主循环里的任务调度与滤波固件整体结构我按一个简单的前后台状态机来写。主循环每100毫秒跑一个温度读取任务先读本地温度再读远程温度接着读状态寄存器判断是否有报警。读取完成后丢进一个环形缓冲区做中位值滤波加一阶低通防止个别异常值直接把显示数据带飞。中位值滤波的方法是连续采5次排序取中间值。这个操作对抑制偶发的尖峰干扰特别有效尤其是空调压缩机启动时大电流冲击可能瞬间干扰远程通道的读数。中位值滤波之后再做一阶低通公式就是y等于(1-alpha)乘上次值加alpha乘新值alpha取0.5左右比较合适。这样处理后显示的温度数值非常平稳不会像原始读数那样来回跳动。有一个坑是滤波初值的问题。刚上电时滤波器的历史值建议直接用第一次读到的温度不要用0否则从0慢慢爬到真实温度要好几秒。我在初始化时判断了一下如果第一次读取成功直接把当前值写入滤波历史后续滤波从第二次读数开始生效。5.2 UART上报与Modbus帧的简单实现温度数据最后要送给上位机我用EUSART1做了一个双向通道。调试阶段直接每秒输出一行CSV格式是时间戳、本地温度、远程温度、状态标志。这种格式用串口助手看非常直观Excel也能直接打开排查问题效率高。如果要接入楼宇自控系统或者现成的上位机Modbus RTU从机是更通用的选择。PIC18F86K22的EUSART配合定时器可以精确实现3.5个字符的帧间隔判断。寄存器映射我定了两个保持寄存器地址0x0001存本地温度乘以10后的整形值0x0002存远程温度乘以10后的整形值。这样上位机读到整数之后除以10就是实际温度避免浮点数传输带来的麻烦。Modbus从机实现的时候有几个细节容易漏一是必须处理广播地址0x00二是寄存器读写请求的起始地址要跟主站完全一致三是在总线上没有主站请求时从机不要主动发数据静默等待。我最初把CSV循环上报的逻辑和Modbus从机逻辑混在一个跑结果从机被自己的串口发送干扰了帧接收后来把两种模式用宏隔离开调试阶段和正式运行阶段分别编译问题就消失了。5.3 I2C总线异常恢复与低功耗唤醒策略I2C在工业现场偶尔会被干扰锁死现象是主控发SCL正常但SDA一直被拉低或者传感器不回ACK。重启MCU往往只解决一时因为总线被锁的原因依然存在。我在固件里加了一个恢复函数如果连续三次读取超时就把原本配置为I2C功能的SCL引脚临时改回普通GPIO输出手动输出9个时钟脉冲让挂在总线上的从机从错误状态里跳出来然后再把引脚切回I2C功能重新初始化。这个恢复方法本质上是模拟一个标准的总线复位时序适用于大多数I2C和SMBus从机。需要注意发送9个时钟脉冲的节奏要慢一点不能太快时钟高电平和低电平各保持10微秒以上确保从机能够识别到完整时钟。恢复之后再读一次状态寄存器看从机是否重新响应。低功耗场景下还有个容易被忽视的问题PJ85718DM在连续转换模式下工作时并不需要主控持续通信芯片自己会持续转换但当主控决定进入休眠状态总线空闲时间过长有些带SMBus超时功能的芯片会认为总线异常在唤醒后回不到正常的通信状态。所以进入休眠前最好把配置寄存器改为单次转换模式或直接关闭转换唤醒后再重新写回连续转换模式然后读取温度。这样能避免莫名其妙的总线锁死问题。6. 三个我实际踩过的坑以及最后的调试体会6.1 远程二极管极性接反的迷惑现象第一个坑是在做首版样板时踩的。我把远程二极管的D和D-极性画反了结果上电后远程温度读数直接是-99℃或者一些毫无规律的跳变值。当时我第一反应是怀疑芯片有问题或者是I2C驱动读错了寄存器折腾了小半天还动过几次滤波参数最后用万用表量了引脚才发现是二极管的极性前后反了。为什么极性接反会导致如此离谱的值因为远程通道内部的电流源有固定的激励方向极性反了以后PN结没有被正向驱动芯片采到的根本不是正常的Vbe差值。更麻烦的是有时候反接并不会让状态寄存器直接置位只会让温度读数完全不可信。给我的教训是打板之前把丝印画清楚D和D-的丝印位置必须以实际器件引脚为准调试时先用万用表二极管档确认二极管方向和PCB丝印一致。遇到远程温度读数发疯第一件事不是怀疑算法而是用万用表量D到D-之间的二极管压降确认有没有接反或者短路。6.2 上拉电阻选大造成的总线挂死第二个坑是上拉电阻数值不对。首版我按照普通板内I2C的经验选了10k欧姆上拉最初板内模式还能正常工作后来把传感器板通过30厘米排线外接出去发现总线时不时会挂死尤其是在环境温度升高、设备工作一段时间以后。原因在于30厘米排线本身有分布电容10k上拉电阻的抗干扰能力太弱开漏下拉释放后信号边沿爬升太慢超过SMBus的上升时间限制传感器端的接口电路产生误判。更糟的是一旦某个从机内部状态错乱SDA就会被锁死在低电平主控怎么也拉不回来。后来我把两条上拉电阻改成1k欧姆总线挂死的现象基本上消失了。修改之后我还特意量了SDA的上升沿在10k时大概有几微秒的斜坡1k时明显陡峭很多。如果你手头的应用环境线更长比如超过半米建议直接把上拉电阻放到总线的物理两端各放一个而不是只挂在主控板这一头。6.3 休眠唤醒后第一个温度帧读到旧值第三个坑发生在做低功耗版本的时候。整套设备用电池供电MCU每隔几秒醒来采集一次温度数据然后继续睡觉。第一次醒来读到的温度总是上次休眠前的旧值而且持续很长一段时间才慢慢更新到当前温度。这个现象说穿了很简单芯片在睡眠期间没有持续转换唤醒后立刻进入读取流程读到的温度寄存器还是上次转换结束时留下的值并非当前温度。解决办法是在唤醒后丢弃第一帧或者干脆等待一次新的转换周期完成后再读取。我最终的做法是唤醒后先写配置寄存器触发单次转换然后延时足够时间再去读温度数据。这类问题在连续运行模式下完全不会出现因为你随时读到的都是最新值但只要涉及低功耗间歇唤醒就必须把转换时间和数据有效性问题放在一起考虑。我把转换超时时间设定在200毫秒左右留足裕量实测下来唤醒后大概300毫秒内就能拿到新鲜的温度数据对HVAC这种温度变化缓慢的应用来说响应速度已经完全足够。做完这套方案我最大的感受是在嵌入式和HVAC温度监控里传感器的绝对精度反而不是最难搞的部分稳定性和抗干扰才是真正的战场。PJ85718DM和PIC18F86K22的组合把数字温度采集、远程二极管检测、灵活上报这些能力都集中到了一个很简明的架构里只要在寄存器配置、布线上稍微用心整条链路就能跑得非常可靠。后续如果要继续扩展我会把远程通道贴到电机绕组和功率管附近再配合PIC18F86K22的PWM输出做自动风扇调速让两路温度数据真正参与控制闭环那又是另一个值得记录的项目了。