☰
基于PIC32与PJ85718DM的本地远程温度采集系统设计
2026/10/22 17:02:46 网站建设 项目流程

前阵子接了一个恒温箱控制项目,遇到一个很现实的问题:控制板本身装在柜内,要就地检测柜内温度,同时还要看几米外风管里和另一个库房里的温度。说白了,一套系统里既要“本地温度”,又要“远程温度”,而且两边都得稳定、准确、能长期跑。最开始我想的是做成传统的一主多从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位原始温度值送给PIC32MX534F064H,PIC32做换算、滤波、标定,得到最终温度值。
  • 远程温度:远端传感器节点(可以是同样带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-485

HVAC现场最常见的距离是几十米到几百米,电磁环境也不算干净,变频器、接触器、风机电机到处都是。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结果全部输出到调试终端。系统上线后如果发现某路温度异常,不用爬上去拆探头,先看这些内部数据就能快速定位是传感器漂了、总线干扰还是软件逻辑问题。这个习惯帮我省了不止一次远程跑现场的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询