1. 项目缘起与整体设计思路
嵌入式温度监测这件事,说起来简单,做起来坑不少。我最早接触这类需求是在一个环境控制类项目里,当时的要求很朴素:本地要能看到实时温度,远程也要能拿到数据,而且整套东西得足够稳定,不能三天两头掉线。后来类似的需求又碰到过好几次,场景从机房环境监控到暖通空调(HVAC)系统的风道温度采集都有,用的核心器件也慢慢固定下来——PJ85718DM 温度传感器搭配PIC18LF46K40 单片机,这套组合在中小规模温度监测里性价比很高,资料也相对齐全。
先说说这两个器件各自是干什么的。PJ85718DM 是一颗数字温度传感器,走的是 I2C 接口,测温范围覆盖工业级常见的区间,精度在常温段能到 ±0.5℃ 左右,对于 HVAC 这种不需要实验室级精度的场景完全够用。PIC18LF46K40 是 Microchip 的一款 8 位单片机,带 I2C、UART、SPI 等外设,Flash 有 64KB,RAM 3.8KB,跑一个温度采集加远程上报的任务绰绰有余。选它的核心原因是低功耗版本(LF 后缀)在 3.3V 下工作电流小,适合长期在线但供电受限的场合。
那为什么不用现成的模块?我试过一些集成度更高的方案,问题在于灵活性差——你想改个上报周期、加个本地报警阈值,往往要受制于模块固件。而自己用传感器加单片机搭,硬件成本其实差不多,但软件完全可控,后期扩展也方便。这就是这个项目方案选型的底层逻辑:用通用器件换取控制权。
整套系统的思路可以拆成三层。第一层是感知层,PJ85718DM 负责把物理温度转成数字量,通过 I2C 总线送给单片机。第二层是处理层,PIC18LF46K40 定时读取温度、做简单的滤波和阈值判断,同时驱动本地显示(比如段码屏或小尺寸字符屏)。第三层是通信层,单片机通过 UART 接一个无线或有线通信模块,把数据打包发到远程端。本地和远程两条路并行,互不干扰,这是保证可靠性的关键设计。
注意:本地显示和远程上报一定要解耦。我见过有人把显示刷新和通信发送写在同一个阻塞流程里,结果通信一卡,本地显示也跟着僵住,用户体验很差。
适合参考这套方案的人,我觉得是这几类:做楼宇自控、机房监控的嵌入式工程师;搞 HVAC 设备二次开发的技术人员;还有电子类专业的学生,想找一个软硬件都覆盖的练手项目。不管你是哪一类,只要跟着把 I2C 通信、定时采集、串口上报这几块吃透,后面换传感器、换单片机都能举一反三。
2. 核心器件解析与硬件连接要点
2.1 PJ85718DM 温度传感器的关键特性
PJ85718DM 这颗传感器,我实际用下来最大的感受是“省心”。它内部集成了感温元件和 ADC,直接输出数字温度值,不需要你外接运放或者做冷端补偿。I2C 地址可以通过引脚配置,一般默认是 0x48 这一档,具体要看数据手册的地址表。测温分辨率可以配置成 9 到 12 位,位数越高转换时间越长,9 位大概几十毫秒,12 位要一百多毫秒。HVAC 场景下温度变化本来就慢,我一般用 11 位或 12 位,牺牲一点速度换精度。
供电范围是 2.7V 到 5.5V,这点很友好,可以直接和 3.3V 的单片机共电源,不用额外做电平转换。功耗方面,连续转换模式下大概几百微安,如果做成间歇采样,平均电流能压到几十微安,对电池供电的远程节点很关键。
有一个细节容易被忽略:传感器的放置位置。PJ85718DM 测的是它自身封装的温度,如果你把它贴在发热的稳压芯片旁边,读数肯定偏高。我在一个项目里就吃过这个亏,传感器离 LDO 太近,实测比真实环境高了 3℃ 多,后来挪开并加了开槽隔离才正常。所以 PCB 布局时,传感器要远离功率器件,最好放在板边或者单独开一块区域。
2.2 PIC18LF46K40 的外设分配
PIC18LF46K40 的外设挺全,但用的时候要有规划,不能想到哪接到哪。我的分配习惯是这样的:I2C 用 MSSP 模块,SCL 和 SDA 选带内部上拉的引脚,这样外部只需要各接一个 4.7kΩ 上拉电阻到 3.3V。UART 用 EUSART 模块,TX 和 RX 接到通信模块,波特率一般设 9600 或 115200,看模块支持情况。定时器用 Timer1,配置成 1 秒中断一次,作为采集节拍。剩下的 GPIO 留给本地显示和按键。
这里有个坑要提醒:PIC18LF46K40 的引脚有多个复用功能,配置的时候一定要先查数据手册的引脚分配表,别把 I2C 和 UART 配到同一个引脚上。我刚开始就犯过这个错,编译能过,但跑起来通信死活不通,查了半天才发现引脚冲突。
时钟方面,我一般用内部振荡器跑 16MHz,够用而且省一个晶振。如果对 UART 波特率精度要求高,比如要跑 115200,内部振荡器的误差可能偏大,这时候建议外接一个 8MHz 或 16MHz 晶振,波特率会稳很多。
2.3 硬件连接清单与注意事项
把连接关系理清楚,后面写代码才不会乱。下面是我常用的连接表:
| 器件 | 引脚 | 连接到 | 说明 |
|---|---|---|---|
| PJ85718DM | SDA | PIC18LF46K40 RB0 | I2C 数据,需 4.7kΩ 上拉 |
| PJ85718DM | SCL | PIC18LF46K40 RB1 | I2C 时钟,需 4.7kΩ 上拉 |
| PJ85718DM | VCC | 3.3V | 与单片机共电源 |
| PJ85718DM | GND | GND | 共地 |
| 通信模块 | TX | PIC18LF46K40 RC7(RX) | 模块发,单片机收 |
| 通信模块 | RX | PIC18LF46K40 RC6(TX) | 单片机发,模块收 |
| 本地显示 | SDA/SCL | 与传感器共用 I2C | 地址不同即可共存 |
提示:I2C 总线上挂多个器件时,上拉电阻只需要一组,不要每个器件都加,否则总线电容太大,波形会变差。
电源部分我建议加一个 100nF 的去耦电容紧挨着传感器和单片机的电源引脚,再并一个 10μF 的钽电容做储能。HVAC 设备里电机启停频繁,电源纹波大,去耦做不好温度读数会跳。我实测过,去耦电容加上之后,读数波动从 ±1.5℃ 降到 ±0.3℃ 以内。
3. 软件架构与核心代码实现
3.1 整体软件流程设计
软件这块,我的原则是“主循环轻,中断重”。具体说,Timer1 中断负责打节拍,每 1 秒置一个标志位;主循环检测到标志位后,依次做三件事:读温度、更新本地显示、打包发送远程数据。这样主循环里没有阻塞等待,即使通信模块暂时忙,也不会影响下一轮采集。
为什么不把采集放在中断里?因为 I2C 读取涉及多次寄存器操作,耗时可能几毫秒,放在中断里会拉长中断响应时间,影响其他中断的实时性。用标志位的方式,中断只做计数,主循环做实际工作,这是嵌入式里很经典的分工。
温度数据处理上,我加了一个简单的滑动平均滤波。连续采 8 次,去掉最大最小值,剩下 6 个求平均。这样能滤掉偶发的尖峰干扰,又不至于让响应变太慢。HVAC 场景温度变化慢,8 次采样也就 8 秒,完全能接受。
3.2 I2C 读取温度的实现细节
PJ85718DM 的温度寄存器是 16 位,高 12 位有效,低 4 位是标志位。读回来之后要右移 4 位,再乘以 0.0625 才是摄氏度。这个换算关系一定要记牢,我见过有人忘了移位,结果读出来的值大得离谱。
下面是读取的核心代码,基于 MCC 生成的 I2C 驱动改写:
float PJ85718DM_ReadTemp(void) { uint8_t raw_h, raw_l; int16_t raw; float temp; I2C1_Start(); I2C1_Write(0x48 << 1); // 写地址 I2C1_Write(0x00); // 指向温度寄存器 I2C1_Restart(); I2C1_Write((0x48 << 1) | 1); // 读地址 raw_h = I2C1_Read(1); // 读高字节,ACK raw_l = I2C1_Read(0); // 读低字节,NACK I2C1_Stop(); raw = ((int16_t)raw_h << 8) | raw_l; raw >>= 4; // 右移 4 位取有效数据 temp = raw * 0.0625f; // 换算成摄氏度 return temp; }这段代码里,I2C1_Read(1)的参数 1 表示读完发 ACK,参数 0 表示发 NACK 然后停止。顺序不能错,否则传感器会一直占着总线。另外,如果总线上还挂了其他 I2C 器件,读写之间要确保地址正确,别串了。
3.3 本地显示与远程上报的并行处理
本地显示我用的是 I2C 接口的字符屏,和传感器共用总线。这里要注意地址冲突问题,传感器是 0x48,屏一般是 0x3F 或 0x27,不冲突。刷新频率不用太高,2 秒一次足够,太频繁反而闪。
远程上报走 UART,数据格式我习惯用简单的文本协议,比如TEMP:25.3\r\n。为什么不用二进制?因为文本协议调试方便,拿个串口助手就能看,出问题好排查。如果对带宽敏感,再考虑二进制压缩。
发送的时候要注意,UART 发送是阻塞的,如果通信模块缓冲区满,putc会卡住。我的做法是加一个超时计数,超过 100ms 还没发完就放弃这一帧,等下一轮再发。这样即使模块偶尔抽风,主循环也不会被拖死。
void SendTempRemote(float temp) { char buf[16]; uint8_t i; sprintf(buf, "TEMP:%.1f\r\n", temp); for (i = 0; buf[i] != '\0'; i++) { if (UART1_Write(buf[i]) == 0) { break; // 超时或失败,放弃本帧 } } }注意:
sprintf在 8 位单片机上比较耗资源,如果 Flash 紧张,可以自己写一个简单的浮点转字符串函数,只保留一位小数。
4. 常见问题排查与实操避坑经验
4.1 温度读数异常的问题排查
温度读数不对,是最常见的问题,原因五花八门。我整理了一个排查表,按可能性从高到低排:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 读数恒为 0 或 255 | I2C 没通 | 用示波器看 SCL/SDA 波形 |
| 读数偏高 3℃ 以上 | 传感器靠近热源 | 挪位置或加隔离 |
| 读数跳动大 | 电源纹波大 | 加去耦电容,检查地线 |
| 读数缓慢漂移 | 自热效应 | 降低采样频率 |
| 读数偶尔错 | 总线干扰 | 缩短走线,加屏蔽 |
自热效应这个事值得多说两句。PJ85718DM 在连续转换模式下自身会发热,如果采样太频繁,比如 10ms 一次,芯片温度会比环境高 0.5℃ 到 1℃。解决办法就是降低采样频率,1 秒一次完全够 HVAC 用,自热影响可以忽略。
4.2 通信不稳定的处理技巧
远程通信不稳定,我遇到过几种典型情况。一种是数据发出去但远程收不到,查下来是波特率不匹配,单片机内部振荡器误差累积导致的。换成外部晶振后问题消失。另一种是数据偶尔丢包,原因是通信模块供电不足,发送瞬间电流拉高把电压拉低了。在模块电源脚并一个大电容就能缓解。
还有一种比较隐蔽的问题:地线环路。如果本地和远程设备分别接地,两个地之间存在电位差,会在通信线上形成共模干扰。解决办法是用隔离模块,或者确保两端共地。我在一个跨楼层项目里就碰到过,后来加了光耦隔离才彻底解决。
4.3 低功耗设计的实操心得
如果远程节点是电池供电,低功耗就是绕不开的话题。我的做法是让单片机大部分时间休眠,Timer1 唤醒后采一次温度、发一次数据,然后继续睡。PIC18LF46K40 休眠电流大概几百纳安,加上传感器和通信模块的待机电流,整体平均电流能压到 100μA 以内,两节 AA 电池撑一年没问题。
但这里有个矛盾:通信模块唤醒和入网需要时间,如果休眠周期太长,每次唤醒都要重新入网,反而更耗电。我的经验是,休眠周期不要超过通信模块的保活间隔,具体数值要查模块手册。一般 30 秒到 1 分钟比较平衡。
提示:休眠前一定要把 I2C 总线的引脚设成高阻或输出低,避免通过上拉电阻漏电。这个细节不注意,休眠电流可能翻好几倍。
5. 系统扩展与场景适配思路
5.1 多点温度采集的扩展方法
单点测完了,很自然会想测多点。PJ85718DM 的 I2C 地址可以通过 ADDR 引脚配置成不同值,理论上一条总线能挂好几个。但实际用的时候,我建议不要挂太多,超过 4 个总线电容就上来了,波形会变差。如果确实要测很多点,可以用 I2C 多路复用器扩展,或者干脆每个点用一个单片机,通过通信网络汇总。
多点采集的软件架构也要调整。我一般用一个结构体数组存各点温度,主循环轮询读取,每个点之间留 10ms 间隔,避免总线太忙。显示的时候可以轮显,或者用多行屏一次显示完。
5.2 HVAC 场景的特殊适配
HVAC 应用和普通环境监测不太一样,有几个特殊点。一是温度范围宽,风道里可能到 60℃ 以上,回风可能接近 0℃,选传感器时要确认量程覆盖。二是湿度大,传感器封装要考虑防潮,必要时加透气膜。三是电磁干扰强,风机变频器就在旁边,通信线和传感器线都要做好屏蔽和滤波。
我在一个风道温度项目里,最初读数一直被变频器干扰,后来在传感器电源脚加了磁珠,通信线换成双绞屏蔽线,问题才解决。这些措施看起来麻烦,但在工业环境里是必须的。
5.3 数据上云的轻量方案
远程数据最终往往要进数据库或者云平台。对于这种小数据量场景,我不建议上复杂的协议,用 MQTT 就挺合适。单片机这边通过通信模块发 MQTT 报文,主题用hvac/room1/temp这种格式,服务端订阅后存库。如果通信模块不支持 MQTT,也可以在网关侧做协议转换,单片机只管发串口数据,网关负责上云。
这样分层的好处是,单片机端逻辑简单,不容易出问题;协议升级、平台迁移都在网关做,不影响现场设备。我现在的项目基本都按这个思路来,维护起来省心很多。
6. 写在最后的几点实操体会
这套 PJ85718DM 加 PIC18LF46K40 的方案,我从最早的单点监测做到现在的多点联网,前后迭代了好几版。最大的体会是,硬件选型不要追求高大上,够用、好买、资料全才是王道。这两颗器件都不是最新最贵的,但胜在稳定,出了问题查手册就能解决。
软件上,我越来越倾向于“简单粗暴”的架构。主循环加标志位,能不用 RTOS 就不用,中断里只做最少的事。这样代码好懂,出问题好查,别人接手也容易。那些花哨的框架,在小项目里往往是负担。
最后分享一个小技巧:调试温度采集的时候,拿一个标准温度计放在传感器旁边做对比,比看示波器还直观。我习惯用这个方法快速判断是传感器问题还是电路问题,省了很多时间。