前段时间做了一套风机盘管的温控改造要求并不复杂面板附近测一个温度作为回风温度参考风管深处再测一个作为出风温度两个值都要参与启停控制还要顺便监控主控板自己的板温。选型绕了一圈最后定了 PJ85718DM 加 PIC18F65K40 这套组合。PJ85718DM 是常见的 I2C 接口高精度温度传感器模块PIC18F65K40 则是 40 管脚的 8 位 MCUI2C 外设稳定、管脚充裕做本地和远程两路温度采集绰绰有余。这篇文章不准备重复数据手册而是把硬件怎么接、I2C 怎么配、远程传感器走线要考虑什么、HVAC 现场调试容易踩的坑一条条摊开讲。适合正在做温度采集、或者想用数字传感器替代 NTC 和模拟输出的开发者参考。1. 项目原由与整体设计思路1.1 为什么要分开监测本地温度和远程温度在嵌入式系统里本地温度通常指单片机周围板温主要用来做自我保护。比如发现板子长时间工作在 85℃ 以上就要降频、降载或者报警。在 HVAC 场景里本地温度又可能是控制面板上的回风温度而远程温度则装在风管末端、空调箱出风口或者机房角落。这两个测点之间的物理距离往往只有几十厘米到几米但热环境差异非常大面板附近可能紧挨着电源模块和功率管风管里则是被调节过的空气。只测其中一个要么保护误动作要么控温不准。这个项目最关键的认知是本地温度和远程温度从一开始就应该当成两个独立通道来做而不是先测一个再靠软件估算另一个。电磁阀、压缩机启停导致的发热变化风道里的气流速度甚至阳光直射都会让两个测点的温度变化完全不同步。如果只放一个传感器温度控制就会慢半拍或者在保护逻辑里出现误判。还有一层意思容易被忽略就是电气距离。远程传感器往往不在主控板上需要通过线缆接到端子排甚至接到另一块独立小板上。这时候总线电容、连接器接触电阻、供电压降都会变成实际问题。这些问题在样机阶段几乎不出现因为大家都在桌面上用杜邦线短接测试一装进风柜里就翻车。后面会专门讲远程走线这部分也是这篇文章里最值得看的内容之一。1.2 组件选型背后的几点考虑先说 PJ85718DM 模块。它的优势非常直接I2C 数字接口主控端不需要 ADC 通道精度高典型误差可以做到 ±0.25℃ 到 ±0.5℃ 这个量级多个模块可以挂同一条总线只要地址不冲突。相比之下NTC 热敏电阻虽然便宜但要接 ADC、要处理分压电阻温漂、要考虑长线上的线阻误差批量校准也很麻烦。数字传感器直接给出温度值系统校准和长时间运行后的复测都轻松很多。再说 MCU 选型。PIC18F65K40 是典型的 40 管脚 8 位机自带 I2C/SPI 硬件外设MSSP 模块、ADC、EEPROM、PWM做温度采集和控制完全够用。选择它的原因不是指标有多惊艳而是资料多、代码简单、管脚充裕能在一颗芯片里把温度采集、继电器控制和参数存储都解决了不用额外加扩展芯片。实际项目里PJ85718DM 和 PIC18F65K40 之间只需要接 4 根线电源、地、SDA、SCL。本地传感器直接靠近 MCU 小板放置远程传感器通过端子排引出线缆。整体方案走下来硬件部分几乎没有需要调试的模拟电路这省掉了我很多原本用来对付 ADC 噪声和参考电压漂移的时间。2. 硬件连接与远程通道设计2.1 PJ85718DM 模块要点与上电检查拿到 PJ85718DM 模块先别急着接线把引脚认清楚。这类模块通常是 4 到 6 个引脚VCC、GND、SDA、SCL部分版本带 A0/A1/A2 地址选择焊盘。供电一般按 3.3V 设计板子上多半已经集成好上拉电阻和去耦电容。上电前用万用表量一下 VCC 和 GND 之间有没有短路再确认没有接反这个步骤花不了 30 秒但能避免后面烧模块的麻烦。地址焊盘是接下来最容易出错的地方。以常见的 7 位 I2C 地址为例芯片缺省地址通常和型号相关比如 0x18 这类基础地址然后由 A2/A1/A0 三个引脚的电平组合出多个可用地址。如果只挂一个模块地址用默认值就行如果要挂两个或更多就必须把地址错开。远程和本地两个传感器挂在同一条总线上时地址冲突会导致数据完全错乱。这类模块的测量分辨率通常可以配置常见的是 12bit 或 13bit对应的温度量化步长分别是 0.25℃ 和 0.0625℃。HVAC 控制其实不需要极致精度0.0625℃ 的量化已经足够但如果现场干扰大可以把分辨率调低一档用更慢的转换速度换更稳定的读数。模块自身功耗很低自热效应很小这里要注意的是外部热源对它的影响。PCB 上的大电流走线、变压器、MOS 管、稳压器都会把热量传导过来安装时尽量让传感器远离这些热源。提示不同批次、不同版本的 PJ85718DM丝印和默认地址可能有差异。不要盲目照抄网上代码里的地址先看模块丝印或数据手册确认。2.2 本地/远程通道在 PIC18F65K40 上的资源配置PIC18F65K40 有多个 MSSP 外设理论上可以配置出独立的两路 I2C。我在这个项目里推荐双 I2C 通道方案本地传感器挂在 I2C1 上远程传感器挂在 I2C2 上。好处很直接两条总线互相隔离远程线缆如果出了故障最多影响 I2C2本地温度采集不会跟着瘫痪。如果只有一路 I2C 可用也可以把两个模块挂同一条总线用不同地址区分这时候远程总线电容会拖慢整条总线的通信速度而且总线卡死时会一锅端。下面这张表是我在设计时做的通道分配可以参考通道用途器件备注I2C1本地温度PJ85718DM 模块1靠近 MCU负责板温和回风温度参考I2C2远程温度PJ85718DM 模块2通过屏蔽双绞线拉到风管末端GPIO压缩机 / 风机控制继电器或接触器使用 P 沟道管或 ULN2003 驱动EEPROM校准参数存储MCU 内置 EEPROM保存传感器地址和温度偏移量电源方面本地和远程模块尽量分开供电各加一个 100nF 陶瓷电容和一个 10μF 电解电容。远程模块如果距离主控板超过 1 米不要直接依赖线缆另一端那点电压最好在远端传感器小板旁边加一颗低功耗 LDO把电源压降问题彻底解决。还有一个细节I2C 总线上拉电阻的位置。近端上拉只能解决近处的电平恢复远程总线太长时远端信号沿会变得很慢。比较好的做法是近端保留 4.7kΩ 上拉在远程模块端再补一个 2.2kΩ 或 4.7kΩ 上拉具体阻值通过示波器边沿测量来确定。2.3 远程 I2C 走线的可靠性与计算I2C 是开漏结构靠上拉电阻把信号拉高本身不适合长距离传输。距离一长线缆电容会拉慢上升沿严重时从机根本没有足够的时间采样。工程上可以用这个公式估算上升时间 tR ≈ 0.8473 × 上拉电阻值 × 总线总电容。I2C 标准模式要求上升时间不超过 1000ns快速模式要求不超过 300ns。举一个实际例子。假设远程线缆单位长度电容是 100pF/m3 米线加传感器引脚和连接器总线总电容约 350pF。上拉电阻用 4.7kΩ算出来 tR ≈ 0.8473 × 4700 × 350pF ≈ 1.39μs。这个值超过了快速模式的 300ns标准模式 100kHz 下勉强能用。如果线拉长到 5 米总线电容到 550pFtR 大约 2.19μs连标准模式都很吃力通信会时好时坏。处理办法有几个。第一降速把 I2C 时钟从 400kHz 降到 100kHzHVAC 温度采集根本不需要高带宽一个温度数据才两个字节。第二降低上拉电阻到 2.2kΩ 甚至 1kΩ但要检查低电平灌电流。以 3.3V 供电、VOL 允许 0.4V 为例2.2kΩ 电阻的低电平电流约等于 (3.3 - 0.4) / 2200 ≈ 1.3mA大多数从机都能承受1kΩ 时约 2.9mA需要查数据手册确认。第三超过 1 米或者现场布线环境恶劣优先考虑加总线缓冲器或中继芯片。这类芯片能把一条长 I2C 总线分成两段近端和远端各自独立驱动远端电容不会影响了近端时序。第四接线方式上SDA 和 SCL 要绞在一起使用屏蔽双绞线屏蔽层在主控板端单点接地不要在远端也接地形成地环路。线缆尽量不要和 220V 交流线、继电器反馈线走同一个线槽如果要平行走线至少隔开 5 厘米以上。3. 固件实现与温度数据链路3.1 I2C 外设初始化与总线扫描在 PIC18F65K40 上配置 I2C 主模式核心是算对 SSPADD 值。时钟公式是SSPADD FOSC / (4 × I2C 频率) - 1。如果系统时钟用 8MHz目标 I2C 频率 100kHz那 SSPADD 8000000 / (4 × 100000) - 1 19。这个值写在初始化寄存器里就行。初始化代码可以这样写#define FOSC 8000000UL #define I2C_FREQ 100000UL #define SSPADD_VAL (FOSC / (4UL * I2C_FREQ) - 1UL) void i2c1_init(void) { SSP1CON1 0b00101000; // 主机模式 SSP1ADD SSPADD_VAL; SSP1CON1bits.SSPEN 1; // 使能 MSSP }不同系列的单片机寄存器名可能不一样但原理是通用的。配好 I2C 之后第一步不是急着读温度而是先跑一遍总线扫描。扫描的思路很简单对总线上每个可能的 7 位地址发起 START 条件、发送写地址字节然后等待 ACK。如果收到 ACK说明这个地址上有设备在线。uint8_t i2c_scan_bus(void) { uint8_t addr; for (addr 0x08; addr 0x77; addr) { i2c_start(); if (i2c_write(addr 1) 0) // 收到 ACK { i2c_stop(); return addr; } i2c_stop(); } return 0xFF; // 没扫到设备 }这个扫描函数非常值得放到上电自检里。它能立刻暴露几个典型问题传感器没有供电、地址焊盘配置错了、SDA 和 SCL 接反、远程线缆断了。我在这个项目上电后第一次跑扫描就发现远程传感器地址被设置成了和本地传感器同一个地址改完跳线帽才恢复正常。3.2 读取温度寄存器的寄存器流程I2C 数字温度传感器的读取流程比较统一但不同芯片的寄存器地址有差异。常见的流程是先写一个命令字节把内部指针指到温度寄存器然后通过 Restart 条件把总线方向切到读连续读两个字节最后 STO P。以我用的这类模块为例温度寄存器通常位于 0x0516 位数据中高 13 位或高 12 位有效。读温度的核心函数可以这样写typedef struct { int16_t raw; float degc; } temp_reading_t; temp_reading_t read_temperature(uint8_t dev_addr) { uint8_t buf[2]; i2c_start(); i2c_write(dev_addr 1); // 写地址 i2c_write(0x05); // 温度寄存器指针 i2c_restart(); i2c_write((dev_addr 1) | 1); // 切到读 buf[0] i2c_read(1); // 第一字节主机回 ACK buf[1] i2c_read(0); // 第二字节主机回 NACK i2c_stop(); uint16_t raw ((uint16_t)buf[0] 8) | buf[1]; int16_t sign_val (raw 0x1000) ? (raw | 0xF000) : raw; temp_reading_t r; r.raw (int16_t)sign_val; r.degc (float)r.raw * 0.0625f; // 13bit 模式 return r; }这里有两个容易错的地方。第一符号位。13bit 格式下温度数据最高位是符号位读取后要先做符号扩展否则负温度会被当成很大的正数。第二LSB 值。13bit 模式下 1 LSB 等于 0.0625℃如果配置成 12bit 模式LSB 变成 0.25℃换算系数必须跟着改。我见过不少问题都是代码里固定写死一个系数换分辨率配置后温度读数整体偏高或偏低。读完之后还要做合理性检查。工业现场偶尔会有瞬时干扰让读到的原始值变成 0xFFFF 或者 0这时不能直接把数据丢给控制逻辑。比较稳妥的做法是连续三次读到无效值才把传感器标记为离线单次无效时沿用上次的有效值避免一个偶发错误让压缩机突然停机。3.3 滤波、回差与无效值处理HVAC 控制最怕温度在设定值附近来回抖。输出端接的是压缩机温度读数每秒钟跳 0.1℃控制逻辑也跟着每秒钟启停一次继电器很快就会废掉。解决思路有三层。第一层是滑动均值滤波。维护一个长度为 8 的环形缓冲区每次新采样覆盖最旧的值输出缓冲区平均值。这样可以把高频抖动抹掉同时保留对真实温度变化的响应能力。#define FILTER_N 8 float filter_buffer[FILTER_N]; uint8_t filter_idx 0; float filter_add_sample(float sample) { filter_buffer[filter_idx] sample; filter_idx (filter_idx 1) % FILTER_N; float sum 0.0f; for (uint8_t i 0; i FILTER_N; i) { sum filter_buffer[i]; } return sum / FILTER_N; }第二层是限幅滤波。如果新采样值和上一次有效值之差超过 1℃就认为这次采样受到干扰直接丢弃。这个阈值不需要很精确它只是为了拦掉明显异常的数据点。第三层是回差控制。所谓回差就是让启动阈值和停止阈值之间留一个滞回区间。比如制冷模式回风温度高于 25℃ 时启动压缩机降到 23℃ 时才关闭压缩机中间 2℃ 就是回差。这样做可以让启停次数大幅下降延长压缩机寿命。无效值处理也要和滤波配合好。我习惯把任何一个测量函数都设计成“返回是否有效”的形式主循环里根据有效标志决定是否更新控制量。如果连续几秒都是无效数据说明传感器真的出了问题此时应该点亮故障指示灯而不是继续使用过期数值。4. HVAC 现场调试与疑难排查4.1 温度偏高、跳变与远程断线的排查思路现场最常见的故障是本地温度偏高。模块本身没问题就是读数比水银温度计高 2℃ 左右这种情况十有八九是安装位置离热源太近。我这次就把本地传感器模块放在了一个线性稳压器附近通电半小时后模块读数和红外测温仪差了将近 2.5℃。解决方法是把模块挪到 PCB 角落并且用开槽的方式阻断铜皮导热路径读数立刻恢复正常。调整传感器物理位置永远优先于软件补偿。远程温度跳变则要优先怀疑接线和连接器。一个典型现象是线缆轻轻一碰读数就从正常值变成 -40℃ 或者 0xFFFF。这说明 I2C 总线上出现了接触不良主机读到的数据已经不是完整帧。处理方法很简单先把连接端子重新压接一遍再用刀片刮一刮氧化表面如果问题还在就换一根线束对比。如果现场有示波器排查效率会高很多。看 SDA 和 SCL 两个引脚低电平如果明显高于 0.4V说明总线拉低能力不足或者远端供电电压跌得太低上升沿如果特别缓说明上拉电阻太大或者总线电容太大。另一个典型问题是远程模块电源电压在线缆上掉了 0.3V 以上这时候传给 I2C 的高电平可能不够 MCU 的输入阈值通信时断时续。下面这个表是我常用的现场排查速查现象可能原因建议处理温度总是偏高 2℃ 左右传感器靠近热源或受气流影响调整安装位置开槽隔热读数跳到 -40℃ / 0xFFFF总线接触不良、线缆断裂重新压接端子替换线束读数变化过于缓慢滤波长度太长或采样周期太短缩短滤波窗口检查采样节奏I2C 通信时好时坏总线电容过大、上拉不足降速到 100kHz减小上拉电阻本地正常、远程 NACK远程地址配置错误或供电不足扫描总线确认地址补远端稳压4.2 I2C 总线卡死现象与恢复方法I2C 总线卡死是做这个方案绕不开的坑。现象非常明确SDA 一直被拉低SCL 还在正常翻转主机无论发什么地址都收不到 ACK程序卡在读函数里出不来。原因通常是通信过程中被噪声打断从机内部状态机认为会话还没有结束于是继续拉住 SDA。恢复的关键在于给从机足够的时钟脉冲让它把内部状态机走完。具体做法是先把 I2C 外设关闭把 SCL 和 SDA 两个引脚切换成 GPIO 输出然后在 SDA 保持高电平的同时对 SCL 连续输出 9 个脉冲让从机完成内部复位。最后产生一个 STOP 条件。void i2c_bus_recover(void) { i2c_disable(); // 关闭 MSSP 外设 SCL_TRIS 0; SDA_TRIS 0; SDA_LAT 1; // 释放 SDA for (uint8_t i 0; i 9; i) { SCL_LAT 0; __delay_us(5); SCL_LAT 1; __delay_us(5); } SDA_LAT 0; // 产生 STOP SCL_LAT 1; __delay_us(5); SDA_LAT 1; __delay_us(5); i2c_init(); // 重新初始化 I2C }软件层面一定要配套超时保护。很多人写的 I2C 读写函数是死等 ACK一旦总线卡死整个主循环就停在那里看门狗都不管用。正确做法是给每个 while 循环加一个超时计数比如超过 10ms 还没有等来 ACK就退出并执行总线恢复流程。HVAC 设备常年通电现场继电器、接触器动作产生的干扰不可避免没有超时机制等于埋了一颗定时炸弹。4.3 校准操作与误差分配数字温度传感器的绝对精度通常已经很不错但装进设备后还是会受到机械应力和热传导的影响批量一致性也需要校准。我的做法是设备整机装好后先通电稳定 30 分钟再进行一次单点偏移校准。单点校准最简单的方法是使用冰水混合物作为 0℃ 参考。找一个保温杯装满碎冰再加水等冰水共存、温度稳定之后把传感器探头插进去注意不要让探头直接接触杯壁。记录传感器读数比如读到了 0.5℃参考值是 0℃那偏移就是 -0.5℃把这个偏移量写进 MCU 的 EEPROM。软件最终输出温度时要加上这个偏移。如果项目对精度要求更高可以做两点校准。比如在冰水混合物里取 0℃在恒温槽里取 40℃分别记录传感器读数和参考温度。然后计算校准斜率和截距公式就是 y kx b。不过 HVAC 应用一般不需要走到这一步单点偏移把绝对误差从 ±1℃ 压缩到 ±0.5℃ 以内已经足够。校准环节还有个容易忽略的点不要对人体温度做校准。体温不稳定探头接触皮肤时的热传递也很差很容易引入更大的误差。我看到过有人拿手捂着传感器调偏移最后生产出来的设备读数全是偏的。5. 扩展方向与个人经验5.1 距离再远一些的替代方案如果项目未来要把远程测温距离扩大到 10 米以上I2C 继续硬撑就不太划算了。I2C 是开漏单端传输天生不适合长距离。即使加了缓冲器长距离下抗干扰能力依然有限。可以考虑下面几个方向。1-Wire 总线方案可以用在 10 米到 30 米的场景单根数据线就能挂多个传感器布线成本低但时序要求比较严格而且每读一个传感器要等较长转换时间。RS-485 则是更工业化的选择每个远程节点放一个小 MCU 读取 PJ85718DM然后通过 RS-485 把温度值转发给主控距离可以到几百米甚至上千米抗干扰能力强很多。如果只想在现有 I2C 方案上稍微扩展还有一种做法是用 I2C 多路复用器。主控连接一个复用器每个远程通道单独隔离开来即使某条通道上模块故障也不影响其他通道。多路复用器需要的 GPIO 或地址线不多比较适合机柜内多个温度探头的场景。5.2 几个让项目收尾更顺的小建议开发板上电后建议第一件事就跑 I2C 总线扫描把地址冲突和接线问题暴露在最早阶段。这个习惯帮我省了大量调试时间。把每个传感器的地址、安装位置、校准偏移量用表格记录下来现场换件时能直接恢复配置不用重新测一遍温度。预留清晰的测试点和调试串口也很重要。我在主板上预留了一组排针把 SDA1、SCL1、SDA2、SCL2 都引出来调试时示波器探头直接夹上去不用在电路板上乱找焊点。调试串口打印传感器原始值和滤波后的数值定位问题比看 OLED 屏幕直观得多。信号线和功率线分开布线这个事怎么说都不过分。继电器、接触器动作瞬间产生的电磁干扰如果耦合进 I2C 线缆轻则读数跳变重则总线卡死。我在这个项目里把传感器线缆和 220V 控制线分在两个线槽间距拉开后误码率几乎降到了零。提示不要默认所有 PJ85718DM 模块出厂配置都一样。每次拿到新批次模块先做一次地址确认再接入系统。最后说点个人体会。这套方案没有用到任何复杂外设但正因为简单长期运行才可靠。项目做完后设备在现场连续跑了一个多月读数和参考温度计稳定在 ±0.5℃ 以内。如果非要说哪个环节最花时间那肯定是远程线缆的接插件工艺和现场布线而不是 I2C 驱动代码。数字温度传感器带来的开发便利是实实在在的但安装位置、线缆质量和连接器可靠性才是这类项目真正的胜负手。