☰
PIC18F97J94与PJ85718DM双芯片温度监测方案实战
2026/10/10 11:02:53 网站建设 项目流程

1. 项目缘起与整体设计思路

嵌入式温度监测这件事,说起来简单,做起来坑不少。我最早接触这类需求是在一个工业控制面板的项目里,当时客户要求同时监测机柜内部温度和远端管道温度,精度要求正负0.5摄氏度,还要能通过上位机远程读取。一开始想用单颗MCU加模拟温度传感器搞定,结果发现模拟传感器走线长了之后噪声大得离谱,ADC读出来的值跳得跟心电图似的。后来换成分立数字传感器加一颗主控MCU的方案,才把稳定性拉回来。

这次要聊的方案,核心是两颗芯片的配合:PJ85718DM和PIC18F97J94。前者是一颗带I2C接口的数字温度传感器,后者是一颗集成度很高的8位MCU,自带LCD驱动、USB模块和多个串口。把它们凑在一起,目标很明确——用一颗本地传感器监测板级或机柜内温度,同时通过MCU的串口或USB通道把数据传到远端上位机,实现本地与远程双路温度监测。这个组合特别适合HVAC控制板、嵌入式数据采集终端、小型环境监控设备这类场景。

为什么选这个组合而不是其他方案?我当时的考量有这么几点。第一,PIC18F97J94的引脚数多、外设全,一颗芯片就能把传感器读取、LCD显示、串口通信、报警输出全包了,省掉了额外扩展芯片的成本和布线麻烦。第二,PJ85718DM的I2C地址可以通过硬件引脚配置,同一条总线上挂多颗也不会冲突,方便后期扩展多点测温。第三,这两颗芯片的供电范围有重叠区间,都是3.3V和5V都能跑的器件,电源设计可以统一处理,不用搞电平转换。

整个系统的数据流是这样的:PJ85718DM负责把物理温度转换成数字量,通过I2C总线传给PIC18F97J94;MCU内部做滤波、标定和阈值判断,一路送到本地LCD显示,一路通过UART或USB转串口芯片发给上位机;上位机端用简单的串口助手或自己写的小工具就能看到实时温度曲线。如果温度超限,MCU还能驱动蜂鸣器或继电器输出报警信号。

这个方案的优势在于分工明确:传感器只管测温,MCU只管处理和通信,各司其职,调试的时候也容易定位问题。如果温度读不准,先查传感器;如果通信断了,先查MCU的串口配置。比起那些把传感器和主控集成在一颗芯片里的方案,这种分立设计在排查故障时友好得多。

注意:PJ85718DM的I2C总线上拉电阻不能省,很多新手直接拿MCU的内部上拉凑合,结果在长走线或高总线电容场景下通信失败。建议用4.7kΩ外部上拉,走线超过20cm时降到2.2kΩ试试。

2. 核心器件解析与选型依据

2.1 PJ85718DM温度传感器的关键特性

PJ85718DM这颗传感器,我实际用下来最深的印象是一致性好。同一批次买十颗,放在同一个恒温槽里,读数偏差不超过0.2摄氏度。它的测温范围覆盖负40到正125摄氏度,对于HVAC和大多数嵌入式场景来说绰绰有余。分辨率方面,默认是12位,对应0.0625摄氏度的步进,这个精度做环境监测完全够用,甚至有点过剩。

它的通信接口是标准I2C,最高支持400kHz时钟频率。这里有个细节值得展开说:很多人在初始化的时候直接把I2C速率设到400kHz,结果发现偶尔读取出错。我实测下来,如果MCU主频不高或者总线走线较长,把速率降到100kHz反而更稳。PJ85718DM内部有配置寄存器,可以设置分辨率、报警阈值和转换模式。它支持连续转换和单次转换两种模式,连续模式下功耗会高一些,但数据更新快;单次模式下MCU可以主动触发转换然后读结果,适合低功耗场景。

温度数据的存储格式是16位,高12位有效,低4位是标志位。读取的时候需要把两个字节拼起来然后右移4位,再乘以0.0625得到实际温度值。这个计算过程看起来简单,但如果不注意符号位扩展,负温度就会读错。比如零下10度,原始数据是补码形式,直接当无符号数处理会得到一个很大的正数。

2.2 PIC18F97J94微控制器的外设优势

PIC18F97J94这颗MCU,在8位机里算是“大块头”了。它有128KB的Flash、4KB的RAM,引脚数最多到100个,外设方面自带USB 2.0全速模块、四个UART、两个SPI、两个I2C、一个LCD驱动模块,还有多个定时器和PWM通道。对于温度监测这种任务来说,它的资源是绰绰有余的,甚至有点“杀鸡用牛刀”的感觉。

但正是这种资源冗余,给后续功能扩展留足了空间。比如你一开始只做温度监测,后来想加个湿度传感器、加个继电器控制、加个LCD显示,这颗MCU都不用换。它的I2C模块支持主从模式,做主设备读PJ85718DM的时候,硬件会自动处理起始位、地址发送、应答检测这些时序,软件只需要配置好寄存器然后读写数据缓冲区就行。

USB模块是这颗芯片的一个亮点。PIC18F97J94的USB支持全速12Mbps,可以枚举成CDC设备,也就是虚拟串口。这样上位机不需要额外的USB转串口芯片,一根USB线就能同时供电和通信。不过USB协议栈的配置相对复杂,如果项目周期紧,建议先用UART加外部转串口芯片的方案,等USB调通了再切换。

2.3 双芯片协同工作的设计考量

把这两颗芯片放在一起用,有几个设计决策需要提前想清楚。第一是供电方案:PJ85718DM的工作电压是2.7V到5.5V,PIC18F97J94是2.0V到3.6V。如果MCU跑3.3V,传感器可以直接用同一路3.3V供电,省掉一路LDO。但如果MCU跑5V,传感器虽然也能工作在5V,但I2C总线的电平就和MCU的3.3V IO不匹配了,需要加电平转换。我一般建议统一用3.3V,简单省事。

第二是I2C总线的布局:传感器尽量靠近MCU放置,走线短、干扰小。如果传感器必须放在远处,比如监测管道温度,那就需要考虑用屏蔽线或者加I2C缓冲器。我遇到过传感器离MCU两米远的情况,I2C通信频繁出错,后来在中间加了一颗I2C中继芯片才解决。

第三是地址分配:PJ85718DM的I2C地址由A0、A1、A2三个引脚决定,可以配置出8个不同地址。如果系统里只有一颗传感器,把这三个脚都接地就行。如果要多点测温,每颗传感器的地址引脚接不同的电平组合,MCU轮询读取即可。

器件工作电压通信接口测温范围分辨率典型精度
PJ85718DM2.7V-5.5VI2C-40~125℃12位±0.5℃
PIC18F97J942.0V-3.6VI2C/UART/USB/SPI———

3. 硬件连接与实操步骤

3.1 最小系统搭建与引脚分配

先把手上的板子理清楚。PIC18F97J94我用的是一块现成的开发板,上面已经焊好了晶振、复位电路和USB座。如果你是自己画板,记得晶振尽量靠近MCU的OSC引脚,负载电容按晶振规格书选,一般22pF左右。复位引脚上拉10kΩ电阻到VDD,再并一个0.1μF电容到地,这是标准配置。

PJ85718DM我买的是SOP-8封装的小板,引脚定义很简单:VDD、GND、SDA、SCL、A0、A1、A2、ALERT。ALERT是报警输出引脚,如果不用可以悬空。A0到A2接地,地址就是0x48(7位地址)。SDA和SCL分别接到MCU的I2C模块引脚上,PIC18F97J94的I2C1模块默认在RC3和RC4上,但可以通过寄存器重映射到其他引脚,具体看你的板子布局。

电源部分,我用了一颗3.3V的LDO给整个系统供电。输入是5V的USB电源,输出3.3V给MCU和传感器。LDO的输入输出端各放一个10μF的钽电容和一个0.1μF的陶瓷电容,滤高频和低频噪声。传感器电源引脚旁边再并一个0.1μF的电容,越近越好。

I2C总线的上拉电阻,我在SDA和SCL上各放了一个4.7kΩ的电阻拉到3.3V。如果你总线上挂的设备多,或者走线长,可以降到2.2kΩ。但电阻太小会增加功耗,静态电流会上去,电池供电的场景要注意权衡。

3.2 I2C通信初始化与传感器配置

软件这边,第一步是初始化MCU的I2C模块。PIC18F97J94的I2C配置寄存器有几个关键位:SSPCON1的SSPM位设为1000,也就是I2C主模式;SSPADD寄存器写入I2C时钟频率的计算值。计算公式是:SSPADD = (Fosc / (4 * Fscl)) - 1。假设MCU主频是16MHz,想要100kHz的I2C速率,SSPADD = (16000000 / (4 * 100000)) - 1 = 39。如果设400kHz,SSPADD = 9。

配置好I2C之后,下一步是给PJ85718DM写配置寄存器。这颗传感器有四个寄存器:温度寄存器、配置寄存器、THIGH报警上限寄存器和TLOW报警下限寄存器。配置寄存器的格式如下:bit7是连续转换模式使能,bit6和bit5是分辨率设置,bit4和bit3是报警极性,bit2是报警模式,bit1和bit0是故障队列设置。

我一般这样配:连续转换模式打开,分辨率设12位,报警极性低有效,报警模式比较器模式,故障队列设2次。对应的配置字节是0x60。写配置的时候,先发起始条件,然后发传感器地址加写位,接着发配置寄存器地址0x01,再发配置值0x60,最后发停止条件。

// I2C写一个字节到PJ85718DM的指定寄存器 void PJ85718_WriteReg(uint8_t reg, uint8_t value) { I2C_Start(); I2C_WriteByte(0x48 << 1); // 地址加写位 I2C_WaitAck(); I2C_WriteByte(reg); // 寄存器地址 I2C_WaitAck(); I2C_WriteByte(value); // 写入的值 I2C_WaitAck(); I2C_Stop(); }

读温度的时候,先发起始条件,发地址加写位,发温度寄存器地址0x00,然后重新发起始条件,发地址加读位,读两个字节,最后发停止条件。第一个字节是温度的高8位,第二个字节是低8位。把两个字节拼成16位数,右移4位,再乘以0.0625就是摄氏度。

float PJ85718_ReadTemp(void) { uint8_t msb, lsb; int16_t raw; I2C_Start(); I2C_WriteByte(0x48 << 1); I2C_WaitAck(); I2C_WriteByte(0x00); I2C_WaitAck(); I2C_Start(); // 重复起始条件 I2C_WriteByte((0x48 << 1) | 1); I2C_WaitAck(); msb = I2C_ReadByte(); I2C_SendAck(); lsb = I2C_ReadByte(); I2C_SendNack(); I2C_Stop(); raw = (msb << 8) | lsb; raw >>= 4; return raw * 0.0625; }

注意:读温度的时候,如果传感器正在转换,读出来的可能是上一次的结果。连续转换模式下,转换周期大约是750ms(12位分辨率)。如果MCU读得太快,会读到重复值。我一般用定时器每500ms读一次,既不会丢数据,也不会读太快。

3.3 本地显示与远程通信的实现

本地显示这块,PIC18F97J94自带LCD驱动模块,可以直接驱动段式LCD。但我手头没有合适的段式屏,就用了一个I2C接口的OLED小屏,挂在同一条I2C总线上。OLED的地址是0x3C,和传感器的0x48不冲突。这样MCU轮询两个设备,一个读温度,一个刷显示。

远程通信我用的是UART。PIC18F97J94有四个UART,我选了UART1,波特率设9600,8位数据,无校验,1位停止位。每读一次温度,就通过UART发一帧数据出去。帧格式我定义得很简单:帧头0xAA,然后是温度高字节、温度低字节,最后是校验和。上位机收到后解析显示。

void UART_SendTemp(float temp) { int16_t temp_int = (int16_t)(temp * 100); // 放大100倍,保留两位小数 uint8_t frame[5]; frame[0] = 0xAA; frame[1] = (temp_int >> 8) & 0xFF; frame[2] = temp_int & 0xFF; frame[3] = frame[1] ^ frame[2]; // 简单校验 frame[4] = 0x55; for (int i = 0; i < 5; i++) { UART_WriteByte(frame[i]); } }

上位机端我用Python写了一个简单的串口接收脚本,用pyserial库读数据,解析后画实时曲线。如果你不想写代码,用现成的串口助手也能看数据,只是没有曲线图。

import serial import struct ser = serial.Serial('COM3', 9600, timeout=1) while True: data = ser.read(5) if len(data) == 5 and data[0] == 0xAA and data[4] == 0x55: temp_raw = struct.unpack('>h', data[1:3])[0] temp = temp_raw / 100.0 print(f"当前温度: {temp:.2f} ℃")

4. 常见问题与排查技巧实录

4.1 I2C通信失败的那些坑

I2C通信失败是这类项目里最高频的问题,没有之一。我踩过的坑包括但不限于:上拉电阻没焊、地址搞错、时序不对、总线电容过大、电源不稳。排查的时候建议按这个顺序来:先用示波器看SDA和SCL有没有波形,如果没有,检查MCU的I2C模块有没有使能、引脚有没有配成I2C功能。如果有波形但传感器不应答,检查地址对不对,用逻辑分析仪抓一下地址字节。

有一次我调了半天,发现是传感器焊接的时候虚焊了,SDA引脚根本没连上。所以遇到问题先拿万用表量通断,别急着改代码。还有一次是上拉电阻用了10kΩ,总线速率设了400kHz,波形上升沿太缓,传感器识别不了。换成4.7kΩ就正常了。

现象可能原因排查方法解决措施
无波形I2C模块未使能查SSPCON1寄存器配置SSPM位为主模式
有波形无应答地址错误逻辑分析仪抓地址确认A0-A2引脚电平
偶发通信失败上拉电阻过大示波器看上升沿换4.7kΩ或2.2kΩ
读数为0传感器未转换查配置寄存器使能连续转换模式
负温度读错符号位未扩展检查数据类型用int16_t接收原始值

4.2 温度读数跳变与滤波处理

温度读数跳变是另一个常见问题。PJ85718DM的12位分辨率对应0.0625摄氏度,理论上读数应该很稳。但实际环境中,电源噪声、气流变化、甚至MCU自身发热都会影响读数。我实测下来,如果传感器靠近LDO或者MCU,读数会比环境温度高2到3摄氏度。解决办法是把传感器用导线引出来,远离热源。

软件滤波方面,我常用的是滑动平均滤波。开一个长度为8的数组,每次读到的温度存进去,然后取平均值输出。这样能平滑掉大部分随机跳变。如果对实时性要求高,可以用一阶滞后滤波:新值 = 旧值 * 0.8 + 当前值 * 0.2。这个系数可以根据实际跳变程度调整。

#define FILTER_LEN 8 float temp_buf[FILTER_LEN]; uint8_t buf_idx = 0; float Filter_Temp(float new_temp) { temp_buf[buf_idx] = new_temp; buf_idx = (buf_idx + 1) % FILTER_LEN; float sum = 0; for (int i = 0; i < FILTER_LEN; i++) { sum += temp_buf[i]; } return sum / FILTER_LEN; }

提示:滤波数组的长度不要设太大,8到16比较合适。太长会导致响应迟钝,温度真的变化时输出跟不上。另外,第一次上电的时候数组里全是0,前几次读数会被拉低,可以先读几次丢弃不用。

4.3 远程通信丢包与校验

UART通信相对I2C来说稳定得多,但在工业环境里,电机启停、继电器动作都会在电源和地线上产生干扰,导致串口丢包或误码。我遇到过上位机收到的温度值偶尔跳到几百摄氏度,明显是数据错位了。后来加了帧头和帧尾校验,情况好转很多。

除了帧头帧尾,还可以加CRC校验。不过对于温度监测这种低频场景,简单的异或校验就够用了。如果丢包严重,可以降低波特率,9600不行就降到4800甚至2400。波特率越低,抗干扰能力越强,但传输速度也越慢。温度数据量很小,2400波特率完全够用。

还有一个容易被忽略的点:UART的地线。如果MCU和上位机之间只连了TX和RX,没有连地线,通信会非常不稳定。一定要确保两边共地,或者用隔离模块。长距离通信建议用RS485差分信号,抗干扰能力比单端UART强得多。

5. 系统优化与扩展思路

5.1 低功耗设计要点

如果这个温度监测系统要用电池供电,低功耗设计就绕不开了。PIC18F97J94本身有休眠模式,电流可以降到微安级别。PJ85718DM在单次转换模式下,转换完自动进入低功耗状态,平均电流只有几微安。我的做法是让MCU定时唤醒,唤醒后触发传感器转换,等转换完成读数据,然后通过UART发出去,最后继续休眠。

休眠周期可以根据实际需求定,比如每分钟测一次。这样平均功耗可以做到几十微安,一节纽扣电池能撑好几个月。需要注意的是,休眠前要把不用的外设关掉,比如LCD背光、USB模块、多余的UART。I2C总线上拉电阻在休眠时会有静态电流,如果电池供电,可以把上拉电阻接到MCU的IO上,休眠时把IO拉低,彻底断掉上拉电流。

5.2 多点测温与报警联动

单点测温只是起步,实际项目里经常需要多点监测。PJ85718DM的地址引脚可以配置出8个地址,一条I2C总线上最多挂8颗。如果还不够,可以用I2C多路复用器扩展。MCU轮询读取每颗传感器的温度,然后分别判断是否超限。

报警联动方面,PIC18F97J94的IO口可以直接驱动蜂鸣器或继电器。我一般设两级报警:温度超过阈值1,蜂鸣器间歇响;超过阈值2,继电器断开加热设备。阈值可以通过上位机下发命令修改,存在MCU的EEPROM里,掉电不丢。

5.3 上位机数据记录与可视化

上位机端如果只是看实时数据,串口助手就够了。但如果要做数据记录和分析,建议用Python写个小工具,把数据存到CSV文件里,再用matplotlib画曲线。我自己的做法是用pyserial读数据,用csv库写文件,用matplotlib的animation功能实时刷新曲线。这样既能看实时趋势,又能事后分析。

import csv from datetime import datetime with open('temp_log.csv', 'a', newline='') as f: writer = csv.writer(f) writer.writerow([datetime.now().strftime('%Y-%m-%d %H:%M:%S'), f'{temp:.2f}'])

如果数据量很大,比如每秒记录一次,跑一个月就是两百多万条记录。这时候CSV就不太合适了,建议用SQLite数据库,查询和统计都方便得多。

6. 实操心得与避坑清单

这个项目我从打样到稳定运行,前后折腾了大概三周时间。大部分时间不是花在写代码上,而是花在排查硬件问题和优化通信稳定性上。下面这几条是我用真金白银换来的经验,希望能帮你少走弯路。

第一条:先调通I2C,再搞其他。很多人一上来就想把显示、通信、报警全做完,结果I2C没调通,后面全是空中楼阁。我的建议是写一个最简单的测试程序,只读传感器温度并通过串口打印,确认这一步稳定了再往上加功能。

第二条:示波器和逻辑分析仪是必备工具。调I2C和UART的时候,没有这两个工具基本靠猜。逻辑分析仪可以抓I2C的完整时序,地址、数据、应答位一目了然。示波器可以看信号质量,判断上拉电阻是否合适。这两个工具加起来几百块钱,能省下你几十个小时的调试时间。

第三条:电源滤波不能省。我一开始觉得3.3V LDO输出很干净,没加滤波电容,结果温度读数偶尔跳变。后来在传感器电源脚旁边加了0.1μF和10μF电容,跳变明显减少。MCU的电源脚也要加,数字电路的开关噪声会通过电源耦合到传感器。

第四条:I2C地址要确认清楚。PJ85718DM的地址是0x48到0x4F,取决于A0-A2引脚。有些模块出厂时A0-A2已经焊了上拉或下拉,你不看原理图直接按0x48写,可能根本不通。拿万用表量一下A0-A2的电平,或者用I2C扫描程序扫一下总线上的设备地址。

第五条:UART通信一定要加校验。工业环境里干扰无处不在,没有校验的数据帧不可靠。帧头帧尾加异或校验是最低要求,条件允许的话上CRC16。上位机收到数据后先校验,校验不过就丢弃,不要显示错误数据。

第六条:温度传感器远离热源。MCU、LDO、功率电阻都会发热,传感器离它们太近,测的就是板子温度而不是环境温度。如果测环境温度,传感器用导线引出来,放在通风处。如果测设备温度,那就把传感器贴在发热器件表面,但要注意绝缘。

第七条:休眠模式下的IO状态要处理好。如果做低功耗,休眠前把I2C引脚配成普通IO并输出低电平,断掉上拉电阻的电流通路。UART的TX引脚也要处理好,不要悬空,否则会有漏电流。这些细节不处理好,休眠电流可能比正常工作还大。

第八条:上位机软件要能容错。串口通信偶尔丢包是正常的,上位机不能因为一帧数据错误就崩溃。解析的时候先找帧头,找到后再读固定长度,校验不过就跳过继续找下一个帧头。这样即使丢了几帧,后面的数据还能正常接收。

最后再说一个关于PIC18F97J94的USB模块的坑。这颗芯片的USB模块需要外部晶振提供48MHz的时钟,如果你用的晶振频率不对,USB枚举会失败。而且USB协议栈的配置比较繁琐,如果项目时间紧,建议先用UART方案,等USB调通了再替换。我当初在USB上卡了两天,最后发现是晶振的负载电容选错了,换了电容立马就好。

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

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

立即咨询