多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

PIC18F97J94与PJ85718DM双芯片温度监测方案实战

PIC18F97J94与PJ85718DM双芯片温度监测方案实战 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总线传给PIC18F97J94MCU内部做滤波、标定和阈值判断一路送到本地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.5VPIC18F97J94是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接地地址就是0x487位地址。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。如果设400kHzSSPADD 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; }注意读温度的时候如果传感器正在转换读出来的可能是上一次的结果。连续转换模式下转换周期大约是750ms12位分辨率。如果MCU读得太快会读到重复值。我一般用定时器每500ms读一次既不会丢数据也不会读太快。3.3 本地显示与远程通信的实现本地显示这块PIC18F97J94自带LCD驱动模块可以直接驱动段式LCD。但我手头没有合适的段式屏就用了一个I2C接口的OLED小屏挂在同一条I2C总线上。OLED的地址是0x3C和传感器的0x48不冲突。这样MCU轮询两个设备一个读温度一个刷显示。远程通信我用的是UART。PIC18F97J94有四个UART我选了UART1波特率设96008位数据无校验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, timeout1) 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上卡了两天最后发现是晶振的负载电容选错了换了电容立马就好。
返回列表