基于PIC18F87J11和PJ85718DM的嵌入式温度监测系统设计

发布时间:2026/10/22 6:43:23
基于PIC18F87J11和PJ85718DM的嵌入式温度监测系统设计
做温度监测项目如果只盯着温度值本身后面大概率会被各种看似等价、实际不同的坑拖住。我前阵子定下一套方案主控用 PIC18F87J11温度信号统一接到 PJ85718DM 这颗温度前端器件上目标场景是嵌入式设备内部监测和 HVAC 风管温度监测——柜门上有本地显示远程监控端也要能读到同一个温度。这篇就把选型逻辑、硬件接法、固件处理和控制联动一次讲透适合正在做嵌入式环境监控、温控器或暖通集控的同行参考。1. 先理清需求一条温度数据为什么要分本地和远程两个出口我见过不少项目设备本地显示一个温度值远程平台也拿一个温度值结果两个数对不上最后查了一整天发现根本不是同一个隐患而是从一开始就没有把本地和远程当成两套独立需求来设计。1.1 本地温度与远程温度的真实差异本地温度是给现场运维人员和本地控制逻辑看的。它要求刷新快、可重复、低延迟一般 500ms 到 1s 刷一次就非常够用因为空气温度、管道温度都是慢变量再快也看不出物理意义。远程温度是给中控室或云端平台看的它的主要价值是趋势记录、报警溯源和跨点位对比刷新周期放到 1s 到 10s 都可以接受关键是数据要有时间戳、要有连续性偶尔丢一帧问题不大但不能出现长时间空白。HVAC 场景里还有一个容易被忽略的差异本地温度通常被控制回路直接使用比如风机启停、阀门调节远程温度主要被值班人员用于监控一般不直接参与闭环控制。这意味着两者的可靠性权重完全不同。本地链路断了设备至少要能按最后有效值维持保护逻辑远程链路断了本地系统不能跟着瘫。1.2 控制用哪一路参考用哪一路在嵌入式应用里很多工程师喜欢把当前温度做成一个全局变量本地显示读它远程上报也读它控制逻辑也读它。这个做法在原型阶段没问题但到了工程阶段就会埋雷一旦网络任务卡顿、滤波逻辑调整或者远程读取触发重发同一个变量可能被多个任务同时碰读出来的瞬时值已经和现场物理温度有一定偏差。我现在的习惯是明确分成三份一份是采集值由温度采样任务写入一份是控制值由滤波和防抖逻辑计算后供本地控制使用一份是上报值由通信任务按自己的节奏读取并加上时间戳。这三分数据在数值上可能差几百毫秒甚至一两秒但没有关系因为它们的用途不一样。控制端不允许因为通信繁忙而延迟动作上报端也不应该为了追求实时而牺牲缓冲。把这层边界画清楚后面所有功能都顺了。1.3 PJ85718DM 与 PIC18F87J11 的角色分工这套方案里我把 PJ85718DM 定位为温度采集前端负责把传感器信号变成规整的数据。传感器可能是 NTC、PT100 或者数字温度芯片前端器件要做的事情包括通道切换、信号调理、线性化、冷端补偿这些脏活累活。主控 PIC18F87J11 则专注于应用层数据聚合、显示、按键、报警、继电器输出、通信协议栈。为什么不用一颗大 SoC 把信号链和应用全包了因为项目需要的是灵活替换。前端器件坏了主控和通信协议都不需要改主控要升级通信方式传感器采集链路也不用动。而且对 HVAC 这种现场环境信号链的稳定性和主控的通用性往往是两个维度的问题拆开之后各自做优化会更舒服。2. 硬件接入传感器选型、线缆处理和通信接口的三个错位点硬件设计看着简单传感器接上、I2C 拉两根线、主控读寄存器但真正到现场跑起来总是会被一些小问题拖住。我把最容易出问题的几个位置单独拿出来说。2.1 传感器选型与线缆布置NTC、PT100 还是数字温度芯片先说结论嵌入式板内测温优先用数字温度芯片或者前端器件自带的片上传感器走线短干扰少HVAC 风管或水管测温优先用 PT100/PT1000 或高精度 NTC因为传感器可以远离主板辐射状线缆铺设也更方便。项目NTC 热敏电阻PT100/PT1000数字温度芯片成本低中高低线性度差需要查表或前端线性化好前端处理简单好自带数字输出抗干扰对线阻和接触电阻敏感三线/四线接法可消除线阻数字信号抗干扰强但传输距离受限常见场景室温、风管温度水管温度、精密监测PCB 板内温度、机箱温度传感器线缆布置上有一个特别容易被忽略的点不要把传感线跟 220V 动力线绑在同一个线槽里也不要把传感线的屏蔽层两端都接地。屏蔽层单端接地是最稳妥的现场如果出现温度值周期性跳动先查的就是这条线是不是在强电附近走了一段。2.2 PJ85718DM 的读取接口与上电时序PJ85718DM 这类前端器件和主控之间一般用 I2C 或 SPI 通信。我在这套方案里用的是 I2C原因很简单走线少多块板子可以共用总线调试时也方便挂一个总线分析工具。接线时 SDA、SCL 必须有上拉电阻具体阻值要看总线上的器件数量和通信速率。如果前端器件还提供了地址选择引脚多个采集节点共用同一条 I2C 总线时要提前规划地址避免上电后冲突。上电时序上有一个经验主控复位后不要立刻去读温度。前端器件内部的电源稳定、ADC 自校准、参考源建立都需要时间常见的做法是上电后等待几十毫秒到一两百毫秒再做一次写配置、读状态、读数据的完整握手。如果上电就读容易读到全 0 或全 F然后被当成真实故障记入报警误导后续排查。2.3 主控 IO 与继电器动作带来的干扰HVAC 设备里主控旁边就是继电器、接触器、风机变频器这些负载动作瞬间电流变化很大会给传感器供电和模拟信号带来干扰。我的做法是把继电器驱动与主控 IO 之间加光耦隔离继电器线圈的供电和主控电源完全分开共地点只在电源入口处单点汇合。如果传感器线比较长采集通道入口还要加 TVS 或 ESD 保护二极管防止雷击感应或设备起停时产生的瞬态电压直接烧掉前端器件。3. 固件里最考验耐心的部分采样状态机、滤波和异常判定硬件点灯成功后真正的工程量在固件。温度采集这块我建议不要写成一个 while 循环里不断读寄存器而是用一个采样状态机把整个流程管起来这样后续加网络、加控制、加报警都好扩展。3.1 为什么不在定时中断里直接读温度很多初学者会把温度读取放在定时中断里觉得这样准。但 I2C/SPI 通信本身需要时序如果中断里还有其他任务或者更高优先级的中断插进来总线时序就会被拉长甚至破坏而且调试起来非常痛苦。温度是慢变量放在主循环任务里轮询完全来得及。我习惯把温度采样任务设计成一个小型状态机typedef enum { TEMP_IDLE, TEMP_START_CONV, TEMP_WAIT_CONV, TEMP_READ_DATA, TEMP_FILTER, TEMP_FAULT_HANDLE, } temp_state_t; void temperature_task(void) { switch (temp_state) { case TEMP_IDLE: if (sampling_timer_expired()) { temp_state TEMP_START_CONV; } break; case TEMP_START_CONV: front_end_start_convert(0); front_end_start_convert(1); /* 本地通道 远程通道 */ temp_state TEMP_WAIT_CONV; break; case TEMP_WAIT_CONV: if (front_end_conversion_done()) { temp_state TEMP_READ_DATA; } break; case TEMP_READ_DATA: front_end_read_temp(0, local_raw); front_end_read_temp(1, remote_raw); temp_state TEMP_FILTER; break; case TEMP_FILTER: local_temp lowpass_filter(local_temp, local_raw, FILTER_ALPHA); remote_temp lowpass_filter(remote_temp, remote_raw, FILTER_ALPHA); temp_state TEMP_IDLE; break; default: temp_state TEMP_IDLE; break; } }这段代码不是某个具体编译器的寄存器级写法只是状态机的骨架。实际工程里你把前面器件提供的驱动函数包成front_end_start_convert()、front_end_read_temp()上层逻辑就非常干净。3.2 滤波参数既要平滑又不能吃掉真实变化温度滤波有很多选择滑动平均、一阶低通、中值滤波。我在 HVAC 项目里最常用的是一阶低通 突变门槛组合。一阶低通的递推公式长这样static uint16_t lowpass_filter(uint16_t prev, uint16_t current, uint16_t alpha) { return (uint16_t)((uint32_t)prev ((uint32_t)(current - prev) * alpha 10)); }这里的alpha是 0 到 1024 之间的权重alpha越大新值占比越高响应越快但滤波能力越弱。alpha越小输出越平滑但温度真实变化也会被拉得很钝。实际调参时我会在 0.05 到 0.3 之间先试一轮然后去现场看几分钟温度曲线如果噪声还是肉眼可见就继续调小如果升温降温时输出总比参考表慢半拍就适当调大。还要加一个突变保护如果本次采样值和上一次滤波结果的差值超过 5°C一般不是真实温度跳变更可能是传感器松动、静电干扰或者读数错误。此时不直接承认这个点而是连续再采三次三次都确认超限才更新。3.3 本地和远程读数不一致时先拆链路再下结论现场最容易遇到的质疑是本地显示 22.3°C远程平台怎么显示 22.1°C这不是 bug而是链路不完全一致。本地显示每秒刷新远程上报每 5 秒甚至更久才刷新两者读数有时间差前端器件的本地通道和远程通道可能接的是不同位置的传感器测温点本身就有物理温差。排查的顺序我一般是从物理层开始查。先拿一个参考温度计分别放在两个传感器旁边确认两者的真实温差再把前端器件的原始读数打出来确认是不是滤波造成的滞后最后再查远程通信链路有没有重传、缓存或丢包。按这个顺序走九成以上的对不上都能解释清楚剩下的是温度和位置本身的差异。4. 远程链路怎么选Modbus RTU、Modbus TCP 还是私有 JSON 协议远程温度如果不上通信链路标题里远程两个字就只是空话。工程上远程方式非常多我不打算全都展开只讲 HVAC 和嵌入式设备里最常用的几条路。4.1 先用 Modbus RTU 打通 HVAC 现场的中控集成HVAC 集控系统对 Modbus 的接受度非常高。很多控制柜、触摸屏、上位机组态软件都原生支持 Modbus RTU over RS-485所以我通常会把寄存器表当作远程温度监测的基线协议而不是自己造一套复杂 JSON。寄存器规划可以做得很直观寄存器地址读写属性内容0x0001只读本地温度有符号整数单位 0.1°C0x0002只读远程温度有符号整数单位 0.1°C0x0010只读状态字节bit0: 本地故障bit1: 远程故障0x0020读写温度报警上限单位 0.1°C0x0021读写温度报警下限单位 0.1°C寄存器规划完通信任务就按主从关系轮询。Host 发请求MCU 回响应中间用 CRC 校验。波特率我一般选 9600 或 19200轮询周期不低于 500ms因为 HVAC 的中控屏通常同时挂很多台设备太快反而会把总线占满。4.2 TCP 网关与本地缓存的组合如果远程监控端在另一个网段或者要走云平台我会在 RS-485 总线上挂一个串口转以太网/4G 网关。MCU 本身不需要去处理复杂的 TCP/IP 协议栈只需要稳定地把温度数据和状态数据发给网关网关负责把 Modbus RTU 转成 Modbus TCP 或直接以 JSON 上报云平台。这种做法的好处是把现场通信和网络通信隔离。网络不稳定、云端服务器更换、甚至整条 TCP 连接重建都不会影响本地总线上其他设备的数据交互。MCU 一旦发现自己上报的数据没有得到网关确认就把带时间戳的数据放进环形缓冲区等链路恢复后再按序补传。缓冲区大小我一般在 RAM 里开 128 到 1024 条记录每条记录包含时间戳和两个温度值。数据量小不需要频繁写 Flash否则存储颗粒的寿命会很快被写满。4.3 断线、重连和数据补传的完整逻辑补传不是把缓存里的旧数据一股脑全发出去那样会让平台端的趋势曲线出现一截历史数据倒灌反而难看。我的做法是恢复连接后先发一条设备上线状态把当前实时温度发出去再按时间戳顺序补传最近 N 条缓存记录最后再进入正常周期上报。补传数量要让远端可以配置比如默认补 30 条。如果断线时间太长、积压数据已超过缓存容量就直接丢弃旧数据只保留最后有效的一段。很多平台侧看的是趋势尾段不是断线前几天的数据。5. 温控动作不要只看温度数字回差、报警与执行器联动HVAC 应用里温度监测最终会落到控制动作上比如开加热器、开风机、开关阀门。这部分如果只按超过设定值就关低于设定值就开来写现场一定会出现继电器疯狂吸合断开的动静。5.1 回差滞回怎么设才合适回差的核心作用是让动作边界在升温和降温两个方向上错开一段距离避免温度在设定点附近抖动时执行器频繁翻转。#define SET_POINT 22.5f #define HYSTERESIS 1.0f static bool heat_output false; void control_update(float current_temp) { if (!heat_output current_temp (SET_POINT - HYSTERESIS / 2.0f)) { heat_output true; relay_on(); } else if (heat_output current_temp (SET_POINT HYSTERESIS / 2.0f)) { heat_output false; relay_off(); } }回差取值一般取 0.5°C 到 1.5°C。取值太小执行器照样抖取值太大温度波动区间会变宽体感不舒服。具体数值要看负载的热惯性风机盘管这类小惯量负载可以小一点大面积水管加热负载就取大一点。5.2 报警确认机制连续三次才算数温度越上限报警不能看单次采样因为传感器尖刺、I2C 读错、现场干扰都可能让瞬时值跳得很夸张。我习惯在滤波后再做报警确认连续三个采样周期都越过上限才真正触发报警之后只要有一次低于回差内的恢复值就清零计数不产生报警记录。报警恢复有两种模式自动恢复和手动确认。自动恢复省心但有些场合需要维护人员到现场确认处理结果这时候就用手动确认。手动确认的逻辑也简单远程端下发一个确认报警命令MCU 清掉报警标志并记录一次事件。5.3 本地保护逻辑必须高于远程控制远程平台可能会下发强制开启加热器或允许温度超过上限之类指令这时候要清醒一点所有可能损坏设备或引发安全问题的操作都应该由本地固件直接否决。我在这套方案里实现的最低保护逻辑是无论远程怎么设置只要本地温度超过 90°C 或前端连续读取失败达到一定次数强制关闭加热输出并进入故障状态。这条规则一旦被突破设备就可能被远程误操作拖进危险工况。6. 标定、量产与维护真正拉开项目差距的地方温度监测系统原型跑通不难难在批次稳定和后期维护。以下三点是我做过几个类似项目后觉得最应该提前规划的。6.1 两点校准偏置和增益要分别考虑前端器件就算做了线性化传感器本身的离散性也会让每块板子的读数不完全一致。最简单的校准方式是两点校准把传感器放进两个已知温度环境比如 20°C 和 60°C 的恒温槽读取实际值然后计算增益系数和偏置量。cal_offset (actual_high * raw_low - actual_low * raw_high) / (raw_low - raw_high); cal_gain (actual_high - actual_low) / (raw_high - raw_low);实际使用温度时temperature raw_temperature * cal_gain cal_offset;校准参数要存到非易失存储区并在参数区写入 CRC 校验。每次上电校验失败就回退到默认校准值同时置一个校准异常标志避免拿错误参数去控温。6.2 参数存储与固件升级的思路HVAC 设备一旦安装很难经常拆机刷固件所以升级支持很重要。如果 MCU 支持 IAP就把 Flash 分成 boot 区和 app 区。boot 区负责校验 app 的 CRC校验通过再跳转升级过程中意外断电boot 区还能让设备重新进入升级模式不至于变成砖。参数区尽量不要和程序区放一起独立划分一块区域并保存双备份写参数时先写备份区成功后再更新主区。这样任何时刻断电至少有一个区域的参数是完整的。6.3 量产一致性温度测试工装比想象中更重要批量生产时每一块板子都要经过基本校准。我的做法是做一个小测试工装把多块设备同时放在一个温控箱里设定 25°C 和 50°C 两个校准点通过串口或数码管输出每块板的原始温度读数。判断标准不是绝对精度要多高而是同一温度下各板之间的最大偏差要控制在一个范围内比如 ±0.5°C。超过范围的就打上标记重新贴传感器或调整校准参数。这一步看起来增加了生产工时但能大幅度降低售后排查成本。温度类故障在现场特别难定位因为环境温度本来就不稳定很难判断是传感器坏了、前端松了还是参数不对。出厂前多花一分钟现场就少熬一次夜。我自己做完这套系统之后回头看真正花时间的不是画原理图也不是写协议而是把每一个异常分支都处理干净。温度和网络都是慢变量但慢变量恰恰最容易在看起来没毛病的时候突然出问题。如果你手头的项目同样用 PJ85718DM 这类前端做主数据采集、用 PIC18F87J11 做主控建议从一开始就把本地链路和远程链路当成两套独立系统来设计各自有降级策略最后在数据层做汇聚。这样调试成本最低上线之后也最不容易半夜被叫起来。