1. 项目缘起与整体设计思路
嵌入式温度监测这个方向,看起来简单,实际上坑特别多。我最早接触这类需求是在一个环境控制类项目里,当时的要求是:本地要能实时看到温度,远程也要能拿到数据,而且设备本身要足够稳定,不能三天两头死机。最开始想用一颗带Wi-Fi的单片机一把梭,后来发现供电、抗干扰、传感器精度这些环节一个比一个难搞,最后老老实实回到“传感器+MCU+通信模块”的分工方案。
这次要聊的方案,核心是两颗芯片:PJ85718DM和PIC18F4455。前者是一颗带I2C接口的数字温度传感器,后者是Microchip经典的8位USB单片机。这个组合的定位很明确——本地用USB或串口读温度,远程通过通信链路把数据传出去,典型应用场景就是嵌入式设备机箱内部温度监控、小型HVAC系统的回风/送风温度采集、以及各种需要“本地显示+远程上报”的场合。
为什么选这个组合?我拆开讲。
PJ85718DM这颗传感器,核心优势是出厂校准、I2C数字输出、精度能到±0.5°C左右(典型值),而且封装小、功耗低。它不像热敏电阻那样需要自己做线性化,也不像热电偶那样要冷端补偿。对于HVAC这种温度范围通常在-20°C到80°C之间的场景,它刚好覆盖,而且数字接口直接省掉ADC这一环,MCU端只需要读寄存器就行。
PIC18F4455这颗MCU,老牌但极其能打。它有全速USB 2.0接口、多个定时器、硬件I2C(MSSP模块)、UART、以及足够多的GPIO。最关键的是,它的USB功能是硬件实现的,不需要软件模拟,这在做“本地USB读取温度”时非常省心。另外它的工作电压范围宽(2.0V到5.5V),工业级温度范围-40°C到+85°C,放在HVAC控制板旁边也不会掉链子。
整体设计思路是这样的:PJ85718DM通过I2C挂在PIC18F4455的MSSP引脚上,MCU周期性读取温度寄存器,做简单的滤波和单位换算,然后分两路输出——一路通过USB虚拟串口(CDC)给本地PC或调试终端,另一路通过UART接无线或有线通信模块,把数据发到远程服务器或网关。本地还可以挂一个小OLED或LCD做实时显示,但这不是必须的。
这个架构的好处是职责分离:传感器只管测,MCU只管读和转发,通信模块只管传。任何一环出问题,排查范围都很清晰。而且PIC18F4455的USB CDC功能让本地调试变得极其方便,插上电脑就是一个串口,不需要额外的USB转TTL芯片。
注意:PJ85718DM的I2C地址是7位的,具体地址要看型号后缀和引脚配置,常见的是0x48到0x4F之间。上电后第一件事就是确认地址,否则I2C总线扫不到设备,后面全是白费功夫。
适合谁来参考这篇内容?如果你正在做嵌入式温度采集、HVAC控制板、或者任何需要“本地+远程”双通道温度监测的项目,这个方案可以直接抄作业。如果你只是想做单点温度测量,那可能有点杀鸡用牛刀,但里面的I2C读取、USB CDC配置、数据滤波这些细节,照样有参考价值。
2. 核心器件解析与选型背后的逻辑
2.1 PJ85718DM温度传感器的关键参数与使用要点
PJ85718DM是一颗数字温度传感器,I2C接口,供电范围通常在2.7V到5.5V之间,和PIC18F4455的3.3V或5V系统都能直接对接。它的温度分辨率是可配置的,常见的有9位到12位可选,对应分辨率0.5°C到0.0625°C。对于HVAC应用,12位分辨率其实有点过剩,因为HVAC的控制精度通常到0.5°C就够了,但高分辨率对做滑动平均滤波有好处,可以平滑掉一些随机噪声。
这颗传感器的温度寄存器是16位的,高字节是整数部分,低字节的高4位是小数部分(12位模式下)。读取的时候要注意,I2C读操作需要先写指针寄存器地址,然后重启总线读两个字节。很多新手在这里翻车,是因为忘了I2C的“写-重启-读”时序,直接读会拿到错误数据。
转换时间方面,12位模式下典型转换时间是750ms左右。这意味着你不能每10ms去读一次,读到的可能是上一次的旧数据。正确的做法是:启动一次转换,等至少750ms,再读结果。或者用连续转换模式,但那样功耗会高一些。在HVAC场景里,温度变化本身就很慢,1秒读一次完全够用,所以用单次转换+延时读取的方式最省电。
实操心得:PJ85718DM的I2C引脚需要上拉电阻,典型值是4.7kΩ。如果总线上挂了多个I2C设备,上拉电阻可以适当减小到2.2kΩ,但不要低于1kΩ,否则灌电流会超标。我见过有人用10kΩ上拉,在长导线情况下波形上升沿太慢,导致通信失败,排查了半天以为是芯片坏了。
2.2 PIC18F4455的USB与I2C资源分配
PIC18F4455的MSSP模块可以配置成I2C主模式或从模式。在这个方案里,它作为I2C主机,去读PJ85718DM。MSSP的引脚是RC3(SCL)和RC4(SDA),这两个引脚需要配置成数字输入输出,并且要关闭对应的模拟功能(如果有的话)。PIC18F4455的引脚复用比较多,配置的时候要仔细看数据手册的引脚分配表。
USB方面,PIC18F4455有专用的USB收发器和稳压器,D+和D-引脚是固定的。USB CDC(通信设备类)的固件可以用Microchip的USB框架来生成,省去自己写描述符的麻烦。CDC的好处是,在PC端看起来就是一个虚拟串口,用任何串口终端都能打开,不需要装驱动(Windows 10及以上自带CDC驱动)。
定时器资源上,我一般用Timer1做温度读取的周期定时,比如每1秒触发一次读取。Timer0可以做系统滴答,Timer2可以留给PWM(如果要做风扇控制)。PIC18F4455有多个定时器,分配起来很灵活,但要注意中断优先级,USB中断的优先级通常要设高一些,否则USB枚举可能会失败。
2.3 为什么不用单芯片方案
有人会问,为什么不直接用带Wi-Fi的MCU,比如ESP32那种,一颗芯片全搞定?我试过,问题主要在三个方面:一是Wi-Fi射频部分对温度传感器有干扰,尤其是模拟传感器,数字传感器好一些但PCB布局不好照样出问题;二是Wi-Fi协议栈本身会占用大量CPU时间和内存,温度读取的实时性会受影响;三是功耗,Wi-Fi持续连接的功耗远高于I2C传感器+低功耗MCU的组合。
在HVAC场景里,设备可能常年运行,功耗和稳定性比“集成度”重要得多。PIC18F4455+PJ85718DM这个组合,MCU可以跑在比较低的频率(比如8MHz),功耗可控,而且USB和UART可以同时工作,本地和远程互不干扰。这个分工方案看起来“老派”,但实际跑起来非常稳。
3. 硬件连接与关键电路设计
3.1 I2C总线连接与上拉电阻计算
PJ85718DM和PIC18F4455的I2C连接很简单:SCL接SCL,SDA接SDA,VCC和GND对应接好。但简单不代表可以随便画。I2C总线的上拉电阻计算,要考虑总线电容和上升时间。总线电容包括PCB走线电容、引脚电容和器件电容,一般估算在50pF到200pF之间。上升时间要求是,标准模式(100kHz)下不超过1000ns,快速模式(400kHz)下不超过300ns。
上拉电阻的最小值由灌电流决定,PIC18F4455的I2C引脚灌电流能力是3mA左右(在5V下),所以上拉电阻不能小于1.6kΩ。最大值由上升时间决定,公式是R_max = t_r / (0.8473 × C_bus)。假设C_bus是100pF,快速模式下t_r是300ns,那么R_max大约是3.5kΩ。所以4.7kΩ在标准模式下没问题,快速模式下可能偏大,建议用2.2kΩ到3.3kΩ。
我实际用下来,3.3kΩ在3.3V系统和5V系统里都表现不错,波形干净,通信稳定。如果总线上只挂PJ85718DM一颗传感器,4.7kΩ也够用,但为了留余量,我一般直接上3.3kΩ。
3.2 电源滤波与去耦
温度传感器对电源噪声比较敏感,尤其是PJ85718DM这种高分辨率器件。VCC引脚旁边必须放一个0.1μF的陶瓷电容,越近越好。如果电源走线比较长,再并一个1μF到10μF的钽电容或电解电容。PIC18F4455的VDD引脚也要放0.1μF去耦电容,每个电源引脚一个,不能省。
HVAC环境里,电机、继电器、接触器这些感性负载很多,电源线上会有大量尖峰和纹波。如果温度读数偶尔跳变,十有八九是电源噪声导致的。我遇到过一次,温度每隔几秒跳2°C,后来在传感器VCC和GND之间并了一个100Ω电阻串联0.1μF电容的RC滤波,问题就解决了。这个RC滤波的截止频率大概在16kHz,对温度这种慢变信号没有影响,但能把高频噪声挡掉。
3.3 USB接口电路与ESD保护
PIC18F4455的USB接口需要外部电路配合。D+和D-线上要串22Ω电阻(有些设计用27Ω),作用是阻抗匹配和限流。VBUS检测引脚(如果用到)要接一个分压电阻,确保电压在MCU的IO耐压范围内。USB连接器的外壳地要和信号地分开,通过一个0Ω电阻或磁珠连接,避免地环路干扰。
ESD保护方面,USB接口是暴露在外面的,很容易被静电打坏。建议在D+和D-线上各加一个TVS二极管,比如单向的,钳位电压选5V左右。我见过不少项目省掉这个,结果在干燥环境里插拔几次USB,MCU的USB模块就挂了。TVS二极管成本很低,但能省掉很多售后麻烦。
注意:PIC18F4455的USB模块需要48MHz时钟,可以用外部晶振,也可以用内部PLL从8MHz或4MHz倍频。如果用外部晶振,负载电容要匹配,否则USB枚举会不稳定。我一般用8MHz晶振+4倍频到32MHz,再通过USB PLL倍频到48MHz,这样时钟精度够,成本也低。
4. 固件实现:从I2C读取到USB CDC输出
4.1 I2C读取PJ85718DM的完整流程
I2C读取温度的过程,我把它拆成四步:启动、写地址、写指针、重启读数据。PIC18F4455的MSSP模块在I2C主模式下,需要手动控制启动位、停止位和ACK/NACK。下面是一个典型的读取函数逻辑,用C语言描述(基于XC8编译器):
// 伪代码,展示I2C读取PJ85718DM的流程 uint16_t read_temperature(void) { uint8_t temp_high, temp_low; uint16_t raw_temp; I2C_Start(); I2C_Write(0x48 << 1); // 写地址,假设7位地址是0x48 I2C_Write(0x00); // 指针指向温度寄存器 I2C_Restart(); I2C_Write((0x48 << 1) | 0x01); // 读地址 temp_high = I2C_Read(ACK); // 读高字节,发ACK temp_low = I2C_Read(NACK); // 读低字节,发NACK I2C_Stop(); raw_temp = (temp_high << 8) | temp_low; return raw_temp; }拿到raw_temp之后,要转换成实际温度。12位模式下,raw_temp的高12位是有效数据,低4位是0。温度值 = raw_temp >> 4,然后乘以0.0625。如果是负数,要注意补码处理。PJ85718DM的输出是二进制补码格式,负温度的高字节最高位是1,转换的时候要先判断符号位。
我一般把温度转换成整数形式,比如乘以100,用int16_t存储,这样避免浮点运算,在8位MCU上跑得更快。比如25.5°C存成2550,显示的时候再除以100。
4.2 温度数据的滤波与校准
原始温度数据会有噪声,尤其是12位模式下,最后几位可能一直在跳。我一般用滑动平均滤波,取8个或16个样本做平均。滑动平均的窗口大小要权衡:窗口太大,响应慢;窗口太小,滤波效果差。对于HVAC应用,温度变化本身很慢,16个样本、每秒采一次,相当于16秒的窗口,完全能接受。
如果对响应速度有要求,可以用中值滤波+滑动平均的组合:先取5个样本,去掉最大最小,剩下3个求平均,再把这个结果放进滑动窗口。这样既能滤掉脉冲噪声,又能平滑随机噪声。
校准方面,PJ85718DM出厂已经校准过,但如果你对精度要求特别高,可以做一个单点校准。用一个已知精度的温度计做参考,在25°C附近读一个值,算出差值,在固件里做偏移补偿。注意,这个偏移补偿只在一个温度点附近有效,如果工作温度范围很宽,最好做多点校准,用分段线性插值。
4.3 USB CDC配置与数据发送
PIC18F4455的USB CDC固件,我一般用Microchip的USB框架生成,然后修改描述符和端点配置。CDC需要两个接口:一个通信接口(用于控制),一个数据接口(用于数据传输)。端点方面,至少需要一个中断IN端点(用于通知)和一个批量IN/OUT端点(用于数据)。
数据发送的流程是:把温度数据格式化成字符串,比如“TEMP:25.5\r\n”,然后写入批量IN端点的缓冲区,等待USB主机读取。注意,USB批量传输不是实时的,主机什么时候来读,取决于主机的轮询周期。对于温度监测,1秒发一次完全够用,不需要追求低延迟。
如果USB没有连接,固件要能自动切换到UART输出,不能死等USB。我一般用一个状态机来管理:检测USB连接状态,如果连接了,走USB CDC;如果没连接,走UART。这样调试的时候插USB,现场部署的时候用UART,一套固件通吃。
实操心得:USB CDC的发送缓冲区不要设太大,64字节就够了。太大浪费RAM,而且如果主机不读,数据会堆积。我一般用双缓冲,一个缓冲区在发送的时候,另一个缓冲区准备下一帧数据,避免发送阻塞。
4.4 UART远程通信协议设计
远程通信走UART,接无线模块或有线网关。协议设计要简单可靠,我一般用“帧头+长度+数据+校验”的格式。比如:
| 字段 | 长度 | 说明 |
|---|---|---|
| 帧头 | 2字节 | 0xAA 0x55 |
| 长度 | 1字节 | 数据段长度 |
| 命令 | 1字节 | 0x01表示温度数据 |
| 数据 | N字节 | 温度值,高字节在前 |
| 校验 | 1字节 | 从长度到数据的异或校验 |
这个协议简单,解析起来不费劲,而且有校验,能发现传输错误。UART的波特率我一般用9600或19200,对于温度数据来说绰绰有余。如果通信距离长,波特率要降低,或者用RS485差分传输。
远程端收到数据后,可以存数据库、做趋势图、或者触发报警。如果温度超过阈值,远程端可以发命令回来,让MCU点亮本地报警灯或继电器。这个双向通信的能力,是HVAC系统里很实用的功能。
5. 常见问题与排查技巧实录
5.1 I2C通信失败排查速查表
| 现象 | 可能原因 | 排查方法 | 解决方法 |
|---|---|---|---|
| 完全读不到数据 | 地址错误 | 用I2C扫描程序确认地址 | 检查传感器型号后缀,确认7位地址 |
| 数据偶尔错误 | 上拉电阻不合适 | 用示波器看SCL/SDA波形 | 调整上拉电阻到2.2k-4.7k |
| 通信一段时间后死锁 | 总线电容过大 | 测量总线电容 | 减小上拉电阻,缩短走线 |
| 读到的温度固定不变 | 转换未完成 | 检查转换时间 | 增加延时或改用连续转换模式 |
| 负温度读数错误 | 补码处理错误 | 用已知负温度测试 | 检查符号位判断逻辑 |
这个表是我在实际项目中总结的,基本上覆盖了80%的I2C问题。其中“通信一段时间后死锁”最隐蔽,往往是因为总线电容在温度变化后发生了变化,导致上升沿变慢,MCU误判为总线忙。解决办法除了减小上拉电阻,还可以在固件里加一个超时复位:如果I2C操作超过一定时间没完成,就强制复位MSSP模块,重新初始化。
5.2 USB枚举失败的常见原因
USB枚举失败,PC端提示“未知设备”或“设备描述符请求失败”,通常有这几个原因:
- 时钟不准:USB对时钟精度要求很高,±0.25%以内。如果用的是内部RC振荡器,大概率枚举失败。必须用外部晶振。
- D+上拉电阻缺失:全速USB设备需要在D+线上接1.5kΩ上拉电阻到3.3V,告诉主机这是一个全速设备。PIC18F4455内部有这个上拉电阻,但需要固件使能。
- 描述符错误:VID、PID、设备类这些描述符如果不符合USB规范,主机就会拒绝枚举。用Microchip的框架生成描述符,不要手写。
- 电源不足:如果设备从USB总线取电,电流超过100mA(未枚举前)或500mA(枚举后),主机会切断供电。检查电路功耗。
我遇到过一次枚举失败,查了半天发现是晶振的负载电容焊错了,导致频率偏了0.5%,换了电容就好了。所以USB问题,先查时钟,再查上拉,最后查描述符。
5.3 温度读数跳变的处理经验
温度读数跳变,分两种情况:一种是偶尔跳一下,很快恢复;另一种是持续跳动,没有规律。
偶尔跳变,通常是电源噪声或EMI干扰。解决办法是在传感器电源脚加RC滤波,或者在固件里加中值滤波。如果跳变幅度很大,比如从25°C跳到80°C,那可能是I2C通信错误,读到了错误的数据。这时候要在固件里加数据合理性检查:如果读到的温度超出传感器量程(比如-55°C到125°C之外),就丢弃这次数据,用上一次的值。
持续跳动,可能是传感器本身的问题,或者I2C总线被其他设备干扰。检查总线上有没有其他I2C设备在频繁通信,如果有,考虑给温度传感器单独一条I2C总线,或者用软件I2C避开冲突。
实操心得:我在一个HVAC项目里遇到过温度持续跳动,最后发现是变频器的高频噪声通过电源线耦合进来了。在传感器电源入口加了一个共模电感,问题彻底解决。所以如果软件滤波怎么都搞不定,一定要往硬件干扰方向想。
5.4 远程通信丢包与重连策略
UART远程通信,丢包是难免的,尤其是无线链路。我的策略是:不追求零丢包,但要能检测和恢复。每帧数据带序号,远程端发现序号不连续,就知道丢包了。如果连续丢包超过一定数量,远程端可以发命令让MCU降低发送频率,或者切换到更可靠的通信模式。
重连方面,如果UART通信中断(比如无线模块掉线),MCU要能检测到,并尝试重新初始化通信模块。我一般用一个心跳机制:MCU每隔一段时间发一个心跳包,远程端收到后回复一个确认。如果连续几个心跳没收到确认,MCU就复位通信模块,重新建立连接。
这个机制看起来简单,但非常有效。我在现场部署的设备,运行了半年多,遇到过几次无线模块假死,都是靠心跳机制自动恢复的,不需要人工去现场重启。
6. 实际部署中的经验与扩展思路
6.1 本地显示与报警的简单实现
本地显示不是必须的,但加上去会方便很多。我用过0.96寸的OLED,I2C接口,和PJ85718DM共用一条总线,地址不冲突就行。OLED显示温度、状态和通信指示,刷新率不用太高,1秒一次足够。如果不想用OLED,用几个LED也行:绿灯表示正常,红灯表示超温,黄灯表示通信故障。
报警逻辑可以放在MCU里,也可以放在远程端。我倾向于放在MCU里,因为本地报警响应更快,不依赖网络。设定一个温度阈值,比如60°C,超过就点亮红灯,同时通过UART发报警帧给远程端。阈值可以通过UART命令远程修改,存在EEPROM里,掉电不丢。
6.2 多传感器扩展与地址冲突处理
一个PIC18F4455的I2C总线可以挂多个PJ85718DM,只要地址不同。PJ85718DM的地址可以通过引脚配置,通常有3个地址引脚,可以组合出8个地址。如果要在一条总线上挂超过8个传感器,就需要用I2C多路复用器,比如TCA9548A,把总线分成8路,每路挂8个传感器,总共可以挂64个。
多传感器的时候,读取策略要调整。不能每个传感器都等750ms,那样64个传感器要48秒才能轮一遍。可以用连续转换模式,让所有传感器同时转换,然后依次读取。或者用分组策略,把传感器分成几组,每组共用一个转换启动信号,组内依次读取。
6.3 低功耗设计与休眠唤醒
如果设备是电池供电的,低功耗就很重要。PIC18F4455有休眠模式,电流可以降到nA级别。PJ85718DM也有单次转换+自动休眠的模式,转换完成后自动进入低功耗状态。
我的做法是:MCU定时唤醒(比如每60秒),启动一次温度转换,等750ms,读取温度,通过UART发出去,然后继续休眠。USB部分在电池供电时关闭,只保留UART。这样平均电流可以做到几十微安,用两节AA电池能跑好几个月。
唤醒源可以用Timer1的外部晶振(32.768kHz),功耗极低。或者用WDT(看门狗定时器)唤醒,但WDT的周期精度不如外部晶振。如果对时间精度要求不高,WDT也够用。
6.4 固件升级与远程维护的考虑
现场部署的设备,固件升级是个麻烦事。如果设备在偏远地方,不可能每次都去现场插编程器。所以设计的时候就要考虑远程升级。PIC18F4455支持Bootloader,可以通过UART或USB接收新固件,写入Flash。
我的做法是:在Flash里划分两个区域,一个Bootloader区,一个应用程序区。Bootloader在复位后先运行,检查是否有升级标志。如果有,就通过UART接收新固件,写入应用程序区,然后跳转过去。如果没有,直接跳转到应用程序。升级标志可以存在EEPROM里,通过UART命令设置。
这个Bootloader不需要太复杂,能接收数据、擦写Flash、校验CRC就行。我写过一个大概2KB的Bootloader,跑在PIC18F4455上,升级一次大概30秒,很稳定。
6.5 这个方案的局限性与替代选择
这个方案不是万能的。如果你的项目需要高速采样(比如每秒100次以上),PIC18F4455的I2C速率和PJ85718DM的转换时间会成为瓶颈。这时候要考虑用SPI接口的温度传感器,或者用带硬件I2C高速模式的MCU。
如果温度范围超出-55°C到125°C,PJ85718DM就不适用了,需要换热电偶或PT100方案。如果对精度要求极高(±0.1°C以内),PJ85718DM的±0.5°C就不够看,要用更高精度的传感器,比如铂电阻配合高精度ADC。
但在大多数HVAC和嵌入式温度监测场景里,这个方案的性价比和稳定性都是很能打的。两颗芯片加起来成本不高,开发难度适中,资料也齐全。我个人的体会是,不要盲目追求新芯片和集成方案,老牌器件的稳定性和生态支持,在实际项目里往往比参数表上的数字更重要。
最后再分享一个小技巧:PIC18F4455的USB CDC在Windows上会被识别为“USB串行设备”,但在Linux上可能需要手动加载cdc_acm模块。如果遇到Linux下不识别,检查内核是否编译了CDC ACM支持,或者用lsusb看看设备有没有枚举成功。这个坑我踩过,折腾了一下午才发现是内核模块没加载。