1. 项目缘起与整体设计思路
嵌入式温度监测这个方向,看起来简单,实际上坑特别多。我最早接触这类需求是在一个 HVAC 控制器的改造项目里,当时客户的要求很朴素:本地要能看到实时温度,远程也要能拿到数据,精度要够,成本要低,还得稳定跑个三五年不出毛病。听起来不难对吧?但真正动手之后你会发现,从传感器选型到信号链设计,从本地显示到远程通信,每一个环节都有取舍。
这个项目用的是 PJ85718DM 搭配 STM32F405RG 的组合。先说说为什么选这两个核心器件。PJ85718DM 是一颗带 I2C 接口的数字温度传感器,封装小巧,典型精度在 ±0.5°C 以内,工作电压范围宽,非常适合嵌入式场景。STM32F405RG 则是 ST 家 F4 系列里性价比很高的一颗 MCU,Cortex-M4 内核带浮点单元,168MHz 主频,1MB Flash,192KB SRAM,外设资源丰富,I2C、UART、SPI、CAN 一应俱全。这两个搭在一起,一个负责精准采温,一个负责数据处理和通信调度,分工明确。
整体设计思路是这样的:PJ85718DM 通过 I2C 总线挂在 STM32F405RG 下面,MCU 周期性读取温度数据,经过滤波和校准之后,一路送到本地显示模块(比如 OLED 或段码屏),另一路通过远程通信接口(RS485 或有线以太网)上传到上位机或云端网关。整个系统需要解决的核心问题包括:I2C 通信的稳定性、温度数据的精度校准、本地显示的刷新策略、远程传输的协议设计,以及整机的低功耗和抗干扰能力。
为什么不用模拟传感器加 ADC 的方案?因为数字传感器省去了信号调理电路,出厂已经校准过,一致性好,而且 I2C 总线可以挂多个器件,扩展方便。对于 HVAC 这种需要多点测温的场景,数字方案在布线和维护上的优势非常明显。当然,数字传感器也不是没有缺点,比如 I2C 总线在长距离传输时容易受干扰,这就需要我们在 PCB 布局和线缆屏蔽上多花心思。
这个项目适合谁参考?我觉得三类人可以看看:一是刚接触嵌入式传感器开发的朋友,可以把它当作一个完整的练手项目;二是做 HVAC 或工业控制的工程师,里面关于抗干扰和远程通信的部分应该有帮助;三是想了解 I2C 通信实操细节的开发者,我会把踩过的坑都写出来。
2. 核心器件解析与选型考量
2.1 PJ85718DM 温度传感器的关键特性
PJ85718DM 这颗传感器,我实际用下来最大的感受就是“省心”。它是数字输出,不需要外部 ADC,直接通过 I2C 读寄存器就能拿到温度值。分辨率可以配置,最高能到 0.0625°C,虽然实际精度达不到这么细,但在 HVAC 场景里完全够用了。测温范围覆盖 -40°C 到 +125°C,覆盖了绝大多数室内外环境监测的需求。
它的 I2C 地址可以通过引脚配置,这意味着同一条总线上可以挂多颗传感器而不会冲突。这一点在多区域温度监测里特别有用,比如一个 HVAC 系统需要同时监测回风、送风、室外三个点的温度,挂三颗 PJ85718DM 就行,地址分别设成不同的值,MCU 轮流读取。
还有一个细节值得注意:这颗传感器内部有可编程的报警功能。你可以设定上限和下限阈值,当温度超出范围时,它的报警引脚会拉低,可以直接接到 MCU 的外部中断引脚上。这样就不需要 MCU 一直轮询,只在异常时响应,省 CPU 也省功耗。我在项目里就用了这个功能来做超温报警,响应速度比纯轮询快很多。
2.2 STM32F405RG 作为主控的优势
STM32F405RG 这颗 MCU 在嵌入式圈子里口碑很好,我用它做过好几个项目,稳定性没得说。168MHz 的主频对于温度监测这种任务来说绰绰有余,甚至有点大材小用。但为什么还是选它?主要是看中了它的外设资源和浮点运算能力。
温度数据虽然不需要复杂的数学运算,但如果你要做滑动平均滤波、多点校准曲线拟合,有硬件浮点单元会轻松很多。另外,F405RG 的 I2C 外设支持硬件 CRC 和 SMBus 模式,在干扰环境下比软件模拟 I2C 可靠得多。我试过用软件模拟 I2C 读这颗传感器,在电机干扰大的场合偶尔会出现数据错位,换成硬件 I2C 之后就没再出现过。
它的 UART 和 CAN 外设也很实用。远程通信如果走 RS485,直接用 UART 加一个收发器就行;如果走 CAN 总线,F405RG 自带 CAN 控制器,加个收发器就能用。这种灵活性让同一个硬件平台可以适配不同的现场通信需求,不用重新画板子。
2.3 本地与远程双通道的架构设计
本地和远程这两个通道,听起来只是“显示”和“上传”的区别,但在设计上要考虑的东西完全不一样。本地显示要求实时性高,刷新率至少 2Hz 以上,人眼看起来才不卡顿;远程传输则更看重可靠性和协议兼容性,刷新率可以低一些,但数据不能丢。
我的做法是在 MCU 内部维护一个温度数据结构,本地显示和远程上传都从这个结构里取数据。本地显示走的是 I2C 接口的 OLED 屏,刷新周期 200ms;远程上传走 RS485,每 1 秒发送一次数据帧。两个任务用不同的定时器触发,互不干扰。这样即使远程通信出现阻塞,本地显示也不会卡住。
注意:本地显示和远程通信如果共用一条 I2C 或 SPI 总线,一定要做好总线仲裁和超时处理,否则一个设备卡死会拖垮整个系统。
3. 硬件连接与信号链实操要点
3.1 I2C 总线布线规范与上拉电阻计算
I2C 总线的布线是这个项目里最容易出问题的地方。我见过太多人因为上拉电阻选错或者走线太长导致通信失败。先说上拉电阻的计算,标准模式下 I2C 总线速率 100kHz,快速模式 400kHz。PJ85718DM 支持快速模式,所以我一般跑 400kHz。
上拉电阻的取值取决于总线电容和上升时间要求。总线电容包括 PCB 走线电容、引脚电容和线缆电容,经验值大约是每厘米走线 1pF 左右。假设总线电容是 200pF,快速模式下上升时间要求小于 300ns,根据公式 R = t / (0.8473 × C),算下来 R 大约是 1.8kΩ。但实际取值还要考虑功耗,太小了静态电流大。我一般用 2.2kΩ 到 4.7kΩ 之间,实测 2.2kΩ 在大多数场景下都能稳定工作。
PCB 走线方面,SDA 和 SCL 要尽量平行走,长度不要超过 30cm。如果传感器和 MCU 不在同一块板上,中间用排线连接的话,一定要在传感器端加上拉电阻,不要只在 MCU 端加。因为排线的电容比较大,单端上拉会导致上升沿变缓,通信误码率升高。
3.2 电源滤波与去耦电容配置
温度传感器对电源噪声很敏感,尤其是 PJ85718DM 这种高精度器件。我在传感器 VCC 引脚旁边放了两个电容:一个 100nF 的陶瓷电容紧贴引脚,负责高频去耦;一个 10μF 的钽电容放在附近,负责低频滤波。这两个电容的接地端要尽量靠近传感器的 GND 引脚,回路面积越小越好。
MCU 这边的电源也要处理好。STM32F405RG 的每个 VDD 引脚都要配 100nF 去耦电容,VDDA 引脚额外加一个 1μF 的电容。如果系统里有继电器或电机这类大功率负载,建议给 MCU 和传感器单独用一路 LDO 供电,不要和负载共用电源。我之前有个项目就是因为传感器和继电器共用 5V 电源,继电器一吸合温度读数就跳变,后来加了独立 LDO 才解决。
3.3 远程通信接口的硬件防护
远程通信走 RS485 的话,硬件防护不能省。我在 A/B 差分线上各串了一个 10Ω 的电阻,然后并了一个 TVS 管做浪涌保护。收发器选的是带隔离的型号,虽然贵一点,但在工业现场能省很多麻烦。如果不带隔离,至少要在收发器和 MCU 之间加光耦,否则地环路问题会让你头疼。
线缆方面,一定要用双绞线,屏蔽层单端接地。我试过用普通排线走 RS485,距离一超过 10 米就频繁丢包,换成屏蔽双绞线之后 100 米都没问题。这个钱不能省。
4. 固件开发与核心代码实现
4.1 I2C 驱动初始化与传感器配置
STM32F405RG 的硬件 I2C 初始化,我用的是 HAL 库,配置起来比较快。时钟速率设成 400kHz,占空比 2:1。初始化的时候要注意,I2C 的 GPIO 要配置成开漏输出加上拉,速度等级设成 Very High。
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; HAL_I2C_Init(&hi2c1);传感器配置方面,PJ85718DM 的配置寄存器需要设置分辨率和报警阈值。我一般把分辨率设成 12 位,转换时间大约 150ms,对于 HVAC 场景足够了。报警阈值根据具体应用设定,比如室内温度报警上限设 35°C,下限设 5°C。
4.2 温度数据读取与滑动平均滤波
读取温度数据的流程是:启动转换、等待转换完成、读取温度寄存器、转换成实际温度值。PJ85718DM 的温度寄存器是 16 位的,高 12 位有效,低 4 位是标志位。转换公式是:温度 = 原始值 × 0.0625。
float read_temperature(void) { uint8_t buf[2]; HAL_I2C_Mem_Read(&hi2c1, PJ85718DM_ADDR, TEMP_REG, 1, buf, 2, 100); int16_t raw = (buf[0] << 8) | buf[1]; raw >>= 4; return raw * 0.0625f; }原始数据会有抖动,直接显示会跳来跳去。我用的是滑动平均滤波,窗口大小 8。具体做法是维护一个长度为 8 的环形缓冲区,每次新数据进来就替换最旧的数据,然后求平均。这样既能平滑数据,又不会引入太大延迟。
#define FILTER_WINDOW 8 float temp_buffer[FILTER_WINDOW]; uint8_t buf_index = 0; float filter_temperature(float new_temp) { temp_buffer[buf_index] = new_temp; buf_index = (buf_index + 1) % FILTER_WINDOW; float sum = 0; for (int i = 0; i < FILTER_WINDOW; i++) { sum += temp_buffer[i]; } return sum / FILTER_WINDOW; }实操心得:滑动平均的窗口不要设太大,否则温度突变时响应会滞后。8 到 16 之间比较合适,具体看你的采样率。
4.3 本地显示刷新与远程数据帧封装
本地显示我用的是 0.96 寸 OLED,I2C 接口,和传感器共用一条总线。这里要注意,OLED 的 I2C 地址和传感器不能冲突。PJ85718DM 的地址我设成 0x48,OLED 一般是 0x3C 或 0x3D,不会冲突。
显示刷新我放在定时器中断里,每 200ms 触发一次。中断里只置一个标志位,实际刷新在 main 循环里做,避免在中断里做耗时操作。
远程数据帧的格式我设计成固定长度,方便上位机解析。帧头 2 字节,温度值 4 字节浮点,校验 2 字节 CRC16,帧尾 1 字节。总共 9 字节。发送周期 1 秒,用 UART 的 DMA 发送,不占用 CPU。
typedef struct { uint16_t header; float temperature; uint16_t crc; uint8_t tail; } remote_frame_t;CRC 校验我用的是标准 CRC16-CCITT,多项式 0x1021。这个校验方式在工业协议里很常见,上位机解析起来也方便。
5. 常见问题排查与避坑经验
5.1 I2C 通信失败排查流程
I2C 通信失败是最常见的问题,我整理了一个排查流程,按顺序检查基本能定位到原因。
| 排查步骤 | 检查内容 | 常见问题 |
|---|---|---|
| 1 | 上拉电阻是否焊接 | 漏焊或阻值过大 |
| 2 | 电源电压是否正常 | 传感器未供电或电压偏低 |
| 3 | I2C 地址是否正确 | 地址配置引脚接错 |
| 4 | 总线是否有电容过大 | 走线过长或挂载设备过多 |
| 5 | 时序是否满足要求 | 时钟速率过高 |
我遇到最多的情况是上拉电阻漏焊,尤其是手工焊接的板子。还有就是传感器地址搞错,PJ85718DM 的地址引脚如果悬空,地址是不确定的,一定要明确接高或接低。
5.2 温度读数跳变的处理方法
温度读数跳变一般有三个原因:电源噪声、I2C 通信误码、传感器自发热。电源噪声前面说过了,加去耦电容和独立 LDO 能解决。I2C 误码可以通过开启 CRC 校验来检测,如果发现误码率高,就要检查布线和上拉电阻。
传感器自发热这个容易被忽略。PJ85718DM 的工作电流很小,一般不到 100μA,自发热可以忽略。但如果传感器旁边有发热元件,比如 LDO 或功率电阻,读数就会偏高。我的做法是在 PCB 布局时让传感器远离热源,至少保持 5mm 以上的距离。
5.3 远程通信丢包与干扰抑制
RS485 丢包在工业现场很常见,主要原因有:地环路、浪涌干扰、终端电阻不匹配。地环路问题用隔离收发器解决,浪涌用 TVS 管,终端电阻要在总线两端各接一个 120Ω 的电阻。
还有一个容易被忽略的点:RS485 的使能引脚控制。发送和接收切换时,要确保使能信号在数据发送完成之后再切换,否则会截断数据帧。我一般用 UART 的 TC(传输完成)中断来切换使能引脚,这样最可靠。
注意:如果现场干扰特别严重,可以考虑降低波特率。9600bps 比 115200bps 的抗干扰能力强很多,虽然慢一点,但稳定性优先。
6. 系统联调与性能验证
6.1 精度校准方法与实测数据
PJ85718DM 出厂已经校准过,但实际使用中还是会有偏差,主要来自 PCB 热传导和参考电压漂移。我的校准方法是用一个高精度温度计作为基准,把传感器和基准温度计放在同一个恒温环境里,等温度稳定后记录两者的差值,然后在固件里做偏移补偿。
实测数据方面,我在 10°C、25°C、40°C 三个点做了对比。未校准前最大偏差 0.8°C,校准后偏差控制在 0.2°C 以内。对于 HVAC 应用来说,这个精度完全够用了。
6.2 长时间运行稳定性测试
稳定性测试我跑了 72 小时连续运行,每 10 秒记录一次数据。测试期间温度读数没有出现跳变或丢包,远程通信的丢包率低于 0.01%。这个结果说明硬件设计和固件逻辑都是可靠的。
测试中唯一出现的问题是 OLED 屏在运行 48 小时后出现了轻微残影,这是 OLED 本身的特性,和电路无关。解决办法是每隔一段时间做一次全屏刷新,或者改用 LCD 屏。
6.3 功耗优化与低功耗模式尝试
如果项目对功耗有要求,STM32F405RG 支持多种低功耗模式。我的做法是在没有通信任务的时候让 MCU 进入 Sleep 模式,用定时器唤醒。传感器也支持单次转换模式,转换完成后自动进入休眠,需要读数时再唤醒。
实测下来,正常模式整机电流大约 45mA,优化后能降到 12mA 左右。如果进一步降低采样率,还能更低。不过对于 HVAC 这种有市电供电的场景,功耗不是首要考虑因素,稳定性才是。
7. 项目扩展与个人体会
这个项目做完之后,我又在此基础上做了几个变种。一个是增加了多点测温,一条 I2C 总线上挂了 4 颗 PJ85718DM,分别监测不同区域的温度。另一个是增加了数据记录功能,用 SD 卡把温度数据存下来,方便后期分析。
扩展的时候要注意,I2C 总线上挂的设备越多,总线电容越大,上拉电阻要相应减小。我挂 4 颗传感器的时候,上拉电阻从 4.7kΩ 换成了 2.2kΩ,通信才稳定。
我个人在实际操作中的体会是,嵌入式温度监测这个方向,硬件设计占七成,固件占三成。很多问题看起来是软件 bug,实际上是硬件没做好。所以我在调试的时候,会先用示波器看波形,确认硬件没问题之后再查代码。这个习惯帮我省了很多时间。
最后分享一个小技巧:如果你不确定 I2C 通信是否正常,可以先写一个最简单的扫描程序,遍历所有可能的地址,看哪些地址有应答。这个程序虽然简单,但在调试新板子的时候特别有用,能快速判断传感器是否正常工作。