1. 项目概述与方案选型思考
1.1 这个项目解决什么问题
先交代下背景。前阵子接了个活儿,要给某厂区的 HVAC 机房做一套温度监测改造。现场情况挺典型:机房里有几台空调机组,需要同时盯着进风温度和出风温度,本地控制柜上要能看到实时数据,运维办公室在隔壁楼,也得能远程看到曲线和报警。如果全部人工拿测温枪去测,一天跑好几趟,既不及时也不现实。
市面上现成的温控器、数据采集器不少,但要么只支持本地显示,要么远程方案要绑定厂商云平台,后期还有年费。现场改造嘛,讲究一个灵活可控。所以最后我决定自己基于 PJ85718DM 加 STM32F215RE 做一套双通道温度监测系统:本地用屏幕实时显示,远程通过以太网口把数据推到内网监控页面。这套方案做下来,效果稳定,成本可控,而且后续想加通道、改报警逻辑都很方便。
这套系统解决的核心痛点有三个:
- 双通道独立监测:进风和出风温度分两路采集,互不干扰,数据独立上报。
- 本地与远程同步可见:本地屏幕显示实时值,同时通过网络把数据送到远程终端,两边数据一致。
- 可裁剪、可扩展:底层基于嵌入式方案,想加湿度传感器、加继电器控制、改报警阈值,都是在原有框架上做加法,不用推翻重来。
如果你也在做类似的嵌入式温度采集、HVAC 环境监测,或者只是想学习数字温度传感器怎么接入 MCU、怎么走以太网远程上报数据,这篇文章的内容应该能给你不少可以直接抄的作业。
1.2 为什么选 PJ85718DM 做温度采集
先说传感器侧。PJ85718DM 是一颗数字温度传感器,我手上这颗是典型的 I2C 接口,内部集成 12 位 ADC,温度分辨率能到 0.0625°C,测量范围覆盖 -55°C 到 +125°C,精度在 -10°C 到 +85°C 范围内可以做到 ±0.5°C 以内。这几个参数放在 HVAC 场景里非常够用:机房环境温度基本在 10°C 到 40°C 之间,不会超出传感器的舒适区,精度余量很充足。
选数字温度传感器而不是 NTC 热敏电阻加 ADC 采集,主要图三点省心:
- 免校准:NTC 方案每路都要做分压电阻匹配和软件查表校准,批量做的时候工作量很大。PJ85718DM 出厂前已经做过校准,读出来的数据直接就是摄氏度,省掉了最麻烦的标定环节。
- 接口干净:I2C 总线两根线搞定,多路传感器可以挂同一条总线上,按地址区分。对 PCB 布局和接线来说都是好事。
- 抗干扰能力强:数字信号传输,不像模拟信号那样容易受电源纹波和走线串扰影响。机房里有变频空调、大功率风机,电磁环境其实不算干净,模拟信号方案在这个场景下是要吃亏的。
PJ85718DM 的工作电压 2.7V 到 5.5V,可以直接用 3.3V 供电,和 STM32 的系统电压统一,不用额外做电平转换。静态电流在微安级别,就算以后要改成电池供电的低功耗版本,这颗传感器的底子也扛得住。
1.3 为什么选 STM32F215RE 做主控
主控侧我选了 STM32F215RE。这颗芯片是 ARM Cortex-M3 内核,主频最高 120MHz,Flash 512KB,SRAM 128KB。放在这个项目里,性能有不少富余,但富余不是坏事——后续想加图形界面、加 TCP 服务、加 JSON 解析库,都有空间。
选它的理由主要有几个:
- 硬件以太网 MAC:F2 系列内置了 10/100M 以太网 MAC 控制器,只需要外挂一颗 PHY 芯片就能联网。相比用 SPI 接口的独立以太网控制器芯片,电路更简洁,软件栈也更顺畅。
- 多路 I2C 和 UART:I2C 挂温度传感器,UART 可以接调试串口、蓝牙模块、4G 模块,接口资源充足。
- 生态成熟:HAL 库和标准外设库都很完善,遇到问题网上资料一抓一大把,开发周期能压得很短。
如果你手头没有同款芯片,其实这个方案移植性也很好:换成 STM32F4 系列或者 GD32 系列,管脚兼容的话基本可以直接搬;就算换了别的 MCU,只要带 I2C 和以太网 MAC,整个架构思路都是一样的。
2. 硬件设计与接口细节
2.1 传感器与 MCU 的接线方式
整个硬件链路其实很简单:PJ85718DM 通过 I2C 总线接到 STM32F215RE,以太网 PHY 芯片通过 RMII 接口接到 MCU,本地显示用一块 I2C 接口的 OLED 屏幕。全部设备挂在同一条 I2C 总线上,地址各不相同,互不冲突。
PJ85718DM 的引脚不多,典型接法如下:
| 引脚功能 | 接线说明 |
|---|---|
| VDD | 接 3.3V,并加 100nF 去耦电容 |
| GND | 接系统地 |
| SDA | 接 STM32 的 PB7(I2C1_SCL 对应的数据线需按实际映射确认) |
| SCL | 接 STM32 的 PB6(I2C1_SCL) |
| A0/A1 | 地址选择引脚,接地或接 VDD 组合出不同地址 |
这里有个容易踩的坑:PJ85718DM 的 I2C 从机地址由硬件引脚决定,7 位地址的高 4 位是固定的,低 3 位看 A0/A1 的电平组合。如果不小心把两片传感器设成了同一个地址,I2C 总线上就会冲突,读回来的数据乱七八糟。所以设计时一定要先规划好每路传感器的地址分配。
我在这套系统里用了两片 PJ85718DM:一片测进风温度,一片测出风温度。接线时把第一片的 A0/A1 都拉低,第二片 A0 拉高、A1 拉低,地址就错开了。OLED 屏幕的 I2C 地址通常是 0x3C,和传感器不在一个范围,不会冲突。
STM32F215RE 的 I2C1 外设挂这三路设备完全够用。I2C 速率我配置成 400kHz 快速模式,实测读写传感器和屏幕都非常稳定。理论上传感器能支持到 1MHz,但 400kHz 在抗干扰方面更有余量,长线传输的时候下降沿和上升沿也不容易变形。
2.2 PCB 布局与抗干扰设计要点
HVAC 现场最容易忽略的就是传感器线路的抗干扰。我把传感器小板和主控板分开设计,传感器通过排线引出,贴在风管表面或者挂到进风口附近,这样传感器能真正测到风道里的温度,而不是主板附近的温度。
几个布局上的经验:
- 传感器尽量远离发热器件:MCU、PHY 芯片、电源芯片都是发热源。如果把传感器贴在这些器件旁边,读出来的温度会偏高好几度,而且这个误差是动态的,负载一变温度就漂,很难校准。
- I2C 走线不要太长:400kHz 下,I2C 信号线超过 30cm 就有明显振铃,波形会变得很难看。实际项目里传感器到主板的排线控制在 20cm 以内,走线内部加了一根 GND 线做屏蔽,实测非常稳定。
- 电源去耦:每颗 IC 的电源引脚旁边都要放 100nF 陶瓷电容,电容尽量靠近引脚。这个习惯在低频电路里看不出差别,但以太网 PHY 工作的时候会产生高频噪声,如果去耦做不好,I2C 读取偶发出错就是常态。
PCB 设计的时候,我把 PJ85718DM 的 A0/A1 地址选择引脚用 0Ω 电阻控制,方便后期调整地址。量产阶段如果确认地址不再改动,可以把 0Ω 电阻换成直接焊接的跳线,省成本也省贴片位。
3. 软件架构与驱动实现
3.1 I2C 驱动与传感器寄存器操作
软件部分,我用的开发环境是 STM32CubeIDE,基于 HAL 库开发。初始化 I2C 的代码很简单,但有几个细节需要注意。
I2C 初始化时,我把时钟频率设成 400kHz,上升时间设置为快速模式对应的参数。如果使用默认的 100kHz 标准模式,读传感器问题也不大,但刷新率会慢一些,OLED 屏幕刷新也会有轻微卡顿。
I2C_HandleTypeDef hi2c1; void I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 400000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } }传感器读取温度的操作方式是:先写入寄存器指针指向温度寄存器,然后读取两个字节的数据。第一位是温度整数部分,第二位是小数部分。需要注意,PJ85718DM 的温度寄存器和很多类似的数字温度传感器一样,数据是以 16 位补码形式存放的,转换的时候还要考虑符号位。
3.2 读取与温度值转换的完整流程
温度数据的读取和转换成摄氏度,是整个固件里比较核心的部分。我写了一个独立函数来干这件事,逻辑清晰,方便复用。
float PJ85718DM_ReadTemperature(uint8_t devAddr) { uint8_t reg = 0x00; // 温度寄存器地址 uint8_t buf[2]; int16_t raw; float temp; HAL_I2C_Master_Transmit(&hi2c1, devAddr << 1, ®, 1, 100); HAL_I2C_Master_Receive(&hi2c1, devAddr << 1, buf, 2, 100); raw = (buf[0] << 8) | buf[1]; if (raw & 0x8000) { raw = (int16_t)(raw | 0xFFFF0000); // 符号扩展 } temp = raw * 0.0625f; // 12位精度下的LSB值 return temp; }代码里的0.0625f就是 12 位 ADC 的分辨率。传感器内部把温度转换成数字量,每个 LSB 对应 0.0625°C。这个值其实就是 1/16,所以也可以用位移来做整数运算,但用 float 做乘法的性能开销在这个场景下完全可以忽略。
实测下来,传感器的转换时间典型值在 100ms 左右,太频繁读取没有意义,反而会增加 I2C 总线的负担。我的设计是每 500ms 轮询一次两路传感器,这个刷新率对 HVAC 应用来说绰绰有余——风管温度本来就是个慢变量,一秒更新两次足够直观了。
3.3 多重采样与软件滤波处理
数字温度传感器虽然比模拟方案稳定,但在工业现场还是会受到干扰。尤其是压缩机启停的瞬间,电源会有短暂的跌落,可能让 I2C 读取出现偶发错误,读出来的数据突然跳变到离谱的值。
我加了两个层面的保护:
第一层:滑动平均滤波。每次读取的结果存入环形缓冲区,取最近 8 次采样的平均值作为显示值。这样即使某次读数有微小波动,最终输出也不会跟着抖。
第二层:异常值剔除。如果某次读取的温度和上一次有效值的差超过 5°C,直接丢弃这次数据,不进入滤波缓冲区。5°C 这个阈值是参考 HVAC 场景的温变速率定的——风管温度不可能在 500ms 内跳动 5°C,超过这个值基本可以确定是干扰或者通信错误。
#define FILTER_SIZE 8 float filterBuf[FILTER_SIZE]; uint8_t filterIdx = 0; uint8_t filterCnt = 0; float Temperature_Filter(float newVal) { static float lastValid = 25.0f; float sum = 0.0f; if (fabsf(newVal - lastValid) > 5.0f) { return lastValid; // 丢弃异常值 } lastValid = newVal; filterBuf[filterIdx] = newVal; filterIdx = (filterIdx + 1) % FILTER_SIZE; if (filterCnt < FILTER_SIZE) { filterCnt++; } for (uint8_t i = 0; i < filterCnt; i++) { sum += filterBuf[i]; } return sum / filterCnt; }这套滤波逻辑实测下来,显示界面上的温度值非常平稳,不再有小数点后两位来回跳的情况。但要注意:滤波只适用于温度这种慢变量,如果是采集振动、冲击这类快变量,滑动平均会抹掉关键的瞬态特征,那时候就得换别的算法了。
3.4 主循环状态机设计
整个固件的结构我设计成了简单的主循环状态机,没有用实时操作系统。HVAC 应用里的任务基本都是周期性的,用 RTOS 反而增加复杂度。
主循环的流程如下:
- 状态 0:系统初始化。初始化时钟、I2C、以太网、OLED,然后建立 TCP 服务器。
- 状态 1:读取传感器数据。轮询两路 PJ85718DM,执行滤波和异常剔除,更新全局温度变量。
- 状态 2:刷新本地显示。把温度值格式化到 OLED 屏幕上,同时更新状态图标。
- 状态 3:网络数据处理。检查 TCP 连接状态,收到上位机请求时回传温度数据,同时处理断线重连逻辑。
- 循环回到状态 1。
状态轮询的时间基准用HAL_GetTick()控制,每个状态根据自己的节奏决定是否执行。比如传感器每 500ms 读一次,OLED 每 1 秒刷新一次,网络数据每 200ms 检查一次。这样各个功能块的节奏独立,互不拖累。
4. 本地监测与远程监测的实现细节
4.1 本地显示:OLED 实时数据面板
本地监测我用的是一块 0.96 寸 I2C 接口的 OLED 屏幕,分辨率 128x64。显示内容分两行:第一行显示进风温度,第二行显示出风温度。字体用的是 16x16 的 ASCII 字库,屏幕上还能显示传感器状态——通信正常显示 OK,异常显示 ERR。
OLED 的驱动基于 SSD1306 控制器,初始化时序和显存操作比较标准。有现成的驱动库可以直接移植,但要注意一个坑:SSD1306 的 I2C 地址是 0x3C,很多库代码里写死的是 0x3D,如果直接抄过来会一直调不通。我遇到这个问题后排查了半天,最后用 I2C 扫描工具确认地址才定位到。
OLED 显示的温度值保留一位小数,比如25.3 C。裸奔的实时数据看着不够直观,我还加了一条简易的柱状图——根据温度值映射像素高度,一眼就能看出温度是偏高还是偏低。
4.2 远程监测:以太网上报方案
远程部分用的是 STM32F215RE 内置的以太网 MAC 加外挂 PHY 芯片,通过 TCP 协议把温度数据推送到上位机。上位机是一台 PC 上跑的简单监控界面,监听固定端口,收到数据后写入数据库并绘制曲线。
以太网初始化这块,我最开始用的是 ST 官方的 LwIP 移植包。LwIP 是开源的轻量级 TCP/IP 协议栈,MCU 上跑完全没压力。配置好 IP、掩码、网关后,MCU 就作为一个 TCP Server 监听 8080 端口。
数据传输格式直接用了简单的 JSON 字符串:
{"dev":"hvac01","type":"temp","ch1":24.8,"ch2":28.1,"alarm":0}上位机收到之后按 JSON 解析,存库、绘图、判断报警。TCP 协议的好处是可靠,不丢包,而且内网环境下延迟很低,基本实时。如果现场没有以太网环境,也可以把 PHY 芯片换成 DM9000 或者直接用 UART 接 4G 模块,传输层改成 MQTT 或者 HTTP POST,整个架构的其余部分完全不用动。
4.3 掉线检测与断线补偿逻辑
远程监测最容易出问题的不是数据传输本身,而是设备掉线后怎么恢复。我遇到过现场工人拔了网线忘了插回去的情况,这时候设备端如果没有掉线检测机制,TCP 连接挂着不释放,等网络恢复后新的连接也建立不起来。
解决方案是在 TCP Server 端加超时检测:每次收到上位机的请求后记录时间戳,如果超过 60 秒没有收到任何请求,主动断开当前连接,回到监听状态。这样即使客户端异常退出或者网络断开,服务器也不会被无效连接占住。
if ((HAL_GetTick() - lastRecvTick) > 60000) { tcp_abort(&pcb); // 超时断开 pcb = NULL; tcp_listening = start_tcp_server(); }上位机端也做了对应的自动重连逻辑:监测软件每隔 5 秒尝试连接一次,连不上就一直重试,连上之后正常收数据。这套机制跑了大半年,期间经历过几次交换机重启、网线松动,恢复后系统都能自动回到正常监测状态,不需要人工干预。
4.4 传感器断线保护
坏了传感器比网络断了更麻烦——网络断了能看出来,传感器读不到数据了,如果程序不处理,显示界面会一直保留上一次的数值,造成一种“温度正常”的错觉。
我在读取函数里加了返回值校验:如果 I2C 通信返回错误码,或者连续读取 3 次都是同一个固定值(传感器数据不可能任何时间都完全一致),就判定传感器异常。异常状态下,本地 OLED 显示 ERR,远程数据包里带上"alarm":1字段,上位机弹报警提示。这样运维人员能第一时间知道去查传感器接线,而不是看着假数据误判。
5. 常见问题与排查技巧实录
5.1 I2C 通信偶发失败问题
测试阶段遇到过一个问题:系统运行几小时后,偶尔会出现一次传感器读取超时。排查过程挺曲折,最初怀疑是传感器本身不稳定,后来发现是 I2C 总线上挂了三颗设备(两颗传感器加一块 OLED),总线的上拉电阻阻值不够。
每种 I2C 设备的手册里都会标注推荐的上拉电阻范围,但很多人会忽略一个关键点:总线上挂的设备越多,等效上拉阻抗就越低。一两个设备时用 10kΩ 没问题,挂三个设备后就需要降到 4.7kΩ。我把上拉电阻换成 4.7kΩ 之后,再也没出现过超时。
这里有个更省事的排查技巧:如果 I2C 偶发异常,用示波器抓 SCL/SDA 波形,看高电平是否接近 VDD、上升沿是否陡峭。如果高电平只有 2V 左右或者上升沿很平缓,基本就是上拉不足。
5.2 温度读数漂移与校准手段
还有一次发现进风温度比实际高了两度,一开始以为是传感器坏了,后来排查发现是传感器贴装位置的问题——我为了固定方便,把传感器直接贴在了风管金属外壳外侧,而风管外壁的温度本身就比气流温度高一些。
解决方法是给传感器做了一个小支架,让芯片悬空在风管内壁附近,用导热硅胶固定接触面但不紧贴金属外壳。调整位置后再用一支校准过的水银温度计做对比测试,误差在 ±0.3°C 之内。
如果你在现场发现传感器读数固定偏大或者偏小,可以在软件里做偏移校准:把实测温差保存到 Flash 里,每次读取后叠加偏移量。这种做法适合批量生产时的一致性校准,单台设备的话还是建议从安装位置上解决问题。
5.3 远程数据中断与排查顺序
远程数据中断是最让人头疼的,因为问题可能出在任何环节。我的排查顺序一般是:
- 先看设备端:串口调试助手打印网络状态,看 TCP 连接是否建立、是否被异常断开。
- 再测网络链路:用 PC
ping设备 IP,能通说明链路正常,不通就查网线、交换机、PHY 芯片配置。 - 最后查上位机:确认监听端口有没有被防火墙拦截、服务是否在运行。
这套顺序能快速定位大多数问题。实际遇到比较多的是第三种情况——上位机电脑重启后防火墙拦截了端口监听,或者监控软件服务没有设置为开机自启,导致设备端一直重连不上。把防火墙入站规则放行对应端口,问题就解决了。
常见问题速查表:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 两端传感器读数都一样 | I2C 地址冲突 | 扫描总线地址,检查 A0/A1 电平 |
| 读数跳变幅度大 | 电源纹波干扰 | 检查去耦电容,加软件滤波 |
| OLED 不显示 | I2C 地址不匹配 | 确认 SSD1306 地址是 0x3C |
| TCP 连接建立后几十秒断开 | 超时检测误判 | 增大超时时间或调整心跳频率 |
| 远程偶尔丢一条数据 | 缓冲区溢出 | 增大 LwIP 内存池,检查上位机接收速度 |
6. 经验总结与后续扩展思路
这套系统从硬件设计到软件调试,前后大概用了两周时间。现在回想起来,方案整体是简洁可靠的,但也有些地方可以做得更好。
硬件上,当时为了赶工期,传感器小板和主控板是分开设计的,实际安装时排线在风管里走线还是比较纠结。如果重新做一版,我会把传感器做成可插拔的探头形式,线缆直接用屏蔽双绞线,接口用航空插头,拆装都方便,抗干扰能力也更上一个台阶。
软件上,目前温度数据是定时主动上报的,上位机收到后只做了展示和报警。后续可以加一个简单的存储功能,把每天的温升曲线记录下来,周末做个报表分析进风出风温差变化,对空调机组的运行效率也有参考价值。
另外,PJ85718DM 本身还支持高低温报警寄存器,可以设定超过阈值后自动拉高 ALERT 引脚。我之前因为主板设计时没有预留这个引脚的连接,这部分功能没有用上。如果你在设计初期就打算做报警联动,记得把 ALERT 引脚引到 MCU 的外部中断输入上,软件上会省不少事。
最后再分享一个体会:嵌入式项目里,“能用”和“好用”之间的差距,往往就在这些细节里。温度传感器谁都会读,但读得稳、传得可靠、断线能恢复、坏了能报警,这才是一套监测系统真正值钱的地方。按这个标准去做,哪怕用最基础的 MCU 和最普通的传感器,也能做出比很多商业产品还靠谱的东西。