资讯动态

基于PIC18F85J50的本地与远程温度监测系统设计与实现

发布时间:2026/10/22 12:12:09 来源:尧图企业网站定制
做嵌入式的人应该都遇到过这么一档子事读取一个温度值本身并不难难的是系统里同时出现“本地温度”和“远程温度”两路数据时怎么保证它们各说各话又互相不干扰。我在一个内部验证项目就叫模拟项目X里用PJ85718DM温度传感器做采集端用PIC18F85J50单片机做主控搭了一套本地加远程的温度监测方案既能用于HVAC的风柜、新风机组控制也能用在嵌入式机柜里的散热监测。整套东西做完之后硬件上没有太玄乎的设计真正让我花时间的是数据传输的可靠性和温度数据的可信度。PJ85718DM负责把温度变成数字量PIC18F85J50负责把本地温度和若干远端节点的温度收拢到一起再做滤波、超时判断和对外输出。下面把器件分工、硬件连接、固件流程和调试过程中踩过的坑全部过一遍希望能给正在做类似东西的人省点时间。1. 为什么本地温度和远程温度必须分开设计1.1 温度数据的语义和采集位置完全不同很多做温度监控的第一次上手会默认把问题简化成“找一颗传感器读出来显示就完了”。但在嵌入式和HVAC场景里本地温度和远程温度的物理意义、用途、可靠度要求都不一样混在一起做会出大问题。本地温度通常指主控板所在环境的温度比如控制柜内的空气温度、MCU附近的机壳温度甚至散热器表面温度。它在嵌入式系统里主要用来做风扇控制、降频保护、过热报警这些实时决策在HVAC里则对应机组控制柜内部的温度用来判断柜体通风是否正常。这类温度的特点是离MCU近走线短读取频率可以高数据出问题后排查范围也小。远程温度则完全不同。远程节点可能分布在风管的送风口、回风口或者一个房间里多个测温点甚至放在楼层的不同区域。这类温度更多是为了环境控制比如根据回风温度调节风机转速、根据区域温差做阀门开度补偿。远程节点的传输距离可能从几米到几十米中间会经过变频器、继电器、压缩机甚至强电电缆电磁干扰比板内严重得多。如果把本地温度和远程温度放在同一个逻辑里不作区分就会遇到一个很尴尬的问题本地温度瞬间跳变你会下意识怀疑是远端线路干扰结果查了半天发现是主控板电源纹波反过来远端节点丢帧时又容易误以为是本地传感器采样时序出了问题。所以哪怕最终显示界面只有一行温度值内部也必须把两路温度当成两个独立的子模块来设计。1.2 器件分工PJ85718DM采集PIC18F85J50汇聚和传输选PJ85718DM做采集端原因比较直接。它是一颗数字接口输出的温度传感器不需要外接ADC、不需要标定放大电路测量范围覆盖了嵌入式机柜和HVAC风管场景的温度区间典型精度也能满足控制系统的温控要求。最重要的是它自带数字接口可以挂在I2C总线上多个远程节点可以用一个主控、一条总线去轮询简化了接线。PIC18F85J50在这一代8位MCU里属于外设比较齐全的类型自带硬件CAN控制器同时也有多个串口、I2C和SPI模块。这一点在远程温度监控项目里非常关键。远程温度如果要走几十米不能直接把传感器的I2C信号拉出去。I2C是开漏结构走长了很容易被线缆电容拖垮而且抗共模干扰能力一般。比较可靠的做法还是远端节点各自测本地温度然后通过CAN这类差分总线把数据传给主节点。PIC18F85J50自带CAN控制器意味着不用外拖一块CAN控制芯片固件层面只需要挂一颗收发器。如果项目临时改为RS-485串口加收发器也能很快切换。所以在这个模拟项目X里本地方案是PJ85718DM直接I2C连接PIC18F85J50读取主控所在环境的温度远地方案是每个远端节点同样使用PJ85718DM由各自的MCU采集后转成CAN数据帧统一上送到主节点。PIC18F85J50更像一个数据汇聚网关的角色把本地和远程温度合成一份完整的状态表再通过串口往外发。对比项本地温度远程温度采集位置主控板/PIC18F85J50附近风管、房间、远端设备传感器PJ85718DMPJ85718DM节点传输方式I2C板内走线CAN总线远距离传输主要作用散热控制、过温保护环境控制、联动调节可靠性要求采样频率高读取不能卡死协议要有超时、重发和报警机制2. 硬件细节传感器怎么接、供电怎么处理、线长怎么控制2.1 PJ85718DM 与 MCU 的接口选择PJ85718DM的手册上支持标准I2C接口我实际设计时也是走的I2C。之所以没有用SPI是因为I2C只需要两根信号线SCL、SDA加上电源和地一共四根在主控板上占用的IO非常少。如果需要扩展更多温控点I2C还能通过地址选择引脚挂多颗传感器用一对主从地址轮询就可以SPI则每颗传感器要占一个片选脚板内布线会麻烦不少。I2C接线的基础很简单PJ85718DM引脚接到哪里说明VDD3.3V电源我用的这颗允许3.3V供电和PIC18F85J50保持一致GND系统地单点接地避免地环路SCLMCU的SCL通过上拉电阻到VDDSDAMCU的SDA通过上拉电阻到VDDAD0/AD1高/低电平组合用来配置I2C从机地址I2C总线必须接上拉电阻这一点经常被忽略。我这次用的主控IO是标准3.3V电平上拉电阻选的是4.7kΩ线很短时还算稳定当我把传感器用一个10cm左右的转接板引出后发现总线边缘变差后来换成了2.2kΩ才恢复。上拉电阻不是固定值它需要结合总线线长、挂载器件数量、通信速率一起调。如果手里有示波器应该看一下SCL和SDA的上升沿如果边沿拖得又长又圆大概率就是上拉电阻偏大。2.2 供电、去耦和参考电压传感器供电如果和继电器、风阀电机共用一路电源温度读数会随机出现毛刺。HVAC柜子里经常有24V转5V、5V转3.3V的多级电源数字芯片和传感器最好都从稳压后的3.3V取电并且要求这一路稳压芯片的输出波纹足够小。我在模拟项目X里给PJ85718DM加了两级滤波靠近传感器电源引脚放了一个100nF陶瓷电容再在前一级放了一个10μF钽电容或者MLCC。这么做不是玄学陶瓷电容负责滤高频噪声大电容负责吸收负载突变时的电压跌落。如果空间允许还可以加一个1Ω到10Ω的小电阻做RC滤波效果会更好。需要注意的是PIC18F85J50的ADC和内部参考电压如果也要用来做其他模拟量采集它的AVDD/AGND引脚要保持干净。我最初犯过一个错误为了省事把模拟地直接和数字地大面积连通导致PIC18F85J50的ADC读数在变频器启动时会抖动。后来把模拟地、数字地做了单点汇接问题才消失。温度传感器是数字接口本身不太怕模拟噪声但主控的供电质量直接影响I2C时序稳定性和CAN收发器的共模表现这一点不能省。2.3 长线布局与CAN物理层的选择远程节点之间的距离如果超过一米I2C就不太合适了。我在模拟项目X里把远程节点全部设计成CAN总线连接PIC18F85J50本身带CAN控制器外部只需要一颗CAN收发器。CAN是差分信号两条线互为参考对共模干扰有天然抑制能力很适合走HVAC现场那种电磁环境复杂的路线。CAN总线物理层的几个硬性规矩需要遵守总线两端各接一个120Ω终端电阻不能省。我调试时遇到过远程节点距离较长、终端电阻只在一端接的情况表现为波特率较高时丢帧低波特率还可以实际上就是信号反射在作怪。两端都接了120Ω之后波形干净了很多。线缆选择上我用的是屏蔽双绞线CAN_H和CAN_L绞在一起屏蔽层只在主控端单点接地。这样做的原因是避免屏蔽层两端都接地后形成地环路电流地环路会直接在屏蔽层上感应出低频噪声反而影响差分信号质量。另外收发器一定要选带隔离的或者在电源侧做隔离。HVAC的现场设备经常和风柜、压缩机共用配电地电位差在几十伏甚至上百伏很正常如果直接用非隔离收发器插拔远端节点时容易把主控的供电打掉。我见过不止一次远程节点安装时“啪”一声主机重启就是因为地电位差太大电流从CAN线回流到了主控板上。隔离收发器虽然贵一点但值得。3. 固件设计从读一个字节到组织完整温度帧3.1 PJ85718DM 的初始化和读数流程固件部分我先说最基础的传感器驱动。PJ85718DM上电后默认会自行开始转换MCU只需要通过I2C读取温度寄存器的值。因为实际芯片的寄存器地址和配置位可能随批次不同我贴的代码是逻辑示意具体地址以你拿到的芯片手册为准但整体流程是通用的。#define TEMP_ADDR_BASE 0x48 // AD0/AD1全部接地时的从机地址 void sensor_init(void) { i2c1_start(); i2c1_write((TEMP_ADDR_BASE 1) | 0); // 从机地址 写标志 i2c1_write(0x01); // 配置寄存器 i2c1_write(0x60); // 设置转换分辨率/工作模式 i2c1_stop(); } int16_t sensor_read_temp_raw(void) { uint8_t msb, lsb; int16_t raw; i2c1_start(); i2c1_write((TEMP_ADDR_BASE 1) | 0); i2c1_write(0x00); // 温度寄存器地址 i2c1_rep_start(); i2c1_write((TEMP_ADDR_BASE 1) | 1); // 切换为读模式 msb i2c1_read_ack(); lsb i2c1_read_nack(); i2c1_stop(); raw (msb 8) | lsb; return raw 4; }读出来的raw是一个12位补码数据也就是左对齐寄存器右移4位后得到的结果。把它转成实际的摄氏度时我习惯先在内部统一转成“0.01℃”的整数方便后面显示和比较阈值避免到处用浮点数。int16_t raw_to_deg_x100(int16_t raw) { return (int16_t)((int32_t)raw * 625 / 100); }这个换算基于传感器分辨率0.0625℃也就是1个LSB等于6.25个0.01℃所以乘以625再除以100就得到0.01℃单位。比如读回raw400实际温度就是400 X 0.0625 25.0℃换算后为2500。3.2 滤波和异常值剔除硬件上滤波再多软件上依然要再处理一次因为HVAC现场的瞬变干扰无规律可循。我最开始直接显示原始读数压缩机一启动风柜控制柜内部湿度大、动力线距离近温度值偶尔会从25℃瞬间跳到40℃又跳回来这种数据直接参与控制会闹笑话。软件滤波我选的是最实用的滑动平均加突变剔除组合。滑动平均能压低随机噪声突变剔除能挡住真正不该有的错误值。滤波窗口选8个样本对100ms读取一次的系统来说就是800ms的平滑周期这个延迟对HVAC和散热控制完全够用。#define FILTER_N 8 int16_t temp_filtered(int16_t current) { static int16_t fifo[FILTER_N]; static uint8_t idx 0; static int32_t sum 0; int16_t prev; prev fifo[idx]; fifo[idx] current; sum current - prev; idx (idx 1) % FILTER_N; return sum / FILTER_N; }平滑之前要先做合理性判断如果本次读数和上一次滤波值的差超过10℃大概率是传感器在转换过程中被读了一次中间状态或者线路受干扰。对于这类样本我直接丢弃不参与滑动平均等待下一次读数。这个“先剔除再滤波”的顺序很重要如果把错误值也扔进平均窗口要等8次采样后才能恢复回来反应就慢了很多。3.3 远程温度帧协议CAN ID、数据域、CRC和超时机制远程温度比本地温度多了一整个传输链路的可靠性问题。PIC18F85J50自带CAN控制器CAN协议本身有硬件CRC但应用层依然要设计数据帧否则节点掉线、数据旧了你都不知道。我在模拟项目X里定义了一种简单的远程节点温度帧。CAN标准帧的11位ID分成两部分高3位是功能码低8位是节点地址。功能码0x1表示温度数据0x2表示心跳/报警。帧字段字节位置内容说明温度值0-1有符号整数单位0.01℃高位在前状态标志2bit0表示数据有效bit6表示传感器故障bit7表示节点低电压序列号3从0循环递增用于丢帧检测CRC84覆盖第0到第4字节前7位用于应用层校验预留5-7固定填0方便后续扩展节点每500ms发送一帧温度数据主控按节点ID分别维护各自的最后接收时间。如果超过1.5秒没有收到某个节点的任何帧就判定该节点超时UI上直接标红并把这个状态写进对外输出的状态字节里。超过5秒无数据则判定离线。typedef struct { uint8_t node_id; int16_t temp_x100; uint8_t status; uint8_t seq; uint32_t last_rx_tick; uint8_t online; } remote_node_t; void handle_can_temperature_frame(CAN_message_t *msg) { uint8_t node_id msg-id 0xFF; uint8_t fn_code (msg-id 8) 0x07; if (fn_code ! 0x01 || node_id REMOTE_NODE_MAX) { return; // 无效帧直接丢弃 } remote_node_t *node remote_nodes[node_id]; node-temp_x100 (msg-data[0] 8) | msg-data[1]; node-status msg-data[2]; node-seq msg-data[3]; node-last_rx_tick tick_now(); node-online 1; }3.4 主循环调度和看门狗PIC18F85J50这类8位MCU做温度汇聚资源虽然有限但用前后台结构就能完成。主循环里按固定节拍做三件事读本地温度、处理CAN接收队列、更新输出状态。读取传感器时要注意一个关键设计I2C操作不得阻塞等待超过几十毫秒。PJ85718DM启动一次转换的典型时间在几十毫秒级别如果每次读之前都死等转换完成系统会被拖死。更好做法是上次读取后隔一段时间再来读读到的数据永远是上一次完成转换的结果只要读取周期比转换周期长数据就能跟上。我在模拟项目X里把主导周期设为100ms远大于传感器转换时间因此每次读到的都是有效数据。void main(void) { sys_init(); i2c1_init(100000); can_init(125000); sensor_init(); while (1) { int16_t local_raw sensor_read_temp_raw(); int16_t local_temp temp_filtered(local_raw); pack_and_send_local_temp(local_temp); handle_can_rx(); check_remote_nodes_timeout(); update_uart_output(); feed_watchdog(); delay_ms(100); } }看门狗要喂但要喂得聪明。我在主循环每个周期结束后直接喂狗如果系统因为I2C总线挂死或某种异常进入死等看门狗会在几百毫秒内复位MCU。这也是为什么前面强调I2C不能阻塞过久一旦阻塞时间超过看门狗超时系统会不断复位现象比不喂狗还难查。4. 调试实录读不到设备、温度乱跳、远程丢帧的完整排查4.1 排查链路一I2C总线挂死与上拉电阻模拟项目X第一次联调时系统的现象是偶发性读不到PJ85718DM的数据。代码在等待读回复时某次会永久卡住。你拔掉传感器重新插上又能跑但过一会儿又卡死。我第一步用示波器观察了SCL和SDA两根线。发现异常时SDA被拉低SCL却在高电平这是典型的从设备“吞掉”最后一个时钟后总线状态处于非法状态。这时主控又不是总线的拥有者无法正常发stop条件。排查下来直接原因有两个第一个是上拉电阻选了10kΩ而传感器和主控之间有一段转接线线电容偏大上升沿太慢第二个是中断服务程序里也进行了一次I2C读取主循环里的I2C操作被中断嵌套打断时序被撕碎。解决方法是把上拉电阻换成2.2kΩ并把所有I2C读写在主循环中集中执行不允许在中断服务程序里操作I2C总线。最后还在I2C读取函数里增加总线恢复逻辑当检测到SDA长时间为低且没有ACK恢复时强制产生9个时钟脉冲把从设备从错误状态中释放出来。这个总线恢复函数在量产测试里救了很多次命。void i2c_bus_recover(void) { // 如果SDA被拉低强制产生9个SCL脉冲 for (uint8_t i 0; i 9; i) { gpio_set_scl(0); delay_us(5); gpio_set_scl(1); delay_us(5); } i2c1_stop(); }4.2 排查链路二温度值“瞬间归零”与软件保护温度值瞬间跳变到接近0是我做HVAC类项目时遇到最魔幻的问题。传感器读数在风扇启动瞬间从正常的25.1℃变成0.06℃而且不是每次都有几天出现一次。我用串口把原始寄存器值打出来发现异常样本的原始值是0x07FF这个值在符号位为0时表示温度很高但转换成有符号int16_t后再右移却会被当成了接近0的正值。问题根源是寄存器高位的符号位被干扰翻转了或者转换未完成时读了中间值。我在软件里做了三道保护。第一道是读取后校验符号扩展是否合理如果读数的绝对值超过物理量程范围比如低于-40℃或高于125℃直接丢弃。第二道是连续性校验本次读数和上次滤波值差超过10℃且维修现场没有任何能解释这种突变的物理过程也丢弃。第三道是把异常样本计数累加连续异常超过3次就主动报警而不是强行用异常值。这三道加完以后温度显示稳定了很多。4.3 排查链路三CAN远程节点丢帧与心跳机制远程节点上电后能通信但主控侧会随机出现超时报警。一开始我以为是CAN波特率不对调了波特率也没解决后来才发现是接收端处理方式的问题。PIC18F85J50的CAN接收可以在查询和中断两种模式下工作。我最开始用查询方式主循环里轮询接收标志位但主循环里还要做I2C读取、温度滤波、串口输出循环周期一旦被传感器读取卡住几十毫秒CAN接收缓冲区就会被新数据覆盖造成丢帧。远程节点一多丢帧率直线上升。解决办法是把CAN接收改成中断驱动在接收中断里把帧拷贝到自定义接收缓冲区主循环只处理缓冲区里的数据。同时利用CAN硬件过滤功能只接收本节点ID范围内的帧避免无效数据打断主循环。这个改造完成后125kbps波特率下三个远程节点连续跑48小时应用层丢帧率为零。这里也体现了一个通用经验任何外部通信接口要么全部中断驱动要么全部查询式最忌讳混合。I2C这种慢速总线适合查询CAN这种可能有突发数据到来的总线适合中断千万不要在查询式代码里又开几个通信中断。5. 这套方案在HVAC与嵌入式里的实际效果和改进空间5.1 实测数据表现和功耗情况模拟项目X最后放到一个风柜控制柜里做了48小时连续运行测试。本地温度读取周期100ms滤波后读数波动范围在0.1℃以内三个远程节点分别放在回风管、送风管和一个房间传感器处每500ms上报一次主控显示刷新周期1秒。测试期间变频器反复启停CAN没有出现应用层丢帧I2C也没有再挂死。功耗方面PIC18F85J50工作在内部时钟降频模式下远程节点每次发送完温度帧后进入短暂空闲等待整机平均电流比我最初用外部高速晶振时低了很多。如果你做的是电池供电的手持温湿度记录仪建议把传感器从连续转换模式改成单次转换模式读完就休眠功耗能再降一个量级。5.2 扩展方向多节点、透明输出、协议网关这套系统最自然的扩展路径是三件事第一件是增加远程节点的数量。CAN ID低8位理论上可以挂多个节点配合PIC18F85J50的硬件过滤表每个主控可以管理十几个远程温度节点。只需要在节点地址分配和超时表上做对称扩展。第二件是把对外输出做成透明串口协议。目前主控把本地和远程温度打包成结构体通过UART周期发送。如果后续要接HMI组态屏或者上位机只需要在UART输出层加一个透明转发协议不改动传感器读数和CAN处理逻辑。温度融合逻辑和通信逻辑分离是我这次最满意的设计决定。第三件是把CAN数据转发到更高层网络。PIC18F85J50本身没有以太网但通过UART外接一个网络模块把CAN节点温度帧转换为文本流就可以让多个控制柜的温度数据汇总到上级管理系统。要注意的是转发层要把节点ID映射成更友好的设备编号比如“机柜A-回风温度”而不是直接丢一串16进制数据出去。5.3 个人关于温度监控可靠性的一点体会做完这个项目我的一个核心体会是温度监控项目里真正花时间的不是把温度读出来而是让温度数据在传输、滤波、超时、报警这些环节里保持可信。一颗传感器坏没坏、一条线缆通不通、一个节点掉没掉线都要在软件里有明确的状态管理。如果接下来有人让我提建议我会说先把数据帧里的“数据有效位”“节点状态位”“超时标志位”这三个东西做好再去纠结0.01℃的精度。因为HVAC和嵌入式控制系统里一个“错误但看起来正常”的温度值远比一个“离谱到一眼能看出来坏了”的温度值危险得多。把这个逻辑理顺了后面的扩展都是水到渠成的事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑