
前阵子接了一个恒温箱控制项目遇到一个很现实的问题控制板本身装在柜内要就地检测柜内温度同时还要看几米外风管里和另一个库房里的温度。说白了一套系统里既要“本地温度”又要“远程温度”而且两边都得稳定、准确、能长期跑。最开始我想的是做成传统的一主多从RS-485网络每个从站都用一颗单片机和传感器主站汇总显示。后来算了下成本、布线、调试和后期维护发现这事完全可以简化成“一颗PIC32MX534F064H做主控板上一颗PJ85718DM当本地温度探头远程节点通过通信接口回传”的结构。这套思路在嵌入式产品里很常见放到HVAC暖通空调现场一样成立。下面我把整个设计逻辑、驱动细节和调试时踩过的坑摊开聊给做温控、采集或者暖通自控的朋友做个参考。1. 为什么非要把本地温度和远程温度放进同一套系统1.1 嵌入式和HVAC的测温诉求其实是一样的很多嵌入式设备比如变频器、伺服驱动器、开关电源既要看电路板附近的环境温度又要看功率器件或电机绕组这些“远端发热点”的温度。HVAC系统更直接新风机组要测回风温度、送风温度和室外温度冷库要测多个库房温度恒温恒湿箱要测箱内温度和控制柜温度。这些场合都存在一个共同点设备自身所在的“本地点”和真正要控制的“远程点”物理上隔着一段距离但它们的数据最终要在同一个控制器、同一块屏、同一套报警逻辑里被处理。我把“本地温度”定义为设备本体附近、由控制器直接读取的温度比如PJ85718DM通过I2C挂在主控板上测出的柜内环境温度“远程温度”定义为主控够不着、需要靠通信线或总线从远处传感器节点拿回来的温度比如风管送风温度、库房温度、室外温度。两种温度如果分开做成两个独立仪表维护成本和故障隐患都会增加。1.2 一开始的“一主多从、每点一片MCU”方案为什么不划算我也见过不少项目是这么干的每个测温点放一个带单片机的采集模块各自独立跑然后通过RS-485把数据发给触摸屏或上位机。这套方案能工作但不是最优解至少有三个让我头疼的地方每个从站都要单独供电、单独调试接口、单独维护固件版本数量一多就成了灾难。从站之间的通信协议如果是临时定的后期换人维护根本看不懂排查故障要拿协议文档对着查半天。从站的硬件成本并不低一个单片机加外围电路比单纯一个传感器探头贵出一截而且可靠性反而因为“环节变多”而下降。所以更实际的做法是主控采用集成度高一点的单片机比如PIC32MX534F064H把本地传感器、远程通信协议、滤波算法、显示和报警全部收拢在一个固件里远程节点不承载协议逻辑只负责把温度变成数字量回传。这样既保留了多点布线的灵活性又没把系统复杂度推给每个从站。2. 两块芯片的分工谁负责测温、谁负责通信2.1 PJ85718DM在系统里的角色PJ85718DM是一颗I2C接口的数字温度传感器我的用法是把它放在主控板上直接测量控制器所处区域的环境温度。它的一大优点是12位分辨率温度步进能做到0.0625℃做HVAC的0.1℃显示和报警阈值判断完全够用另一个优点是有可配置的报警输出脚通常叫OS引脚可以设置成超温自动拉低接PIC32的外部中断这样即使主程序在忙着跑通信温度突然超标也能第一时间触发中断处理。这颗芯片本身功耗很低适合长时间通电运行。另外它的I2C地址可以通过A0到A2几个引脚设置理论上一条I2C总线上能挂多颗。如果一块板上有多个温度关键点比如电源模块附近、处理器散热片附近、风道入口附近用多颗PJ85718DM并联是最省事的方案驱动代码完全一致不用为每路重新开发。2.2 PIC32MX534F064H为什么能一肩挑PIC32MX534F064H属于32位MIPS M4K内核的PIC32系列主频最高可到80MHz型号里的F064代表64KB程序Flash常见封装是80引脚。它对温度监测这种任务来说算力是明显过剩的但“过剩”恰恰是好事我可以同时跑Modbus RTU主站协议、温度滤波、标定参数保存、LCD刷新逻辑还能留出余量给后续功能扩展。另外一个重要点是这颗芯片的外设丰富I2C、UART、CAN、USB、ADC该有的都有而且本身自带CAN控制器。这意味着远程接入方式很灵活大空间、远距离的HVAC现场用RS-485加Modbus RTU工厂里节点多、实时性要求高的嵌入式场景可以用CAN不用换主控。再加上它能用内部Flash保存校准偏移量和增益系数断电再上电参数不丢这对温度采集系统来说非常实用省得每次开机都重新标定。2.3 整个系统的数据流用文字说清楚系统跑起来之后的数据路径大概是这样的本地温度PJ85718DM通过I2C把12位原始温度值送给PIC32MX534F064HPIC32做换算、滤波、标定得到最终温度值。远程温度远端传感器节点可以是同样带PJ85718DM的小板也可以是第三方温度变送器通过RS-485总线把数据上传PIC32的UART收到帧解析出温度值再走同一套滤波和标定流程。输出PIC32把所有温度统一存到一个结构体里显示到LCD、上报给上位机或者用来触发本地报警继电器。这样一来本地和远程温度数据进入系统后就是“一等公民”后面做超温报警、历史记录、趋势对比全都用同一套逻辑不用再分两套代码处理。3. 从I2C寄存器到摄氏度PJ85718DM驱动的完整落地3.1 硬件连接和并联扩展的小细节PJ85718DM用3.3V供电即可。PIC32MX534F064H的I2C引脚也是3.3V电平直接连没问题。SDA和SCL每条线都需要接上拉电阻一般用2.2kΩ到4.7kΩI2C标准要求400kbps的快速模式下上拉不能太小否则边沿不够快。我习惯用4.7kΩ起步如果示波器看到SCL上升沿太缓再换成2.2kΩ。板上有多个PJ85718DM时地址引脚配置要注意错开。比如第一颗A0/A1/A2全接地地址用一个值第二颗把A0接高地址换一个值。这样初始化时就能用同一个驱动程序反复调用代码里只需要维护一张地址表。OS报警脚我推荐接到PIC32的外部中断引脚。配置PJ85718DM的比较器输出为超温有效后一旦温度超过阈值OS脚自动拉低PIC32进入中断立即执行保护动作比如关加热器、启动风扇、弹报警画面。这个机制比“轮询到下一轮才发现超温”可靠得多尤其在柜内风机故障这种需要快速响应的场景里价值很大。3.2 初始化流程连续转换模式加12位分辨率下面这段代码展示初始化过程。实际项目中寄存器地址和位定义必须对照PJ85718DM数据手册但思路是通用的先配置转换模式和分辨率再等待第一次采样稳定。#define TEMP_CFG_REG 0x01 #define TEMP_DATA_REG 0x00 #define TEMP_I2C_ADDR 0x48 /* 以实体硬件地址为准 */ void temp_sensor_init(void) { uint8_t cfg 0x60; /* 连续转换模式 12位分辨率示意 */ i2c_write_reg(TEMP_I2C_ADDR, TEMP_CFG_REG, cfg, 1); }需要注意一个问题传感器上电后并不是立即可读数尤其是第一次上电内部ADC需要完成一次转换周期。我会在初始化之后加一个延时通常等待100ms到200ms然后再启动正式轮询避免拿到全零或者复位值。3.3 读温度寄存器并按12位格式换算PJ85718DM这类数字传感器的温度寄存器通常是两个字节12位有效数据靠左对齐低4位是未定义或符号扩展位。读出来之后先组合成16位数再右移4位得到真正的12位原始值然后乘以0.0625就是摄氏度。float temp_read_celsius(void) { uint8_t buf[2]; int16_t raw; i2c_read_reg(TEMP_I2C_ADDR, TEMP_DATA_REG, buf, 2); raw (buf[0] 8) | buf[1]; raw 4; /* 12位数据是有符号数这里做符号扩展 */ if (raw 0x0800) { raw | 0xF000; } return (float)raw * 0.0625f; }符号扩展是第一次做这类传感器最容易错的地方。温度低于零度后原始值的最高位为1如果不做扩展换算出来会变成一个大正数比如-0.5℃可能被算成4095.9375℃。HVAC现场冬天测室外温度时出现这种错误轻则显示乱码重则触发错误的报警逻辑所以务必保留符号处理。3.4 精度不够怎么办两点标定和参数保存PJ85718DM的标称精度做HVAC显示和控制通常够用但如果你要控制的恒温箱要求±0.2℃以内或者传感器安装位置散热条件不理想读数会偏移这时候就必须标定。我的做法很简单用0℃冰水混合物和60℃左右的热水做两个参考点拿标准温度计最好是经过校验的和传感器同时测量记下两个点的偏差然后按下式计算修正参数。/* 两个标定点标准温度t1,t2传感器读数r1,r2 */ float gain (t2 - t1) / (r2 - r1); float offset t1 - r1 * gain; /* 之后每个读数都用 final raw_read * gain offset */标定后的gain和offset要写进PIC32MX534F064H的内部Flash或者外部EEPROM。每次上电读出来运行时直接用不能按常量藏在代码里否则换一批传感器或者重新标定后还得重新编固件。4. 把远程温度拽回来RS-485加Modbus RTU与CAN的取舍4.1 为什么远程组网优先选RS-485HVAC现场最常见的距离是几十米到几百米电磁环境也不算干净变频器、接触器、风机电机到处都是。RS-485是差分传输抗共模干扰能力强两线制布线成本低而且天然支持多点拓扑一条总线上挂几十个温度节点很轻松。从协议角度Modbus RTU在暖通和工业现场又是事实标准大部分触摸屏、PLC、上位机组态软件都原生支持所以我把“远程温度读取”首选方案定成了“PIC32 UART转RS-485加Modbus RTU主站”。物联网和云平台当然也能做远程温度但在现场设备这一层先把RS-485网络搭好后面的网关、DTU、云接入都只是在这条链路上做转发架构会很干净。4.2 Modbus RTU读取远程温度的最小实现PIC32MX534F064H发送一个标准读保持寄存器请求从站返回温度值。帧格式是从站地址、功能码、寄存器地址高字节、寄存器地址低字节、寄存器数量高字节、寄存器数量低字节、CRC低字节、CRC高字节。CRC16的计算代码如下static uint16_t modbus_crc(const uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; uint16_t i; uint8_t bit; for (i 0; i len; i) { crc ^ data[i]; for (bit 0; bit 8; bit) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; }发送请求帧的时候Modbus要求CRC低字节在前硬件字序和协议字序经常把人绕晕我直接按下面的方式填充uint8_t req[8] {0x01, 0x03, 0x00, 0x00, 0x00, 0x01}; uint16_t crc modbus_crc(req, 6); req[6] crc 0xFF; req[7] (crc 8) 0xFF; uart_send(req, 8);从站响应的第3和第4字节是温度数据我约定用有符号16位、单位0.1℃。比如25.6℃就表示成0x0100也就是256。解析时要特别注意负温度如果从站返回的是补码形式直接拼成int16_t再乘0.1f即可。int16_t raw_temp (resp[3] 8) | resp[4]; float temp raw_temp * 0.1f;用0.1℃作为远程协议单位比直接用float在总线上传要省两个字节也方便老式PLC和HMI做显示是现场很实用的惯例。4.3 PIC32MX534F064H自带的CAN怎么用PIC32MX534F064H本身带CAN控制器这算是这颗芯片给嵌入式场景留的后手。如果你的温度节点数量特别多比如一套设备里要监控三四十个点或者节点间的数据还要承担实时控制责任RS-485轮询的效率就有点不够了。CAN总线是多主方式节点可以主动上报实时性更强。我做过的最小CAN温度协议是这么设计的用11位标准标识符高3位表示帧类型低8位表示节点地址数据域里第0字节放节点编号第1字节放温度高字节第2字节放温度低字节第3字节放状态位。PIC32收到帧后按ID判断是哪个节点再按同样的0.1℃协议换算温度。这样做的好处是CAN控制器硬件自带消息滤除和错误处理主程序压力比RS-485轮询小很多。4.4 远程数据的不确定性超时、重试和离线判定远程通信和本地I2C最大的不同是“不可靠”。总线可能断从站可能掉电数据帧可能在电气干扰下CRC出错。所以PIC32跑Modbus主站时的核心逻辑不是“怎么读出正确数据”而是“读不到数据时系统怎么表现”。我的策略很简单每次轮询一个从站时设定超时时间比如200毫秒到300毫秒超过收不到有效响应就判定本次通信失败连续失败3次之后把这个节点标记为离线温度显示成“--”报警逻辑忽略该节点。一旦某次通信恢复正常自动清除离线标记。不能让一个坏节点拖慢整条总线的轮询周期也不能让远端断线后系统一直卡在等待状态不动。5. 滤波和坏值剔除温度曲线从“能看”到“能信”5.1 好好的温度数据为什么也会很脏数字温度传感器的读数一般不会像ADC采集那样满屏噪声但在HVAC现场仍然会有毛刺变频器启动时地线电位瞬变、RS-485收发切换瞬间的串扰、继电器吸合时的辐射都可能导致I2C读回一个错值或者Modbus帧被干扰。没有滤波直接显示偶尔跳出来的离谱温度会让操作人员对整套系统失去信任。5.2 中值滤波加滑动平均的组合用法温度是一个慢变量特别适合滤波。我用的是两段式先做中值滤波把偶发的毛刺彻底干掉再做滑动平均把传感器自身的微小波动压平。中值滤波原理不复杂连续取N次读数排序后取中间值作为本次输出。N通常取5到9太小压不住毛刺太大浪费算力。滑动平均就是维护一个FIFO缓冲区窗口内的所有数据求平均输出当前平滑后的温度窗口一般取8到16。#define MEDIAN_WIN 5 #define AVG_WIN 8 static uint8_t avg_buf[AVG_WIN]; static uint8_t avg_idx 0; float temp_filtered_read(void) { float samples[MEDIAN_WIN]; float sorted[MEDIAN_WIN]; float median_val; float avg_sum 0.0f; uint8_t i, j; /* 连续读MEDIAN_WIN次排序取中值示例代码完整项目中应单独维护采样队列 */ for (i 0; i MEDIAN_WIN; i) { samples[i] temp_read_celsius(); sorted[i] samples[i]; } /* 简单冒泡排序窗口小无所谓 */ for (i 0; i MEDIAN_WIN - 1; i) { for (j i 1; j MEDIAN_WIN; j) { if (sorted[j] sorted[i]) { float t sorted[i]; sorted[i] sorted[j]; sorted[j] t; } } } median_val sorted[MEDIAN_WIN / 2]; avg_buf[avg_idx] (uint8_t)(median_val * 10.0f); /* 用整数保存0.1℃精度 */ avg_idx (avg_idx 1) % AVG_WIN; for (i 0; i AVG_WIN; i) { avg_sum avg_buf[i]; } return avg_sum / AVG_WIN / 10.0f; }这段代码只是示意实际工程里中值窗口和滑动平均窗口要分开维护不能在每一次返回前都重新采一整组数据否则响应太慢。但核心思想是清楚的先挡毛刺再平滑波动。5.3 变化率限制防止“瞬移体温”有时候风管里热源突然启动真实温度确实会快速变化但不会出现0.1秒内跳5℃这种物理上不可能的事情。这种情况下我还会加一个变化率限制器每轮输出的温度与上一轮输出值的差不能超过一个设定值超过就按设定的最大斜率往上或往下“爬”。float rate_limit(float new_value, float old_value, float max_delta) { float delta new_value - old_value; if (delta max_delta) { return old_value max_delta; } if (delta -max_delta) { return old_value - max_delta; } return new_value; }变化率限制器要注意一个场景系统刚上电时上一次值是初始值0真实温度可能已经是30℃了直接限幅会把温度从0“爬”到30花掉十几秒。解决方式是上电后第一次读数直接当成初始值不经过限幅器后续才启用。5.4 坏值判定的几条硬规则滤波只能处理干扰处理不了真正的坏值。我在代码里维护了一套判定规则任何一条不满足就直接丢弃本次采样不进入滤波链读数必须落在传感器数据手册标称范围内一般取-55℃到125℃。读数不能等于传感器定义的故障码很多数字传感器在短路或断线时会返回特定全0或全1值这些值不能参与运算。远程节点的温度必须通过CRC校验且响应帧的地址、功能码必须与请求一致。6. HVAC现场调试时真正会踩的坑6.1 传感器装在哪比用什么传感器更影响结果有次某库房项目的远程探头读数总是比水银温度计低2℃左右排查了一圈软件都没问题最后发现是探头被装在风管壁外侧测到的其实是墙体和外部环境混合温度根本不是风管里的空气温度。把这个探头重新移到风管中心或至少插入风管内部1/3以上深度读数立刻就正常了。HVAC现场布传感器几条经验很实在风管温度探头要尽量顺着气流方向插入探头末端不能贴管壁房间温度传感器要避开阳光直射、窗边、散热器和空调出风口控制柜内传感器要远离大功率电阻和散热片否则当地温度测出来永远是偏高的。6.2 RS-485布线终端电阻和偏置电阻不能省远程通信最常见的诡异故障是“单独测试都好接上全部节点就乱”。根因大概率是RS-485总线没有做终端匹配或偏置。总线两端各接一个120Ω终端电阻用来消除信号反射同时A线加上拉、B线下拉保证总线空闲时电平处于确定状态避免一发噪声就被从站误判成起始位。还有一个容易忽略的点RS-485的A/B线必须用双绞线最好是屏蔽双绞线屏蔽层在控制柜侧单端接地。我之前见过用普通平行线拉几十米的情况总线稍微长一点就丢帧换成双绞线后问题直接消失。6.3 地线电位差远程读数整体偏高1℃的真实案例有次调试另一套设备远程节点读数整体偏高1.2℃而且很稳定。传感器本身标定没问题本地读数也正常问题出在远程节点电源取自不同开关电源两地之间有电位差导致传感器供电参考地和主控不一致。RS-485本身是差分传输不受影响但远程节点的传感器电源如果和主控不是同一个参考地采集数据就容易偏移。解决思路有两个能共地就共地多拉一根地线不能共地就在远程节点做隔离用DCDC隔离电源加RS-485隔离收发器传感器部分单独参考本地地。这个方案成本高一些但能根治地环路问题。6.4 传感器也会自己发热低风速环境尤其明显数字温度传感器内部有振荡器和ADC在工作自身会消耗电流产生微小温升。普通环境下0.1℃到0.3℃的误差可以接受但在低风速风管或者密闭机箱里热量散不出去自热偏差就可能被放大。PJ85718DM这类低功耗器件的自热比较小但也不能完全无视。处理办法通常有两种一是降低采样频率比如从每秒连续转换改成每2秒转换一次二是如果允许用间歇供电方式测量前先上电等稳定再读数读完断电。对HVAC这种温度变化本来就慢的场景降低采样频率完全不影响效果还能顺手降低平均功耗和自热偏移。最后说说我做完这套系统的感受温度监测本身不复杂真正的复杂度在“让温度读数长期可信”。硬件上把传感器放对位置、把总线和地线处理好软件上做好滤波、坏值剔除、超时重试和标定保存这套系统才算真正能交给现场长期运行。最后分享一个个人习惯我会在固件里保留一个调试寄存器通过串口可以把PIC32芯片每次读到的原始寄存器值、滤波前数据、滤波后数据、远程帧CRC结果全部输出到调试终端。系统上线后如果发现某路温度异常不用爬上去拆探头先看这些内部数据就能快速定位是传感器漂了、总线干扰还是软件逻辑问题。这个习惯帮我省了不止一次远程跑现场的时间。