ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于PJ85718DM与PIC18LF47K42的嵌入式温度监测方案:本地显示与远程上报

基于PJ85718DM与PIC18LF47K42的嵌入式温度监测方案:本地显示与远程上报 1. 项目缘起与整体设计思路嵌入式温度监测这件事说起来简单做起来坑不少。我最早接触这类需求是在一个HVAC控制板的项目里当时的要求很朴素本地要能看到机房回风温度远程中控室也要能读到同一路数据而且两边的读数不能打架。最开始想用一颗带Wi-Fi的模组一把梭结果发现现场电磁环境复杂无线丢包严重中控室拿到的温度经常是几分钟前的旧值。后来老老实实回到本地传感器本地MCU有线远程链路的经典架构才把稳定性做起来。这个项目标题里的两颗芯片恰好代表了这套架构的两个关键角色。PJ85718DM是一颗带I2C接口的数字温度传感器负责把物理温度转成数字量PIC18LF47K42是一颗8位单片机负责采集、处理、显示和转发。一个管感知一个管决策与通信分工非常清晰。这套组合能解决的核心问题是在同一个节点上同时满足本地显示和远程上报两种需求并且保证两路数据同源、同步、可校验。适合谁来参考这份内容如果你正在做HVAC温控器、机房环境监控、冷柜温度记录、农业大棚测温这类项目手上又只有8位MCU的资源预算那这套方案基本可以直接抄。哪怕你用的是别的传感器或别的MCU里面的采样策略、滤波方法、远程链路设计思路也是通用的。我下面会把选型理由、硬件连接、固件逻辑、滤波算法、远程协议、故障排查全部拆开讲尽量做到你看完就能动手。先说清楚整体思路避免一上来就陷进寄存器细节。整个系统的数据流是这样的PJ85718DM以一定周期把温度转换成数字量PIC18LF47K42通过I2C总线读回来做滑动平均和异常值剔除一路送去本地显示比如段码屏或小尺寸字符屏另一路通过UART或RS-485打包上报给远程主机。本地和远程用的是同一份经过滤波的数据所以不会出现本地28度、远程30度这种尴尬情况。为什么不用模拟传感器加ADC因为数字传感器省掉了运放、基准源和校准环节PJ85718DM出厂就带校准长期漂移小对8位MCU来说I2C读取比ADC采样加查表要省心得多。为什么不用带无线的方案HVAC现场金属风管、变频器、接触器一大堆2.4G频段被干扰得厉害有线链路虽然布线麻烦但一次布好之后基本不用管。这就是典型的用工程可靠性换一点施工成本的取舍。2. 核心器件解析与选型考量2.1 PJ85718DM 温度传感器的关键特性PJ85718DM是一颗数字输出温度传感器走I2C总线典型精度在常温区间能做到正负0.5度以内分辨率可以配置到0.0625度。这个分辨率对HVAC来说其实有点过剩因为空调控制通常0.5度一档就够了但高分辨率对做滑动平均滤波有好处——原始数据越细平均之后越平滑。它的供电范围比较宽2.7V到5.5V都能工作这一点很关键。PIC18LF47K42的LF版本是低电压型号工作电压上限比标准版低如果传感器只能3.3V供电那整条I2C总线的电平就得统一。我一般把两者都放在3.3V域里省掉电平转换芯片布线和调试都简单。I2C地址方面PJ85718DM通常提供几个可选地址通过地址引脚拉高拉低来切换。这意味着同一条I2C总线上可以挂多颗同型号传感器分别测进风口、出风口、回风口温度。做多路测温的时候这个特性非常实用不用为每一路单独占一个MCU引脚。注意I2C总线的上拉电阻不能省也不能随便选。3.3V系统下我一般用4.7k总线电容大的时候降到2.2k。上拉太弱波形上升沿变缓高速模式下会读错上拉太强静态功耗上去低功耗场景不划算。2.2 PIC18LF47K42 作为主控的定位PIC18LF47K42属于PIC18系列里外设比较丰富的一档UART、SPI、I2C、多个定时器、比较器都有Flash和RAM对温度监测这种任务绰绰有余。选它而不是选更小的8脚MCU主要看中三点第一硬件I2C外设能减轻CPU负担不用软件模拟时序第二多个UART方便同时接本地调试口和远程通信口第三引脚数量够能直接驱动段码屏或并口字符屏不用额外加驱动芯片。LF这个后缀要特别注意。它的最高工作频率和标准版有差异供电电压范围也不同。如果你打算用5V系统得先确认手里的具体型号能不能吃5V别想当然。我在一个项目里就因为没核对清楚板子打回来发现5V下跑不到预期主频只能降频使用白白浪费了性能余量。2.3 本地与远程双路输出的架构选择本地显示和远程上报看着是两件事其实共用同一条数据链。我的做法是在RAM里维护一个当前有效温度变量采样、滤波、异常判断全部围绕这个变量做显示任务和通信任务都只读这个变量不各自去读传感器。这样做的好处是数据一致性有保证坏处是这个变量得做好并发保护——如果通信在中断里跑显示在主循环里跑读写同一变量时要注意原子性。远程链路我优先选RS-485而不是UART直连。UART直连距离短几米之外就容易被干扰而且没有总线仲裁能力多节点组网很麻烦。RS-485差分传输抗共模干扰强几百米距离下依然稳定配合Modbus RTU协议还能和大多数中控系统对接。代价是要加一颗收发器芯片成本增加有限可靠性提升明显。对比项UART直连RS-485传输距离通常小于3米可达数百米抗干扰能力弱强差分传输多节点组网困难支持总线挂多节点硬件成本低增加收发器适用场景板内调试现场远程上报3. 硬件连接与采样电路实操3.1 I2C总线连接与上拉电阻计算接线本身不复杂传感器的SDA接MCU的SDASCL接SCL电源和地接好地址引脚按需要配置。真正容易出问题的是上拉电阻和走线。I2C是开漏输出没有上拉就没有高电平这是新手最常踩的坑——波形一直是低读出来全是0。上拉电阻的取值有个经验公式R (Vdd - Vol) / Iol同时还要满足上升时间要求。实际工程里我不会去精算直接按总线电容估总线电容小于100pF用4.7k100到200pF用3.3k超过200pF用2.2k并缩短走线。3.3V系统下4.7k是万能起点读不到再往下调。走线方面SDA和SCL尽量并行走长度控制在20厘米以内远离电机驱动、继电器、开关电源这些噪声源。如果传感器和MCU不在同一块板上用排线连接时最好SDA和SCL各配一根地线做隔离别让信号线和电源线绞在一起。3.2 电源去耦与噪声抑制数字温度传感器对电源噪声其实挺敏感尤其是HVAC现场风机和压缩机启停时电源上会有明显的尖峰。我在传感器电源脚旁边一定放一颗0.1uF陶瓷电容紧贴引脚再并一颗1uF到10uF的钽电容或电解电容做低频储能。这两颗电容的位置比容值更重要离引脚越近越好走线越短越好。如果现场干扰特别严重可以在电源入口加一颗磁珠或者小电感配合电容组成LC滤波。但要注意磁珠的直流电阻别让压降影响传感器供电。我遇到过因为磁珠选型不当传感器供电跌到2.5V以下读数直接乱跳的情况后来换成低阻磁珠才解决。提示调试阶段如果读数偶尔跳变先别急着改代码用示波器看一眼传感器电源脚和I2C波形。很多软件问题其实是硬件噪声代码改半天不如加一颗电容。3.3 本地显示与远程接口的引脚分配PIC18LF47K42的引脚规划要提前做别等画板子的时候才发现引脚不够或者功能冲突。我的分配习惯是I2C固定用硬件外设对应的引脚UART1留给本地调试UART2配合RS-485收发器做远程通信显示接口用剩下的普通IO。RS-485收发器的方向控制脚DE/RE用一个IO控制发送前置高发送完置低这个切换时机要卡准早了数据没发完晚了总线占用时间过长。显示部分如果用段码屏需要占用较多IO做段选和位选如果用I2C接口的字符屏可以和传感器共用总线但要注意地址不能冲突。我一般倾向I2C字符屏省引脚接线简单代价是刷新率低一点但对温度显示来说完全够用。4. 固件逻辑与滤波算法实现4.1 采样周期与定时器配置采样周期不能太短也不能太长。太短了传感器内部转换还没完成读到的可能是上一次的结果或者无效值太长了温度变化响应迟钝空调控制会滞后。PJ85718DM的单次转换时间通常在几十毫秒量级我一般把采样周期定在1秒既给足了转换时间又能及时反映温度变化。定时器配置上用一个定时器产生1秒中断在中断里置一个标志位主循环检测到标志位再去读传感器。为什么不直接在中断里读I2C因为I2C读取涉及等待和超时处理放在中断里会拉长中断时间影响其他任务的实时性。中断只做打标记这种轻量操作重活留给主循环这是嵌入式开发的通用原则。// 定时器中断服务程序示意 void __interrupt() timer_isr(void) { if (TMR0IF) { TMR0IF 0; TMR0 TIMER_RELOAD_VALUE; sample_flag 1; // 只置标志不做实际读取 } }4.2 温度数据读取与原始值转换读PJ85718DM的流程是发起始条件发设备地址加写位发寄存器指针重复起始发设备地址加读位读两个字节发停止条件。读回来的两个字节是补码格式高字节在前低字节在后需要拼成一个16位有符号数再乘以分辨率得到实际温度。这里有个细节容易错分辨率配置不同转换系数就不同。如果配置成0.0625度每LSB那原始值右移4位才是整数部分。我见过有人直接拿原始值当温度用显示出来是几百上千度排查半天才发现是没做移位和符号处理。int16_t raw (high_byte 8) | low_byte; float temperature raw * 0.0625f; // 按实际配置的分辨率调整负温度的处理也要注意补码格式下负数的符号位在高字节的最高位拼接和转换时不能当成无符号数处理否则零下温度会变成很大的正数。4.3 滑动平均与异常值剔除策略原始温度数据一定会有抖动哪怕传感器精度再高电源噪声和量化误差也会让读数在正负0.2度之间跳。直接显示这个跳动的值用户看着难受控制逻辑也容易误动作。我的做法是两级处理先做异常值剔除再做滑动平均。异常值剔除用变化率限制如果本次读数和上次有效值的差超过某个阈值比如2度就认为这次是异常值丢弃不用继续沿用上次的值。这个阈值要根据实际场景定HVAC温度变化慢2度已经很大了超过基本就是干扰。但如果是测水温或者快速变化的场景阈值要放宽。滑动平均用长度为8的环形缓冲区每次存入一个有效值输出8个值的平均。长度选8是折中太短了平滑效果不够太长了响应迟钝。8个采样点按1秒周期算就是8秒的窗口对HVAC来说响应速度可以接受。#define FILTER_LEN 8 float temp_buffer[FILTER_LEN]; uint8_t buf_index 0; float filter_temperature(float new_temp) { static float last_valid 25.0f; if (fabsf(new_temp - last_valid) 2.0f) { new_temp last_valid; // 异常值用上次有效值替代 } last_valid new_temp; temp_buffer[buf_index] new_temp; buf_index (buf_index 1) % FILTER_LEN; float sum 0; for (uint8_t i 0; i FILTER_LEN; i) { sum temp_buffer[i]; } return sum / FILTER_LEN; }注意缓冲区初始化时不能全填0否则上电后前8秒的平均值会被0拉低显示一个明显偏低的温度。我的做法是上电后先连续读8次真实值填满缓冲区再进入正常滤波流程。5. 远程通信协议与数据打包5.1 Modbus RTU 帧结构设计远程上报我选Modbus RTU原因是它简单、通用、中控系统基本都支持。一帧数据由地址、功能码、数据、CRC校验组成没有复杂的握手和加密8位MCU处理起来毫无压力。温度值在Modbus里通常用保持寄存器承载一个寄存器16位。温度是浮点数直接塞进一个寄存器放不下我的做法是乘以10或乘以100转成整数再放进去中控那边收到后除以同样的系数还原。比如25.6度乘以10变成256一个寄存器就能装下精度保留到0.1度对HVAC足够。字段长度说明从站地址1字节每个节点唯一功能码1字节03读保持寄存器寄存器地址2字节温度存放位置寄存器数量2字节读取个数CRC校验2字节低字节在前5.2 本地显示与远程数据的一致性保证前面提过显示和通信共用同一个当前有效温度变量。实现上滤波函数算完之后把结果写进这个全局变量显示任务和通信任务都读它。如果通信在中断里做读这个变量时要先关中断再读读完开中断防止读到一半被采样中断改写。还有一种情况是远程主机主动来读这时候MCU是被动响应读的就是当前变量值不存在同步问题。但如果是MCU主动定时上报就要注意上报周期和采样周期的关系。我一般让上报周期是采样周期的整数倍比如采样1秒上报5秒这样每次上报的都是刚更新过的值不会上报到半新半旧的数据。5.3 RS-485 收发方向控制时序RS-485是半双工收发不能同时进行方向控制脚的切换时机很关键。发送前先把方向脚置为发送等一小段时间让收发器稳定再往UART数据寄存器写数据。发送完成的判断不能只看发送寄存器空要看发送移位寄存器也空了否则最后一个字节还没发完就切回接收会把尾字节截断。void rs485_send(uint8_t *data, uint8_t len) { RS485_DE 1; // 切到发送 __delay_us(10); // 等待收发器稳定 for (uint8_t i 0; i len; i) { while (!TXIF); TXREG data[i]; } while (!TRMT); // 等待移位寄存器空 __delay_us(10); RS485_DE 0; // 切回接收 }这个10微秒的延时不是随便写的具体值要看收发器的使能时间和波特率。波特率越高字节间隔越短延时太长会浪费总线时间太短则收发器还没准备好。实测下来9600波特率下10微秒很稳115200下要缩短到2微秒左右。6. 常见问题与排查技巧实录6.1 I2C读不到数据的排查顺序I2C读不到数据是最常见的问题排查要有顺序别东一榔头西一棒子。我的顺序是先量电源再量上拉再看波形最后查地址。电源不对什么都白搭先确认传感器供电在范围内。上拉电阻如果漏焊或者阻值太大波形拉不到高电平用示波器一看便知。波形正常但读不到多半是地址错了PJ85718DM的地址由引脚决定核对一下引脚电平和代码里的地址是否一致。还有一种隐蔽情况是总线被某个器件拉死SDA一直为低这时候要逐个断开器件定位。现象可能原因排查方法完全无响应电源未接或电压不足万用表量供电脚波形拉不高上拉电阻缺失或过大检查上拉电阻地址无应答地址配置错误核对地址引脚电平偶发读错总线干扰或时序临界示波器看波形质量总线锁死某器件异常拉低SDA逐个断开定位6.2 温度读数跳变的几种典型原因读数跳变我遇到过好几次原因各不相同。有一次是电源去耦电容离传感器太远风机一启动读数就跳把电容挪到引脚旁边就好了。有一次是I2C走线和电机线捆在一起重新布线后解决。还有一次是滤波窗口太短把窗口从4加到8就平稳了。要区分是硬件噪声还是软件问题有个简单办法把传感器单独用短线接到MCU远离所有干扰源如果读数稳定了那就是现场干扰问题如果还跳那就是代码或者配置问题。这个最小系统法能快速缩小排查范围。6.3 远程通信丢包与校验错误处理RS-485通信丢包先查终端电阻。总线两端各接一个120欧姆终端电阻中间节点不接这是标准做法。少了终端电阻信号反射会导致误码多了终端电阻总线负载过重信号幅度下降。我见过一条总线上每个节点都焊了终端电阻结果通信距离大幅缩短拆掉多余的就好了。CRC校验错误频繁的话除了查终端电阻还要看波特率是否匹配、地线是否共地。RS-485虽然抗干扰强但前提是各节点共地地电位差太大会烧收发器或者导致通信异常。长距离布线时最好用带屏蔽层的双绞线屏蔽层单端接地。提示调试RS-485时先用手头的USB转485工具单独和每个节点通信确认单节点正常后再接入总线。这样能把节点问题和总线问题分开排查效率高很多。7. 低功耗与长期稳定性优化7.1 间歇采样与休眠策略如果这个节点是电池供电或者对功耗有要求就不能一直全速跑。PIC18LF47K42支持休眠模式我的做法是让MCU大部分时间休眠定时器唤醒后采一次温度处理完继续睡。传感器也支持单次转换模式转换完自动进入低功耗不用一直保持工作状态。休眠策略的关键是平衡功耗和响应速度。HVAC温度变化慢采样间隔放到10秒甚至30秒都不影响控制效果功耗能降一个数量级。但如果本地有显示显示刷新不能停那就得让显示部分独立工作或者降低刷新率别为了省电把显示也关了用户会以为设备坏了。7.2 传感器长期漂移与自校准思路数字传感器虽然出厂校准过但长期使用还是会有轻微漂移。对精度要求高的场合可以定期做自校准用一个已知温度的参考点比如冰水混合物0度比对算出差值存进EEPROM后续读数都减去这个偏移。这个方法在实验室可行现场不一定有条件所以更多是作为维护手段而不是自动功能。更实际的做法是记录长期数据趋势如果发现读数整体缓慢偏移再安排人工校准。我在一个机房项目里就是每季度导出一次历史数据对比标准温度计偏移超过0.5度才去现场校准平时不用管。7.3 看门狗与异常恢复机制现场设备最怕死机一死机温度监测就断了可能造成严重后果。PIC18LF47K42自带看门狗定时器一定要开。主循环里定期喂狗如果程序跑飞或者卡死看门狗超时复位设备自动恢复。喂狗的位置有讲究不能放在定时器中断里因为中断可能还在跑而主循环已经卡死这样看门狗就失效了。正确做法是在主循环的关键路径上喂狗确保主循环真的在正常运转。另外复位后要做好状态恢复比如重新初始化I2C和UART把滤波缓冲区重新填满别让复位后的前几秒显示异常值。void main(void) { system_init(); while (1) { CLRWDT(); // 主循环喂狗 if (sample_flag) { sample_flag 0; do_temperature_task(); } do_display_task(); do_communication_task(); } }这套方案我从最早的原型板到现在前后迭代了好几版踩过的坑基本都写在上面了。核心体会就一句话温度监测不难难的是在真实电磁环境里长期稳定地测准、传对。传感器和MCU选对了只是起点电源、布线、滤波、通信每一环都得抠细节。你要是刚开始做类似项目建议先把最小系统跑通再逐步加显示和通信别一上来就全功能联调出了问题根本不知道是哪一环。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进