☰
基于PJ85718DM与STM32F303RC的嵌入式温度监测系统设计与实现
2026/10/10 12:11:47 网站建设 项目流程

1. 项目缘起与整体设计思路

嵌入式温度监测这个方向,看起来简单,实际上坑特别多。我最早接触这类需求是在一个暖通空调控制器的改造项目上,当时的要求很明确:本地要能实时看到机房回风温度,远程中控室也要同步拿到数据,而且两路数据不能互相干扰。最开始想用单颗数字温度芯片搞定,结果发现本地显示和远程传输对采样率、通信接口、抗干扰能力的要求完全不一样,硬凑在一起反而把系统搞得很脆弱。

后来我把方案拆成了两条独立又协同的链路:本地温度采集用 PJ85718DM 这颗红外温度传感器,远程温度监测则依托 STM32F303RC 的片上外设和通信资源来搭建。这样分工的好处是,本地测量专注在“准”和“快”上,远程传输专注在“稳”和“远”上,各司其职,互不拖累。

PJ85718DM 是一颗数字输出的红外温度传感器,支持 I2C 接口,内部集成了热电堆传感单元和信号调理电路,出厂时做了温度校准,读数直接就是摄氏度,不需要外部再做冷端补偿。它的视场角比较窄,适合做点式非接触测量,比如风管表面温度、换热器翅片温度这类场景。而 STM32F303RC 是带 FPU 的 Cortex-M4 内核,主频 72MHz,片上资源对于温度监测这种任务来说绰绰有余,关键是它的定时器、ADC、USART、I2C 这些外设组合起来,刚好能把本地采集和远程上报串成一条完整的链路。

整个系统的设计思路可以概括为三层:感知层负责把温度物理量变成数字量,处理层负责数据滤波、单位换算、阈值判断,通信层负责把处理后的数据打包送到远端。三层之间通过明确的数据结构解耦,任何一层出问题都不会把整个系统拖死。这个思路在后面调试阶段帮了大忙,因为你可以单独测试每一层,快速定位故障点。

注意:红外温度传感器测的是目标表面的辐射温度,不是空气温度。如果你要测气温,得让传感器对着一个经过气流充分冲刷的黑色薄片,或者直接选接触式方案。这一点在 HVAC 场景里特别容易搞混。

2. 核心器件选型与关键参数解析

2.1 PJ85718DM 的测温原理与接口特性

PJ85718DM 的核心是一个热电堆传感器,它把目标物体发出的红外辐射转换成微弱的电压信号,内部再经过低噪声放大、模数转换和线性化处理,最终通过 I2C 输出数字温度值。它的测温范围覆盖 -20°C 到 +120°C,在这个区间内精度可以做到 ±0.5°C 左右,分辨率是 0.02°C。这个精度对于 HVAC 应用来说完全够用,因为暖通系统里温度控制的目标精度通常也就是 ±0.5°C 到 ±1°C。

I2C 接口的好处是接线简单,两根线就能挂多个器件,而且 STM32F303RC 的 I2C 外设支持标准模式 100kHz 和快速模式 400kHz,跟 PJ85718DM 的通信速率完全匹配。实际用的时候我建议跑在 100kHz,因为红外传感器的内部转换时间大概在 100ms 量级,通信速率再高也没意义,反而增加总线上的噪声耦合风险。

这颗传感器的视场角是 35 度左右,意味着在 10cm 距离上,测量光斑直径大约是 6cm。这个光斑大小决定了你安装时要注意什么:如果被测目标比光斑小,读数就会被背景辐射污染。我在一个风管温度监测项目里就吃过这个亏,传感器对着一个细铜管测,结果读数一直偏高,后来加了一个遮光筒把视场限制住才解决。

2.2 STM32F303RC 的资源分配与通信规划

STM32F303RC 有 256KB Flash 和 48KB RAM,对于温度监测这种任务来说,资源非常宽裕。我一般会把程序分成三个任务:I2C 采集任务、数据处理任务、通信上报任务。这三个任务可以用裸机的前后台架构跑,也可以用 FreeRTOS 做任务调度。如果系统里还有其他控制逻辑,建议上 RTOS,因为温度采集的实时性要求不高,但通信上报的时序要求比较严格,用 RTOS 可以更好地保证优先级。

外设分配上,I2C1 用来接 PJ85718DM,USART2 用来做远程通信,TIM2 用来做采集周期的定时触发。USART2 的波特率我通常设 115200,这个速率在工业现场的抗干扰能力和传输距离之间比较平衡。如果你需要更远的传输距离,可以在 USART 后面加一颗 RS485 收发器,用差分信号传输,几百米没问题。

ADC 在这个方案里不是必须的,但如果你还想监测本地空气温度,可以外接一个 NTC 热敏电阻分压后接到 ADC 通道上。STM32F303RC 的 ADC 是 12 位的,采样率最高 5Msps,测热敏电阻这种慢变量绰绰有余。我一般会用 ADC1 的通道 1 接分压电路,然后在软件里做查表或者用 Steinhart-Hart 公式换算成温度。

2.3 本地与远程测温的差异化设计

本地测温的核心诉求是“看得见、反应快”。PJ85718DM 的采样周期我设在 200ms,这个速度对于人眼观察来说已经足够流畅,同时也不会让 I2C 总线太忙。本地显示可以用 OLED 或者段码屏,通过 I2C 或者 SPI 接口接在 STM32 上。如果只是做调试用,直接通过 USART 打印到串口助手也行。

远程测温的核心诉求是“传得远、不丢包”。我在通信协议上做了一个简单的帧结构:帧头 0xAA 0x55,后面跟设备地址、温度值高字节、温度值低字节、校验和。校验和用累加和取反,计算量小,检错能力对于这种短帧来说够用。每帧数据 8 个字节,115200 波特率下传输一帧不到 1ms,即使每秒上报 10 次,总线占用率也很低。

本地和远程的数据流是并行处理的,但共享同一个温度值变量。我用了一个双缓冲机制:I2C 中断里把新采集的温度值写入缓冲区 A,主循环里从缓冲区 A 读取并更新到全局变量,同时把全局变量写入缓冲区 B 供通信任务使用。这样即使通信任务偶尔被阻塞,也不会影响采集任务的实时性。

3. 硬件连接与电路设计要点

3.1 PJ85718DM 的外围电路

PJ85718DM 的典型应用电路很简单,VCC 接 3.3V,GND 接地,SDA 和 SCL 分别接 STM32 的 I2C 引脚,另外 SDA 和 SCL 各需要一颗 4.7kΩ 的上拉电阻到 3.3V。这个上拉电阻的阻值不是随便选的:阻值太小,总线电容充电太快,上升沿过冲;阻值太大,上升沿变缓,高速通信时容易误码。4.7kΩ 是 100kHz 下的经典值,如果你跑 400kHz,可以降到 2.2kΩ。

电源去耦也很关键。我在 VCC 和 GND 之间并了一颗 100nF 的陶瓷电容和一颗 10μF 的钽电容,前者滤高频噪声,后者提供瞬态电流。红外传感器对电源纹波比较敏感,如果电源不干净,读数会跳。实测下来,加了这两颗电容之后,读数的峰峰值噪声从 0.3°C 降到了 0.05°C 以内。

注意:PJ85718DM 的 SDA 和 SCL 引脚是开漏输出,必须加上拉电阻才能正常工作。如果你直接接到 STM32 的 I2C 引脚上而不加上拉,总线会一直处于低电平,通信根本起不来。这个坑我见过不止一个新手踩过。

3.2 STM32F303RC 的最小系统与调试接口

STM32F303RC 的最小系统包括:3.3V 稳压电路、8MHz 晶振(用于 PLL 倍频到 72MHz)、复位电路、BOOT 选择电路。稳压芯片我一般用 AMS1117-3.3,输入 5V,输出 3.3V,最大电流 800mA,带 STM32 和几个传感器绰绰有余。晶振的负载电容根据晶振规格书选,通常是 20pF 左右,但实际值需要根据 PCB 走线电容微调。

调试接口用 SWD,只需要 SWDIO、SWCLK、GND 三根线就能下载和调试。我习惯在 PCB 上留一个 4 针的排针,除了这三根线再加一根 3.3V,方便给调试器供电。SWD 接口的好处是占用的引脚少,而且 STM32F303RC 的 SWD 引脚和 GPIO 是复用的,不调试的时候可以当普通 IO 用。

USART2 的引脚是 PA2(TX)和 PA3(RX),我一般会在这两个引脚上各串一颗 100Ω 的电阻,然后再接到外部连接器上。这颗电阻的作用是限流保护,万一外部接错线或者短路,不至于把 STM32 的引脚烧掉。虽然 STM32 的 IO 口有一定的过流保护,但加一颗电阻成本几乎为零,可靠性提升很明显。

3.3 电源与抗干扰设计

HVAC 现场的环境比较恶劣,电机启停、继电器动作都会在电源线上产生尖峰干扰。我在电源入口处加了一颗 TVS 管和一颗共模电感,TVS 管用来钳位浪涌电压,共模电感用来抑制共模噪声。这两个器件加起来不到两块钱,但能显著降低系统死机的概率。

PCB 布局上,模拟部分和数字部分要分开。PJ85718DM 虽然输出的是数字信号,但它的内部模拟前端对噪声很敏感,所以我在 PCB 上把传感器的地线和 STM32 的地线分开走,最后在电源入口处单点汇合。这个做法叫“单点接地”,可以避免数字地上的噪声电流流过模拟地,影响传感器读数。

通信线如果走的是 RS485,建议用双绞线,并且 A、B 线要尽量靠近走,减少差模干扰。如果传输距离超过 100 米,最好在末端加一个 120Ω 的终端电阻,匹配线缆特性阻抗,减少反射。这些细节在实验室里可能看不出差别,但到了现场就是稳定和不稳定的分界线。

4. 软件架构与核心代码实现

4.1 I2C 驱动与温度读取流程

STM32F303RC 的 I2C 外设用 HAL 库驱动比较方便,但 HAL 库的 I2C 函数在异常情况下容易卡死,所以我一般会加一个超时机制。具体做法是:在调用HAL_I2C_Master_Transmit和HAL_I2C_Master_Receive时传入超时参数,比如 100ms,如果超时了就重新初始化 I2C 外设。这个做法虽然粗暴,但在现场环境下非常有效,因为 I2C 总线被拉死的情况并不罕见。

读取 PJ85718DM 的流程是:先发送从机地址和寄存器地址,然后重启 I2C 总线,发送读命令,读取两个字节的数据。第一个字节是温度值的高字节,第二个字节是低字节,组合起来是一个 16 位有符号整数,单位是 0.02°C。换算成摄氏度就是:temperature = raw_value * 0.02。

#define PJ85718DM_ADDR 0x5A << 1 #define PJ85718DM_TEMP_REG 0x07 float read_pj85718dm_temperature(I2C_HandleTypeDef *hi2c) { uint8_t reg = PJ85718DM_TEMP_REG; uint8_t data[2]; int16_t raw; if (HAL_I2C_Master_Transmit(hi2c, PJ85718DM_ADDR, &reg, 1, 100) != HAL_OK) { HAL_I2C_DeInit(hi2c); HAL_I2C_Init(hi2c); return -273.15f; } if (HAL_I2C_Master_Receive(hi2c, PJ85718DM_ADDR, data, 2, 100) != HAL_OK) { HAL_I2C_DeInit(hi2c); HAL_I2C_Init(hi2c); return -273.15f; } raw = (int16_t)((data[0] << 8) | data[1]); return raw * 0.02f; }

这段代码里,返回 -273.15°C 表示读取失败,因为绝对零度在物理上不可能达到,所以这个值可以安全地作为错误标志。实际用的时候,我会在调用这个函数之后判断一下,如果返回值小于 -100°C,就认为读取失败,使用上一次的有效值。

4.2 定时采集与数据滤波

采集周期用 TIM2 来定时,配置成 200ms 中断一次。在中断服务函数里置一个标志位,主循环检测到这个标志位就去读传感器。这样做的好处是采集周期由硬件定时器保证,不受主循环其他任务的影响。

读回来的原始数据不能直接用,因为红外传感器容易受到环境温度波动和电磁干扰的影响,读数会有毛刺。我一般用滑动平均滤波,窗口大小取 8。具体做法是维护一个长度为 8 的环形缓冲区,每次新数据进来就替换最老的数据,然后求平均值。这个滤波算法计算量小,对周期性噪声的抑制效果很好。

#define FILTER_WINDOW 8 float temperature_buffer[FILTER_WINDOW]; uint8_t buffer_index = 0; float filter_temperature(float new_value) { float sum = 0; temperature_buffer[buffer_index] = new_value; buffer_index = (buffer_index + 1) % FILTER_WINDOW; for (int i = 0; i < FILTER_WINDOW; i++) { sum += temperature_buffer[i]; } return sum / FILTER_WINDOW; }

滑动平均的窗口大小需要根据实际噪声情况调整。窗口越大,滤波效果越好,但响应速度越慢。200ms 采集周期下,8 点滑动平均的响应时间大约是 1.6 秒,对于 HVAC 这种慢过程来说完全够用。如果你需要更快的响应,可以把窗口降到 4,但噪声会稍微大一点。

4.3 远程通信协议与数据打包

远程通信我用的是自定义的简单协议,帧结构如下:

字节位置内容说明
00xAA帧头1
10x55帧头2
2设备地址0x01~0xFE
3温度高字节温度值整数部分
4温度低字节温度值小数部分
5校验和字节2~4累加和取反
60x0D帧尾1
70x0A帧尾2

温度值的编码方式我做了简化:高字节存整数部分,低字节存小数部分乘以 100。比如 25.6°C,高字节是 25,低字节是 60。这样接收端解析起来很直观,不需要做浮点数运算。校验和用累加和取反,接收端收到后重新计算一遍,如果对不上就丢弃这一帧。

发送的时候用HAL_UART_Transmit阻塞发送,因为一帧只有 8 个字节,115200 波特率下发送时间不到 1ms,对主循环的影响可以忽略。如果你用的是 RTOS,也可以放到一个独立的发送任务里,通过队列传递数据。

void send_temperature_frame(float temperature) { uint8_t frame[8]; int16_t temp_int = (int16_t)temperature; int16_t temp_frac = (int16_t)((temperature - temp_int) * 100); frame[0] = 0xAA; frame[1] = 0x55; frame[2] = DEVICE_ADDRESS; frame[3] = (uint8_t)temp_int; frame[4] = (uint8_t)temp_frac; frame[5] = ~(frame[2] + frame[3] + frame[4]); frame[6] = 0x0D; frame[7] = 0x0A; HAL_UART_Transmit(&huart2, frame, 8, 100); }

5. 本地显示与远程监控的协同

5.1 本地 OLED 显示的实现

本地显示我用的是 0.96 寸的 OLED 屏,I2C 接口,分辨率为 128x64。这颗屏的驱动芯片通常是 SSD1306,网上有现成的驱动库,移植过来就能用。显示内容我一般分三行:第一行显示当前温度值,第二行显示温度单位,第三行显示通信状态。

刷新频率不用太高,500ms 刷一次就够了,因为温度变化本身就很慢,刷太快反而浪费 CPU 资源。OLED 的 I2C 地址和 PJ85718DM 不能冲突,SSD1306 的默认地址是 0x3C,PJ85718DM 是 0x5A,两者不冲突,可以挂在同一条 I2C 总线上。

注意:OLED 和温度传感器挂同一条 I2C 总线时,总线的电容负载会增加。如果发现通信不稳定,可以适当减小上拉电阻的阻值,比如从 4.7kΩ 降到 2.2kΩ。另外,OLED 刷新的时候电流波动比较大,建议在它的 VCC 引脚旁边就近放一颗 10μF 的电容。

5.2 远程数据接收与可视化

远程端我用的是另一块 STM32 板子做接收,通过 USART 接收数据帧,解析出温度值后,再通过 USB 转串口送到电脑上,用串口助手或者自己写的小工具做可视化。如果你需要接入 SCADA 系统,可以在接收端加一个 Modbus RTU 协议转换,把温度值映射到保持寄存器里,这样上位机就能用标准的 Modbus 协议来读取了。

Modbus RTU 的帧格式比自定义协议复杂一些,但好处是通用性强,很多组态软件都原生支持。转换的时候只需要把温度值乘以 10 变成整数,存到寄存器里,上位机读出来再除以 10 就是实际温度。这个做法在工业现场非常常见,因为 Modbus 的可靠性经过了长期验证。

5.3 数据同步与异常处理

本地显示和远程上报的数据必须一致,否则操作员会困惑。我在软件里做了一个简单的同步机制:每次采集到新数据并滤波后,先更新本地显示缓冲区,再更新远程发送缓冲区,两个缓冲区共用同一个温度值变量。这样只要采集任务正常,本地和远程的数据就是一致的。

异常处理方面,我定义了三种状态:正常、传感器故障、通信故障。传感器故障的判断依据是连续 5 次读取失败,通信故障的判断依据是连续 10 帧发送失败。一旦进入故障状态,本地显示会闪烁提示,远程端也会收到一个特殊的故障帧。故障恢复后自动回到正常状态,不需要人工复位。

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

6.1 温度读数跳变或偏差大

这是最常见的问题,原因通常有三个:电源噪声、视场污染、环境温度突变。排查的时候先看电源,用示波器测一下传感器 VCC 引脚上的纹波,如果峰峰值超过 50mV,就要加强滤波。然后检查视场,看看传感器镜头有没有灰尘或者水汽,用无水酒精棉签轻轻擦拭。最后看环境温度,如果传感器本身被阳光直射或者靠近热源,读数也会偏高,需要加遮光罩或者调整安装位置。

我遇到过一个案例,传感器读数每隔几秒就跳一下,后来发现是旁边的一个继电器动作时产生的电磁干扰。解决方法是在继电器线圈两端加一颗续流二极管,同时在传感器的电源线上加一颗磁珠。这两个措施加起来成本不到一块钱,但问题彻底解决了。

6.2 I2C 通信失败或总线锁死

I2C 总线锁死通常是因为从机在通信过程中被复位,导致 SDA 线被拉低。解决方法是:在初始化 I2C 之前,先把 SCL 引脚配置成推挽输出,发送 9 个时钟脉冲,然后再配置成 I2C 模式。这 9 个脉冲可以把从机的移位寄存器清空,释放 SDA 线。

void i2c_bus_recovery(void) { GPIO_InitTypeDef gpio = {0}; gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7; gpio.Mode = GPIO_MODE_OUTPUT_OD; gpio.Pull = GPIO_PULLUP; gpio.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &gpio); for (int i = 0; i < 9; i++) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); HAL_Delay(1); } HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); HAL_Delay(1); }

这段代码在 I2C 初始化失败的时候调用一次,实测下来能解决 90% 以上的总线锁死问题。剩下的 10% 通常是硬件问题,比如上拉电阻没焊或者引脚短路,那就得查 PCB 了。

6.3 远程通信丢包或误码

远程通信丢包的原因比较多,常见的有:波特率不匹配、地线环路、线缆过长、终端电阻缺失。排查的时候先用示波器看波形,如果波形畸变严重,说明线缆太长或者终端电阻不对。如果波形正常但误码率高,检查一下两端的波特率是否一致,以及地线是否共地。

我在一个项目里遇到过 RS485 通信时好时坏的问题,后来发现是 A、B 线接反了。RS485 的 A 线应该接对方的 A 线,B 线接 B 线,如果接反了,空闲状态下总线电平是反的,通信就会时断时续。这个错误很隐蔽,因为有时候居然能通信,只是误码率高,很容易被忽略。

6.4 常见问题速查表

现象可能原因排查方法解决措施
温度读数跳变电源噪声示波器测 VCC 纹波加去耦电容、磁珠
温度读数偏高视场污染检查镜头是否干净清洁镜头、加遮光筒
I2C 通信失败总线锁死测 SDA、SCL 电平执行总线恢复程序
远程丢包波特率不匹配核对两端配置统一波特率
远程误码线缆过长测波形畸变加终端电阻、缩短线缆
系统死机电源浪涌测电源入口电压加 TVS 管、共模电感

7. 实操心得与避坑经验

7.1 传感器安装位置的选择

红外温度传感器的安装位置直接决定了测量精度。我的一般原则是:传感器要正对被测目标,距离控制在 5cm 到 20cm 之间,太近了光斑太小容易受局部影响,太远了光斑太大容易混入背景辐射。如果被测目标表面比较光亮,比如抛光金属,最好在表面贴一块黑色胶带,增加发射率,否则读数会偏低。

在 HVAC 风管里安装的时候,传感器要避开风管弯头和阀门,因为这些地方气流不稳定,温度分布不均匀。我通常会把传感器安装在直管段的中部,距离上游弯头至少 5 倍管径的距离。这个位置的气流最稳定,测出来的温度最有代表性。

7.2 通信线缆的选型与布线

RS485 通信线我推荐用屏蔽双绞线,屏蔽层单端接地,接在接收端。双绞线的作用是抑制差模干扰,屏蔽层的作用是抑制共模干扰。如果现场电磁环境特别恶劣,还可以用带铠装的线缆,机械强度更高,但成本也上去了。

布线的时候要远离动力线,至少保持 30cm 以上的距离。如果必须交叉,尽量垂直交叉,不要平行走线。平行走线会导致动力线上的噪声耦合到通信线上,轻则误码,重则烧毁收发器。这个坑我在一个工厂项目里踩过,后来重新布线才解决,费时费力。

7.3 软件看门狗与异常恢复

工业现场的环境不可预测,软件看门狗是最后一道防线。STM32F303RC 内置了独立看门狗和窗口看门狗,我一般用独立看门狗,超时时间设 2 秒。主循环里每 500ms 喂一次狗,如果程序跑飞了,2 秒后自动复位。

除了看门狗,我还建议加一个软件复位计数器,存在备份寄存器里。每次复位后读取这个计数器,如果连续复位超过 5 次,就进入安全模式,只保留最基本的通信功能,不再执行控制逻辑。这个机制可以防止程序陷入“复位-跑飞-复位”的死循环,给维护人员争取排查时间。

7.4 温度校准的实操方法

PJ85718DM 出厂时已经校准过了,但在高精度应用里,还是建议做一次现场校准。校准方法很简单:用一个经过计量的标准温度计作为参考,把传感器和标准温度计放在同一个恒温环境里,等温度稳定后,记录两者的读数差,把这个差值作为偏移量存在 STM32 的 Flash 里,每次读数时减去这个偏移量。

校准点的选择也有讲究,最好在量程的低端、中端、高端各选一个点,比如 0°C、25°C、50°C,分别测出偏移量,然后用线性插值的方法计算任意温度下的偏移量。这样校准后的精度可以做到 ±0.2°C 以内,比出厂精度提升了一倍多。

8. 方案扩展与后续优化方向

这套方案目前跑在裸机前后台架构上,稳定运行了半年多,没有出现过死机或者数据丢失。如果后续要扩展,我觉得有几个方向值得考虑。第一个是增加无线通信模块,把 RS485 换成 LoRa 或者 Zigbee,这样就不用布线了,安装更灵活。不过无线通信的可靠性受环境影响比较大,需要做更多的现场测试。

第二个方向是增加数据存储功能,在 STM32 上挂一颗 SPI Flash 或者 SD 卡,把温度数据按时间戳存下来,方便事后分析。这个功能在故障诊断的时候特别有用,可以回溯温度变化的趋势,判断是传感器问题还是工艺问题。

第三个方向是增加多路温度采集,用 I2C 多路复用器扩展总线,挂多颗 PJ85718DM,同时监测多个点的温度。这个在大型 HVAC 系统里很有用,比如同时监测送风、回风、新风、排风四个温度,计算焓值和能效比。多路复用的关键是地址分配和采集时序,需要仔细规划,避免总线冲突。

我个人在实际操作中的体会是,嵌入式温度监测这个方向,硬件设计占三分,软件设计占七分。硬件只要按规格书来,一般不会出大问题;软件才是真正体现功力的地方,滤波算法、异常处理、通信协议,每一个细节都决定了系统在现场能不能稳定运行。多花时间在软件上,比反复改硬件划算得多。

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

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

立即咨询