☰
PIC18F85J10与PJ85718DM:HVAC嵌入式I2C温度采集方案详解
2026/10/22 6:39:14 网站建设 项目流程

做嵌入式温度采集,尤其暖通空调(HVAC)这类要在电控箱里长期连续运行的控制器,最常见的方案就是一颗 8 位单片机接几颗 I2C 数字温度传感器。这次要讲的项目,就是用 PIC18F85J10 作为主控,通过 I2C 总线读取 PJ85718DM 数字温度传感器,同时监测本机温度和远端探头温度,覆盖嵌入式设备本板防过热和 HVAC 现场空气温度反馈两类场景。适合正打算做温控器、风阀控制器、机柜温度监测,或者单纯想把多路温度采集做稳定的朋友参考。

整个项目做下来,真正花时间的其实不是“读出温度”本身,而是怎么把本地和远程温度都读得准、读得稳,以及现场长线供电、变频器干扰、传感器故障之后控制器该怎么表现。下面按我在实际调试中整理的顺序,把方案选型、硬件接线、固件实现、HVAC 落地和排错经验一次说清。

1. 先把方案盘清楚:为什么是这颗 MCU 和这颗传感器

1.1 项目需求拆解

标题里的信息量其实不小:PJ85718DM 负责把温度变成数字量,PIC18F85J10 负责采集、处理并执行控制,应用场景是嵌入式设备和 HVAC。拆开看,这个项目有四个层面的硬需求。

一是本地温度监测。嵌入式设备里要防止主控板局部过热,比如电源芯片、功率管区域温度异常时提前告警。二是远程温度监测。HVAC 系统里,控制回风温度、送风温度、室外环境温度时,传感器不可能都装在 PCB 上,必然有一段线缆把探头引到风管或室外。三是控制逻辑。单纯“测温度”没有意义,必须根据温度差去驱动继电器、接触器或者给上位机报数据。四是长期稳定性。HVAC 设备不是实验室几分钟跑完就结束,常常几个月不停机,温度传感器一旦失效或者总线被干扰,控制器不能无故宕机或误动作。

所以这个项目不是一个“点灯式”的测温 demo,而是一个偏工业可靠性的综合采集控制问题。PJ85718DM 在我手头的定位是低成本、免校准、数字化传输的主力探头,配合 PIC18F85J10 的 MSSP 接口,既能把本地探头直接贴在主板上,也能把第二颗、第三颗探头拉到远端。

1.2 PIC18F85J10 为什么够用

选 PIC18F85J10 不是因为它性能多强,而是因为它在这个场景里足够匹配,且整套成本压得住。

这颗 8 位机属于 PIC18 J 系列,工作电压范围宽,适合 3.3V 主控系统;内部带多个 MSSP 模块、UART、定时器和 EEPROM。以温度采集这种任务来说,每秒最多处理几路 I2C 数据,运算量根本谈不上压力,真正有用的是它有独立的 MSSP 硬件模块,CPU 只需要操作寄存器就能完成 I2C 通信,不用软件模拟时序,这会省掉大量调试时间。

另一层考虑是 EEPROM 和看门狗。HVAC 现场要保存设定温度、传感器偏移校准值、故障次数统计,内部 EEPROM 能直接做掉,不用外挂存储。看门狗则保证程序卡死或总线异常时能自动复位,对于无人值守设备是刚需。再加上 J 系列引脚多,后面要扩展按键、LCD 1602、蜂鸣器、继电器驱动,I/O 完全够用。

1.3 PJ85718DM 是干什么的

PJ85718DM 是一颗低功耗 I2C/SMBus 接口数字温度传感器,寄存器模型和市面上常见的兼容型数字温度芯片类似:内部有温度 ADC,出厂前做过校准,直接通过 I2C 读寄存器就能拿到带符号的温度数据,不需要像 NTC 热敏电阻那样做分压电路、查表、逐批标定。它带 A0-A2 地址引脚,一条 I2C 总线上最多可以挂 8 颗,所以“本地和远程温度”这个需求最直接的实现方式,就是同一型号放两颗或者多颗,每颗配一个不同地址。

相比 NTC 方案,数字传感器最大的优势是免标定和抗干扰。NTC 在 HVAC 里用了很多年,但阻值分散性大,每块板子都要校准,长线传输时线阻还会直接叠加进误差;而 PJ85718DM 这种数字传感器,温度先在自己内部变成数字量,长线传输的是 I2C 电平信号,对线阻不敏感。相比 1-Wire 单总线方案,I2C 的时序容错更好,逻辑分析仪抓波形也直观,排查起来简单得多。

2. 硬件和接线:本地温度与远程温度是怎么接出来的

2.1 最小系统和电源处理

PIC18F85J10 的最小系统按照数据手册搭就行,但有几个细节在 HVAC 场景下特别容易踩坑。供电我建议用 3.3V LDO 单独给 MCU 和传感器供电,如果系统里有继电器或接触器,那一路驱动电源要彻底隔离,至少做到地线分开。实测继电器吸合瞬间如果电源抖动超过 0.3V,I2C 通信就会出现随机失败,温度读数偶尔跳变。

MCU 的 VDD/VSS 引脚比较多,每一对都要就近放 0.1uF 陶瓷电容,总入口再加 10uF 电解电容。晶振用外部晶体而不是内部振荡器。PIC18F85J10 内部振荡器虽然能用,但 I2C 时序和 UART 波特率在温度变化大的现场会受影响,外部 8MHz 晶体配合内部 PLL 或直接分频是最稳的选择。

本地传感器如果贴在 PCB 上,要避开板上的发热源。我做过一次实测,把传感器放在 LDO 旁边 5mm 处,读数比旁边冷风道里的温度高了 2.3 度,这种偏差不是软件能完全补掉的,布局阶段就要留出距离。远程传感器则通过端子引出,供电、地、SDA、SCL 四根线,采用屏蔽双绞线,这是 HVAC 现场最标准的接法。

2.2 I2C 上拉电阻和地址分配

I2C 总线是开漏结构,SDA 和 SCL 必须有上拉电阻才能工作。上拉阻值选择会直接影响通信稳定性,阻值太大上升沿太缓,阻值太小灌电流偏大。3.3V 系统、100kHz 标准速率下,总线上挂两颗传感器、线长半米以内,用 4.7k 上拉没问题;如果远程探头线长超过 2 米,建议把上拉降到 2.2k,甚至可以在远端传感器附近再补一个上拉电阻。

地址分配属于“接线前就要想好”的事。PJ85718DM 的三个地址引脚高低电平决定 7 位地址的低三位。我的习惯是第一颗本地探头地址 A0-A2 全接 GND,得到基准地址 0x48;第二颗远程探头把 A0 接 VDD,地址变成 0x49。换算成 I2C 8 位地址时,本地是 0x90,远程是 0x92,这个值在固件里直接写死,比运行时再去扫描总线省事,也不会因为总线挂异常设备导致误判。

2.3 远程探头的走线、距离和抗干扰

远程测温最大的坑是线缆电容和现场干扰。I2C 设计初衷是板级总线,不是长线传输总线。100kHz 速率下,普通双绞线走 2 米以内问题不大;走到 5 米时,线电容会明显拖慢上升沿,这时候要么把速率降到 10kHz,要么选低电容屏蔽线,要么在远端也加上拉。我实际项目中有一段 8 米长的探头线,降速后跑得很稳,但再往上加长度就不划算了。

如果现场走线非常长,超过 10 米,我不建议继续硬扛 I2C。更合理的是改成 RS485 分布式采集,每个区域节点用一颗小单片机读就近的传感器,再把结果通过 Modbus 协议上传给主控。标题里的 PIC18F85J10 在 HVAC 系统里完全可以作为区域节点的主控,这样每段 I2C 都保持短距离,区域之间的长线交给 RS485,抗干扰能力会强很多。

屏蔽线的处理也有讲究:屏蔽层只能单端接地,接到控制板的 GND,不能接到远端传感器那一侧的 GND,否则会形成地回路,反而把干扰引入总线。我在现场就遇到过把屏蔽层两头都接地之后,压缩机一启动温度读数就跳 1 度的情况,改成单端接地后立刻干净了。

2.4 端子处的保护电路

HVAC 现场电磁环境比实验室恶劣得多,变频器、压缩机、接触器启停时会有很强的空间干扰。传感器端子引到设备外部,还需要考虑静电和误接。我习惯在每个远端端子的 SDA、SCL 对地各加一颗 TVS 管,电源入口加一颗防反接的肖特基二极管,SDA、SCL 再各串 100Ω 到 220Ω 的电阻,限制异常电压的冲击电流。这些元件成本不高,但能避免现场一根线接错就烧掉一片传感器。

本地传感器如果和控制板共电源共地,保护可以简化,但也不建议省略。很多 HVAC 控制器长期带电维修,维修人员用万用表表笔碰到端子是常有的事,有了 TVS 和串阻,至少不至于直接烧到主控的 MSSP 引脚。

3. 固件实现:从 I2C 波形到稳定温度值

3.1 I2C 外设初始化

PIC18F85J10 的 MSSP 模块配置成 I2C 主机模式,用的是标准做法。下面这段初始化代码在常用的 PIC C 编译环境里可以直接用,关键是 SSPADD 的计算:主模式时钟等于 Fosc / (4 × (SSPADD + 1)),要 100kHz 时反向算出加载值即可。

#define _XTAL_FREQ 8000000UL void I2C_Init(void) { // 主机模式,时钟 = Fosc / (4 * (SSPADD+1)) SSPCON1 = 0x08; SSPCON2 = 0x00; SSPSTAT = 0x00; // 7位地址、标准电平 SSPADD = (_XTAL_FREQ / (4UL * 100000UL)) - 1; // 100kHz SSPCON1 |= 0x40; // 使能 MSSP }

如果远程探头线变长,需要把速率降到 10kHz,只要把 100000 换成 10000,SSPADD 会自动变大,时钟变慢,I2C 上升沿的要求就宽松很多。这个参数我在调试时反复改过,10kHz 下 5 米双绞线完全能跑,只是采集速度慢一点,但温度本身变化慢,500ms 轮询一轮完全感受不到延迟。

3.2 一次完整的温度读取流程

PJ85718DM 的温度读取流程和同类寄存器兼容芯片一致:先发送起始条件,写器件地址加写位,再写温度寄存器指针 0x00,然后重启总线,发器件地址加读位,连续读两个字节,最后发停止条件。读回的高字节和低字节拼成一个 16 位数值,右移后乘以 0.0625 就是摄氏度。

#define SENSOR_LOCAL_ADDR7 0x48 // 本地探头 #define SENSOR_REMOTE_ADDR7 0x49 // 远程探头 unsigned char I2C_WriteByte(unsigned char dat) { unsigned char res; SSPBUF = dat; while (!SSPIF); SSPIF = 0; res = ACKSTAT ? 1 : 0; // ACKSTAT=1表示无应答 return res; } unsigned char I2C_ReadByte(unsigned char sendAck) { unsigned char dat; RCEN = 1; while (!SSPIF); SSPIF = 0; dat = SSPBUF; ACKDT = sendAck ? 0 : 1; // 1发ACK,0发NAK ACKEN = 1; while (!SSPIF); SSPIF = 0; return dat; } unsigned int ReadTempReg(unsigned char addr7) { unsigned char msb, lsb, err; SEN = 1; while (!SSPIF); SSPIF = 0; // START err = I2C_WriteByte(addr7 << 1); // 写地址 if (err) return 0xFFFF; err = I2C_WriteByte(0x00); // 温度寄存器 if (err) return 0xFFFF; RSEN = 1; while (!SSPIF); SSPIF = 0; // 重启 err = I2C_WriteByte((addr7 << 1) | 1); // 读地址 if (err) return 0xFFFF; msb = I2C_ReadByte(1); lsb = I2C_ReadByte(0); PEN = 1; while (!SSPIF); SSPIF = 0; // STOP return ((unsigned int)msb << 8) | lsb; }

读回裸寄存器值之后,转成温度数值有两个注意点。一是要考虑符号位,温度零下时 16 位值转成有符号数再右移,右移时按带符号处理。二是温度高位和低位拼成 int 后,直接做算术右移,不要无脑除以 16,负数除法的行为和右移不一致,容易在零下温度时出错。

signed int code = (signed int)raw; signed int temp12 = code >> 4; // 12位有符号温度码 long temp_x100 = temp12 * 25L / 4; // 扩大100倍的温度,1LSB=0.0625度

用扩大 100 倍的定点数表示温度,是为了后面控制逻辑不需要浮点数。比如 23.4375 度,temp_x100 等于 2343,和设定值 2200 比较时直接用整数比较,没有浮点运算,在 8 位机上省很多指令。

3.3 滤波、校准和故障剔除

温度传感器读数不能拿来直接用,至少要过三关:滤波、校准、故障剔除。

滤波方面,我实测最有效的是“中值滤波 + 滑动平均”组合。每路传感器连续读 5 次,取中间值,再把这 5 次的中间值放进最近 8 个有效样本的环形缓冲里求平均。这样既挡掉了单次毛刺,又让温度变化曲线足够平滑。HVAC 控制里温度变化本身是秒级甚至分钟级的,多几轮滤波不会拖慢响应,但对抑制变频器干扰非常有效。

校准方面,数字温度传感器出厂误差一般在 ±0.5 度左右,HVAC 大多数场景直接能用,但如果你对绝对精度有要求,可以用冰水混合物和精密水银温度计做单点偏移校准。偏移量保存在 PIC18F85J10 的内部 EEPROM 里,下次上电自动加载。注意是单点偏移而不是斜率校准,对于这种传感器来说单点已经能纠正大部分误差。

故障剔除方面,读回 0xFFFF、0x8000 这种明显异常的值,以及算出来超过 -40 到 125 度的范围,都直接判为无效样本,跳过不参与滤波。连续 5 次读到无效样本,就置故障标志,传感器故障后控制系统按安全状态运行,而不是用一组乱跳的温度去控制压缩机。

3.4 多路轮询和主循环调度

本地和远程两颗传感器不需要同时读,温度变化慢,只要周期性轮询即可。我的做法是在主循环里用一个 10ms 时基,每路传感器 500ms 读一轮,两路就 1 秒一个完整周期。这样每一路都能完成 5 次连续采样、中值滤波、滑动平均,CPU 占有率很低,剩下的时间都留给显示刷新和控制逻辑。

void Task_UpdateTemperatures(void) { unsigned int raw; signed int code; raw = ReadTempReg(SENSOR_LOCAL_ADDR7); if (raw != 0xFFFF) { code = (signed int)raw >> 4; FilterPush(0, code); // 送入本地滤波队列 } raw = ReadTempReg(SENSOR_REMOTE_ADDR7); if (raw != 0xFFFF) { code = (signed int)raw >> 4; FilterPush(1, code); // 送入远程滤波队列 } }

主循环里再放一个看门狗清狗计数。I2C 每次开始、停止、读写都要加超时判断,不能无限等 SSPIF。我习惯在每步操作里用 10ms 超时,超时就退出本次读取并返回错误,这样即使某个传感器把总线拉死,主循环也能继续跑,不会导致整个控制器卡死。

4. HVAC 场景怎么落地

4.1 恒温控制:滞回比 PID 更实用

HVAC 控制里最常见的需求是恒温控制,比如设定 22 度。直接用 PID 在 8 位机上做其实没有必要,大多数商用温控器用的都是滞回控制。滞回的核心是设定一个开启阈值和一个关闭阈值,中间留一个死区,防止继电器频繁通断。

我用定点温度 temp_x100 做比较,设定值 2200 代表 22.00 度,滞回量设置 300,也就是 3 度。实际做下来,加热和制冷切换之间留 0.5 度左右的死区,再配合接触器的机械延迟,继电器一天通断次数能控制在几十次以内,完全不会像 PID 参数没调好时那样疯狂抖动。

4.2 多区域远端扩展的总线设计

一根 I2C 总线上最多挂 8 个 PJ85718DM,这决定了用纯 I2C 做多区域采集时有上限。如果 HVAC 项目里区域超过 8 个,我建议不要把 8 颗传感器全挂在一条远程总线上,因为每颗传感器的线缆都可能走不同方向,地电位差和干扰会互相叠加。

更稳妥的做法是按区域分组,一个区域控制器只负责本区域的两三路温度,区域之间用 RS485 通信。比如一个楼层有 4 个 VAV 区域,每个区域一个 PIC18F85J10 节点,节点之间走 RS485 总线,主控制器统一汇总。这个架构下,I2C 永远只出现在节点内部,距离短、干扰小,整体可靠性和可维护性都明显更好。

如果一定要把多组远程传感器复用到同一颗 MCU 上,可以用 I2C 总线多路开关,把不同组的传感器挂到不同通道,MCU 通过切换通道分时读取。这样其实是用硬件把总线隔离成多个短线段,比一条长线挂多个设备稳定得多。

4.3 报警、参数保存和数据记录

传感器故障和温度超限在 HVAC 里必须区别处理。我的经验是温度超限分两级:第一级是预报警,比如温度超过设定值 5 度,亮黄灯、通过 UART 上报;第二级是动作报警,比如超过 10 度,直接切断制冷或加热输出,防止设备在异常工况下继续运行。传感器本身故障则走另一套逻辑,按停机或按上次有效值估算,绝不能拿一个错误温度去启动压缩机。

设定参数、校准偏移、报警阈值都存在 EEPROM 里。PIC18F85J10 的 EEPROM 操作不复杂,但要注意写入寿命和写周期时间。现场调试时,我不建议频繁通过上位机改写 EEPROM,频繁写会把寿命耗尽。合理做法是把易变参数放在 RAM,只有确认最终参数后才批量保存一次。

5. 调试这几天踩过的坑和速查

5.1 上电读数全是 0xFF,却找不到原因

这种情况十有八九是总线上根本没有从设备应答。我先说排查顺序,按这个顺序查,基本几分钟能定位。

先用逻辑分析仪或示波器抓 SDA、SCL 波形,看 MCU 发的地址字节之后有没有 ACK 低电平。没有 ACK,直接从这几个方向查:传感器供电有没有接对,VDD 和 GND 有没有接反;A0-A2 地址引脚电平是不是自己预期的那样,比如你程序写 0x48,但传感器地址脚全接高就是 0x4F,肯定应答不了;上拉电阻有没有焊虚;SDA 和 SCL 线序有没有在端子上接反。

一个很容易忽略的点是,很多市售温度传感器模块上的丝印 SDA、SCL 和实际引脚顺序不一定一致,焊板前先用万用表二极管档量一下模块上排针和传感器引脚之间的连通关系,比你写完程序再去猜快得多。

5.2 SDA 被从机拉死,软件怎么恢复

I2C 最烦的故障是 SDA 一直为低,整个总线被锁死。常见原因是某颗传感器在上电时序异常时进入错误状态,或者在通信中途 MCU 复位了,而传感器还在等后续时钟。遇到这种情况,单纯重发起始条件没用,必须让传感器收到足够多的时钟脉冲来解锁。

我的处理是在每次 I2C 操作前先判断 SDA 和 SCL 电平,如果 SDA 异常为低,就软件产生 9 个时钟脉冲,然后发一个 STOP 条件,再重新初始化 MSSP。配合看门狗,即使总线被锁,控制器也能在几百毫秒内恢复通信。这个方法对这类寄存器兼容的数字温度传感器都有效。

5.3 远程温度读数跳变,先别怀疑传感器

远程探头读数波动大的时候,多数开发者第一反应是换传感器,但按照我的经验,真正原因往往是走线干扰或滤波不够。我之前出现过现场数据在压缩机启动瞬间跳 3 度的情况,单独测传感器又是好的,最后定位到是传感器线缆和压缩机动力线走在同一个线槽里,屏蔽层又两头接地形成地回路。把动力线分开走、屏蔽层单端接地、远端加上拉、软件再补一道中值滤波后,问题彻底消失。

要注意,不是所有跳变都要靠硬件解决。纯软件的角度,把采样次数从 3 次提高到 5 次、滑动窗口从 4 个变成 8 个,效果也很明显。温度信号本身低频,适当增加平滑不会损失真实响应。

5.4 常见问题速查表

现象可能原因处理办法
所有读数都是 0xFF无 ACK,传感器未应答查供电、地址引脚、上拉电阻、线序
读数固定为 0x00读到了配置寄存器或地址错误确认寄存器指针写入 0x00 后再读
本地读数偏高传感器靠近发热源调整 PCB 布局,加散热隔离
远程读数乱跳干扰、地回路、上拉不足屏蔽层单端接地,降速,加滤波
总线偶尔死锁传感器异常状态软件 9 时钟恢复,配合看门狗
换板子后读不到焊接问题或传感器损坏用逻辑分析仪对比正常板和故障板波形
零下温度显示正数符号位处理错误温度码按有符号右移,再乘 0.0625

我个人在实际调试里最大的体会是,这类温度采集项目真正决定成败的往往不是芯片本身,而是布线和软件容错。先把一颗传感器读稳、连续跑一天看数据曲线,再写控制逻辑,比一上来就堆功能稳妥得多。后面如果再扩展,这个框架可以直接套用到多区域温控、机柜温度监控、冷链数据记录这些类似的场景,换传感器型号、换 MCU 平台,核心思路都一样。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询