☰
PIC18F87J10与PJ85718DM实现嵌入式温度采集与以太网远程上报
2026/10/11 4:42:55 网站建设 项目流程

1. 从一个温度采集需求说起:为什么选这套组合

嵌入式温度监测这件事,看起来简单,真做起来坑不少。我最早接触这类需求是在一个暖通空调控制板的项目里,当时的要求是:本地要实时读取板载温度传感器,同时还要通过远程接口把温度数据汇总到上位机,用于楼宇级别的能耗分析和设备保护。难点不在于“读温度”本身,而在于本地与远程两路数据的一致性、采样时序的稳定性,以及通信链路的抗干扰能力。

这套方案的核心是两颗芯片:PJ85718DM和PIC18F87J10。前者是一颗带 I2C 接口的数字温度传感器,后者是 Microchip 经典的 8 位单片机,自带以太网控制器。一个负责“感知”,一个负责“处理与上报”,分工非常清晰。我选择这个组合的原因很直接:PIC18F87J10 内部集成了 10/100 Mbps 以太网 MAC 和 PHY,不需要外挂网络芯片就能实现远程数据上传,而 PJ85718DM 的 I2C 接口占用引脚少、精度够用,两者搭配在成本、功耗和开发难度上达到了一个很好的平衡点。

这篇文章适合谁看?如果你正在做嵌入式温度采集、HVAC 控制器开发,或者需要把本地传感器数据通过网络上报,那这套思路可以直接参考。我会从硬件连接、寄存器配置、采样逻辑、远程通信、问题排查几个维度,把整个实现过程拆开讲清楚。即使你用的是别的单片机和传感器,底层的设计逻辑也是通用的。

2. 硬件设计与器件选型背后的考量

2.1 PJ85718DM 的关键特性与选型理由

PJ85718DM 是一颗低压数字温度传感器,支持 I2C 总线通信,典型精度在 ±0.5°C 范围内,工作电压覆盖 2.7V 到 5.5V。这个电压范围很关键,因为 PIC18F87J10 的 I/O 电平是 3.3V 或 5V 可选,传感器能直接对接,不需要额外的电平转换电路。

选它而不是选模拟传感器加 ADC 的方案,主要考虑三点。第一,数字接口抗干扰能力强,HVAC 环境里继电器、风机、压缩机启停带来的电磁干扰很大,模拟信号走线稍长就容易耦合噪声,而 I2C 是数字信号,有明确的逻辑阈值,抗扰度好很多。第二,传感器出厂已经校准,不需要我在产线上做温度标定,省了一道工序。第三,I2C 总线可以挂多个器件,如果以后要在不同位置布多个测温点,只需要扩展地址就行,硬件改动极小。

PJ85718DM 的 I2C 地址通常由出厂设定,具体地址需要查对应批次的数据手册确认。实际使用时,我一般会在总线上加 4.7kΩ 的上拉电阻到 VCC,这是 I2C 的标准做法,阻值不能太小,否则功耗增加;也不能太大,否则上升沿变缓,高速通信时容易出错。

2.2 PIC18F87J10 的资源分配与网络能力

PIC18F87J10 是我个人比较喜欢的一颗片子。它有 128KB 闪存、3936 字节 RAM,对于温度采集加网络上报这种任务来说绰绰有余。最关键的是它内置了以太网模块,支持 RMII 接口连接外部 PHY,或者在某些封装里直接集成 PHY。实际项目中我用的是外接 PHY 的方案,因为布线更灵活。

引脚分配上,我把 I2C 的 SDA 和 SCL 分配到 RC3 和 RC4,这两个引脚支持 I2C 功能,复用起来方便。以太网部分占用 RB 组的部分引脚用于 RMII 数据线,具体分配要看硬件原理图。剩下的 GPIO 用来驱动本地显示(比如一个 1602 液晶)和几个状态指示灯。

这里有个细节值得注意:PIC18F87J10 的以太网模块和 I2C 模块在中断优先级上需要合理配置。温度采样是周期性的,实时性要求不算极高,但网络通信的中断可能比较频繁。我的做法是把 I2C 采样放在主循环里轮询,以太网收发用中断处理,这样不会因为网络突发流量导致温度采样被长时间阻塞。

2.3 本地与远程的硬件拓扑

整个系统的硬件拓扑可以这样理解:PJ85718DM 贴在需要测温的位置,通过两根线(SDA、SCL)连到 PIC18F87J10。单片机一边周期性地读温度,一边把数据打包成 UDP 或 TCP 报文,通过以太网口发到远程服务器。本地如果有一块小液晶,就直接显示当前温度;远程服务器收到数据后存数据库,做趋势分析。

这里“本地”和“远程”的区分很重要。本地温度是板载传感器直接测到的,反映的是设备附近的真实温度。远程温度则有两种含义:一种是把本地温度传到远端显示,另一种是远端也有传感器,数据回传到本地。我做的项目里两种都有,但核心链路是一样的——传感器采集、单片机处理、网络传输。

注意:I2C 总线的走线长度一般不要超过 1 米,如果传感器离单片机较远,建议改用差分总线或者把传感器放在离单片机近的位置,通过延长网络线来实现远程监测。

3. 软件架构与核心逻辑拆解

3.1 整体软件分层设计

软件部分我分成三层:驱动层、业务逻辑层、通信层。驱动层负责 I2C 读写和以太网控制器的寄存器操作;业务逻辑层处理温度数据的采集、滤波、阈值判断;通信层负责把数据封装成网络报文并发送。

这样分层的好处是,如果以后要换传感器,只需要改驱动层;如果要换通信协议,只动通信层。业务逻辑层基本不用动。我在实际项目里吃过“一锅粥”代码的亏,后来强制自己按这个结构写,维护成本低了很多。

驱动层的 I2C 部分,我用的是软件模拟 I2C,而不是硬件 I2C 模块。原因是我这颗 PIC18F87J10 的硬件 I2C 在低速率下偶尔会出现总线锁死,软件模拟虽然占一点 CPU,但时序完全可控,出问题也容易排查。软件模拟的延时用 NOP 指令微调,确保 SCL 频率在 100kHz 左右。

3.2 温度采集的时序与滤波策略

PJ85718DM 的转换时间通常在几十毫秒量级,具体要看分辨率和配置。我一般设置成 12 位分辨率,转换时间大约 50ms 到 100ms。采样周期我定在 500ms,也就是每 500ms 读一次温度。这个周期对于 HVAC 应用足够了,因为温度变化本身很慢,采样太快没有意义,反而增加功耗和总线负载。

滤波方面,我用的是滑动平均加中值滤波的组合。具体做法是:连续采 5 个点,去掉最大值和最小值,剩下 3 个取平均。这样既能抑制突发噪声,又不会引入太大滞后。实测下来,在继电器频繁动作的环境里,原始数据偶尔会跳变 1°C 到 2°C,经过滤波后波动控制在 0.2°C 以内。

提示:如果传感器和单片机共用同一个电源,电源纹波会影响温度读数。建议在传感器 VCC 引脚旁边加一个 0.1uF 的去耦电容,位置尽量靠近引脚。

3.3 远程通信协议的选择与实现

远程通信我选的是 UDP,而不是 TCP。原因很简单:温度数据是周期性的,偶尔丢一两个包不影响整体趋势分析,UDP 的开销小、延迟低。如果用 TCP,每次重传都会增加延迟,而且连接维护在嵌入式端也比较麻烦。

UDP 报文的格式我定义得很简单:前两个字节是帧头 0xAA55,接着一个字节是设备 ID,然后两个字节是温度值(放大 10 倍,用有符号整数表示,比如 253 表示 25.3°C),最后两个字节是校验和。整个报文 9 个字节,非常紧凑。

发送频率和采样频率一致,也是 500ms 一次。远程服务器收到后解析并存储。这里有个细节:如果网络暂时断开,单片机不会阻塞,继续采样和本地显示,只是发送失败而已。等网络恢复后,新数据继续发,旧数据不补发。这是有意设计的,因为温度监测更关注实时性,历史数据补发意义不大。

4. 实操过程:从寄存器配置到数据上报

4.1 I2C 驱动编写与传感器初始化

先讲 I2C 驱动的关键代码。软件模拟 I2C 需要实现起始条件、停止条件、字节发送、字节接收、应答检测这几个基本函数。我用的是位操作,直接操作 PIC 的 LAT 和 PORT 寄存器。

// I2C 起始条件 void I2C_Start(void) { SDA = 1; SCL = 1; __delay_us(5); SDA = 0; __delay_us(5); SCL = 0; __delay_us(5); } // 发送一个字节,返回应答位 unsigned char I2C_WriteByte(unsigned char data) { unsigned char i, ack; for(i = 0; i < 8; i++) { SDA = (data & 0x80) ? 1 : 0; data <<= 1; __delay_us(5); SCL = 1; __delay_us(5); SCL = 0; __delay_us(5); } SDA = 1; __delay_us(5); // 释放 SDA 等待应答 SCL = 1; __delay_us(5); ack = SDA; // 读应答位 SCL = 0; __delay_us(5); return ack; }

传感器初始化主要是配置它的配置寄存器,设置分辨率、工作模式等。PJ85718DM 的配置寄存器地址和位定义需要查数据手册,不同批次可能略有差异。我一般会先读一次器件 ID 确认通信正常,再写配置。

// 初始化温度传感器 void TempSensor_Init(void) { I2C_Start(); I2C_WriteByte(0x90); // 写地址 I2C_WriteByte(0x01); // 配置寄存器 I2C_WriteByte(0x60); // 12 位分辨率,连续转换模式 I2C_Stop(); __delay_ms(100); // 等待第一次转换完成 }

4.2 温度读取与数据转换

读温度的过程是:先发写地址指向温度寄存器,再发读地址,然后连续读两个字节。高字节在前,低字节在后。原始数据是 12 位有符号数,需要转换成实际温度值。

float TempSensor_Read(void) { unsigned char msb, lsb; int raw; float temp; I2C_Start(); I2C_WriteByte(0x90); // 写地址 I2C_WriteByte(0x00); // 温度寄存器 I2C_Start(); // 重复起始 I2C_WriteByte(0x91); // 读地址 msb = I2C_ReadByte(1); // 读高字节,发应答 lsb = I2C_ReadByte(0); // 读低字节,发非应答 I2C_Stop(); raw = (msb << 8) | lsb; raw >>= 4; // 12 位数据在高端 if(raw & 0x800) { // 负温度判断 raw |= 0xF000; } temp = raw * 0.0625; // 分辨率 0.0625°C return temp; }

这里的分辨率 0.0625°C 是 12 位模式下的 LSB 对应值,计算方式是 1/16。如果是 9 位模式,LSB 就是 0.5°C。实际项目中,0.0625°C 的分辨率已经远超 HVAC 的需求,但高分辨率对滤波有帮助,所以我还是用 12 位。

4.3 以太网初始化和 UDP 发送

PIC18F87J10 的以太网初始化比较繁琐,需要配置 MAC 寄存器、PHY 寄存器、缓冲区等。Microchip 提供了 TCP/IP 协议栈,但我这个项目比较简单,直接裸写寄存器更可控。

关键步骤包括:设置 MAC 地址、配置 PHY 为 100Mbps 全双工、初始化接收和发送缓冲区、使能收发。PHY 的配置通过 SMI 接口读写寄存器,需要等待协商完成。

// 简化的 UDP 发送函数 void UDP_Send(float temp) { unsigned char buf[9]; int temp_int = (int)(temp * 10); buf[0] = 0xAA; buf[1] = 0x55; buf[2] = DEVICE_ID; buf[3] = (temp_int >> 8) & 0xFF; buf[4] = temp_int & 0xFF; // 校验和计算 buf[5] = buf[0] ^ buf[1] ^ buf[2] ^ buf[3] ^ buf[4]; // 实际发送需要填充以太网帧头、IP 头、UDP 头 // 这里省略底层细节,重点是把数据交给 MAC 发送 MAC_SendFrame(buf, 9); }

实际发送时,需要在数据前面加上 UDP 头、IP 头和以太网帧头。目的 IP 和端口在初始化时设定好。如果服务器在局域网内,可以直接用 MAC 地址发送,不需要 ARP;如果跨网段,就需要先 ARP 解析。

注意:以太网发送缓冲区是有限的,如果发送频率太高,缓冲区会满。我的做法是在发送前检查缓冲区状态,如果满就丢弃当前帧,不阻塞主循环。

4.4 本地显示与阈值报警

本地显示我用的是一个简单的 1602 液晶,通过 4 位数据线驱动。每 500ms 更新一次温度值。如果温度超过设定的上限或下限,液晶上会显示报警标志,同时点亮一个红色 LED。

阈值判断的逻辑放在业务逻辑层,用滞回比较,避免温度在阈值附近波动时报警频繁切换。比如上限设 30°C,滞回 1°C,那么温度超过 30°C 触发报警,降到 29°C 以下才解除报警。

// 阈值判断与报警 void CheckAlarm(float temp) { static unsigned char alarm_state = 0; if(!alarm_state && temp > TEMP_HIGH_LIMIT) { alarm_state = 1; ALARM_LED = 1; } else if(alarm_state && temp < (TEMP_HIGH_LIMIT - TEMP_HYSTERESIS)) { alarm_state = 0; ALARM_LED = 0; } }

5. 常见问题与排查技巧实录

5.1 I2C 通信失败排查

I2C 通信失败是最常见的问题,表现是读不到数据或者读到的全是 0xFF。排查顺序我一般是这样的:

现象可能原因排查方法
完全无应答传感器未供电或地址错误用万用表测 VCC,确认地址
偶尔应答上拉电阻不合适示波器看波形上升沿
数据错误时序太快或干扰降低 SCL 频率,加屏蔽
总线锁死从机拉低 SDA 不放发送 9 个时钟脉冲复位

我遇到过最坑的一次是上拉电阻用了 10kΩ,在 100kHz 下波形上升沿太缓,导致数据偶尔出错。换成 4.7kΩ 后问题消失。所以上拉电阻不是随便选的,要根据总线电容和速率算。

5.2 温度读数跳变与滤波参数调整

温度读数跳变通常有两个来源:电源噪声和总线干扰。电源噪声可以通过加去耦电容解决,总线干扰则需要检查走线是否远离高频信号线。

滤波参数方面,滑动窗口的大小需要权衡。窗口太大,响应慢;窗口太小,滤波效果差。我一般先用 5 点窗口试,如果还是跳,加到 7 点或 9 点。但要注意,窗口越大,滞后越明显,对于需要快速响应的报警场景,窗口不能太大。

提示:如果温度跳变呈现周期性,比如每隔几秒跳一次,很可能是附近有周期性干扰源,比如变频器或开关电源。这种情况下,除了滤波,还要考虑物理隔离或屏蔽。

5.3 以太网通信不稳定的处理

以太网通信不稳定表现为丢包率高或者连接时断时续。常见原因包括:PHY 协商失败、网线质量差、电磁干扰、缓冲区溢出。

我的排查步骤是:先看 PHY 的状态寄存器,确认连接速率和双工模式是否正常;然后用 ping 测试基础连通性;如果 ping 正常但 UDP 丢包,检查发送缓冲区是否溢出;如果缓冲区经常满,降低发送频率或者增大缓冲区。

还有一个容易被忽略的点:PIC18F87J10 的以太网模块对时钟精度有要求,如果外部晶振偏差太大,会导致通信失败。我用的是 25MHz 晶振,负载电容按手册推荐值选,实测频偏在 20ppm 以内,通信很稳定。

5.4 常见问题速查表

问题排查方向解决措施
传感器无响应供电、地址、上拉测电压、查手册、换电阻
温度值固定不变转换模式配置检查配置寄存器
温度值偏差大传感器自热、位置远离热源,增加采样间隔
网络丢包严重缓冲区、网线、干扰降频、换线、加屏蔽
单片机死机看门狗、堆栈溢出使能看门狗,优化代码
本地显示闪烁刷新频率太高降低刷新率,加缓冲

6. 实操心得与扩展思路

这套方案我在两个项目里实际用过,累计运行超过一年,稳定性没问题。有几个心得值得分享。

第一,采样周期不要设太短。温度本身变化慢,500ms 足够了。设成 100ms 除了增加功耗和总线负载,没有任何好处。我试过 100ms 采样,结果 I2C 总线偶尔出错,改回 500ms 后一次问题都没出过。

第二,远程通信一定要做超时处理。UDP 发送虽然不阻塞,但如果 PHY 异常,发送函数可能卡住。我在发送函数里加了超时计数,超过一定时间就强制返回,避免整个系统卡死。

第三,本地和远程的数据要分开处理。本地显示用滤波后的数据,远程上报可以用原始数据或者滤波后的数据,看需求。我一般上报滤波后的数据,因为远程端通常不做二次滤波。

扩展方面,这套架构很容易加多个传感器。I2C 总线可以挂多个 PJ85718DM,只要地址不冲突。单片机轮流读取,然后打包成一个报文发出去。如果要监测湿度,也可以挂一个湿度传感器,报文格式稍微改一下就行。

另外,如果远程服务器需要更复杂的数据分析,可以在服务器端做,单片机只负责采集和上报。这样单片机的固件保持简单,维护成本低。我个人的原则是:能在服务器端做的,不要放在单片机端,因为服务器端的算力和存储资源丰富得多,改起来也方便。

最后分享一个小技巧:调试阶段,我会在单片机里加一个串口输出,把原始温度数据、滤波后数据、发送状态都打印出来。这样一旦出问题,看串口日志就能快速定位。等系统稳定后,再把串口输出关掉,节省资源。这个习惯帮我省了很多排查时间。

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

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

立即咨询