ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

嵌入式温度监测方案:本地实时保护与远程Modbus上送的设计实践

嵌入式温度监测方案:本地实时保护与远程Modbus上送的设计实践

1. 温度监测的两种场景:为什么同一套方案要拆成"本地"和"远程"

嵌入式和 HVAC(暖通空调)领域的温度监测,看起来都是"测个温度",但真正上手之后才会发现,本地监测和远程监测对硬件资源的消耗、对软件架构的要求、对传感器选型的约束完全是两回事。

先定义一下我这里的"本地"和"远程":

  • 本地监测:指在设备本体或同一控制板卡上完成温度采集、算法处理和阈值判断。典型场景包括电机过热保护、电源模块温升监测、压缩机排气温度检测。它的核心诉求是实时性和可靠性,数据基本不出板子,即使通信断掉也要能独立工作。
  • 远程监测:指把采集到的温度数据通过总线、无线等方式上传到上位机、网关或云平台,用于集中监控、趋势分析、报警联动。典型场景包括多联机外机组的温度群控、数据中心的回风温度监控、楼宇自控系统的温度点表。它的核心诉求是通信一致性和数据可追溯性。

实际工程项目里,这两类需求经常会同时出现在同一个控制器上:比如一台 HVAC 外机,压缩机排气管温度必须在本地硬件层面做快速保护,同时回气温度、盘管温度、环境温度又要定期上报给集中控制器。如果只用一颗 MCU 硬扛所有事,一旦通信协议栈出问题,本地保护逻辑也会被拖累。这时候,合理的做法就是用一颗专用传感器芯片负责本地快速响应,用另一颗资源更充足的 MCU 负责远程通信和整体调度。

我这次要聊的组合,就是围绕这个思路展开的:PJ85718DM作为温湿度/温度采集前端,R7FA2A2AD3CFP作为主控 MCU,两者配合完成本地实时监控和远程数据上送。下面从器件选型开始,逐步拆解整个软硬件方案。

2. PJ85718DM 与 R7FA2A2AD3CFP:两颗芯片的分工逻辑

2.1 传感器前端 PJ85718DM 的角色

PJ85718DM 是一颗数字式温度传感器,从命名和封装特征看,它属于典型的 I²C 接口数字传感器,类似于常见的低功耗高精度温度传感芯片。它的关键作用有两个:

第一,降低主控的硬件设计复杂度。传感器内部集成了温度感知单元、ADC、寄存器映射和通信接口,MCU 不需要外接运放、不需要校准电路,直接用 I²C 读寄存器就能拿到温度值。对于 HVAC 这种需要多路温度采集的场景,PCB 面积和 BOM 成本都很敏感,这种集成度高的前端芯片非常有优势。

第二,把"采集"和"处理"解耦。主控负责发起读取命令和解析数据,传感器自己负责采样和转换。即便主控忙于通信任务,传感器依然能按照配置的空闲转换周期持续刷新数据,MCU 随时读到的都是最新值,不会因为调度延迟读到旧数据。

以典型应用为例,PJ85718DM 的工作流程大致是:

  • 上电后通过 I²C 接口写入配置寄存器,设置转换精度和测量模式;
  • 触发一次温度转换,等待转换完成标志位置位;
  • 读取温度寄存器的高字节和低字节,按芯片手册给出的公式换算成实际温度;
  • 可选地配置报警阈值寄存器,超过阈值时告警引脚拉低。

这里要特别注意一个细节:**很多数字温度传感器的"转换完成"标志位是自动清零的,读取数据寄存器的瞬间就会清掉。**如果你的主控代码在中断里读数据,又在主循环里判断状态寄存器,很容易出现数据错乱。这个坑下文会专门展开。

2.2 主控 MCU R7FA2A2AD3CFP 的角色

R7FA2A2AD3CFP 是瑞萨 RA 系列 Cortex-M23 内核的 MCU,主频最高 48MHz,自带 8KB SRAM 和 128KB 左右的 Flash(具体以型号配置为准),集成 12 位 ADC、多路定时器、UART/I²C/SPI 等常用外设。这个配置在 HVAC 和嵌入式控制场景里属于"够用且不浪费"的典型选择。

它在方案里负担的工作有:

  • 通过 I²C 总线周期性读取 PJ85718DM 的温度数据;
  • 对温度数据做软件滤波(滑动平均、中值滤波等),消除环境波动造成的跳变;
  • 本地阈值判断:比如排气温度超过 105℃ 时立即切断负载,这类逻辑追求微秒级响应,必须写在定时器中断或独立线程里,不能依赖远程通信;
  • 把温度数据打包成自定义协议或 Modbus 帧,通过 UART/RS485 发送到远程网关;
  • 接收远程下发的配置命令,比如修改报警阈值、修改采集周期。

之所以选这颗 MCU,而不是随便找一颗 8 位单片机,是因为远程通信协议栈、CRC 校验和状态管理需要一定的 Flash 和 RAM 空间。8 位机硬写 Modbus 从站虽然也能实现,但代码维护成本和调试难度会明显更高。Cortex-M23 内核支持裸机开发和 RTOS,兼容性好,后续想加个 RTOS 也是顺手的事。

2.3 两者之间的电气连接与 PCB 设计要点

PJ85718DM 作为从设备挂在 I²C 总线上,R7FA2A2AD3CFP 作为主控控制总线。连接结构很简单:SCL、SDA 两根线,加上 VDD 和 GND。但有三个细节容易被忽视:

上拉电阻的阻值选择。I²C 总线的上拉电阻一般取 4.7kΩ,但如果你把线拉得很长(比如超过 30cm),就要适当降低到 2.2kΩ 或 1kΩ,以保证上升沿时间满足 I²C 时序要求。特别是 HVAC 设备内部空间大,传感器可能放在铜管附近,连接线要绕过压缩机,线长容易超标。建议在原型阶段就用示波器看 SCL/SDA 的上升沿,不要只看"能不能通信"。

电源去耦。传感器芯片旁边必须放一个 100nF 的陶瓷电容,靠近 VDD 引脚。如果传感器与主控共用一组电源,而主控驱动继电器或蜂鸣器时会造成电源纹波,那最好给传感器单独加一个 LDO 或磁珠隔离。温度传感器的参考电压直接决定转换结果的准确度,电源脏,温度数据就飘。

地线处理。传感器地线不要单独走一根细线绕很远,最好在传感器附近就近打过孔连接到主控地平面。尤其是 PWM 调速的风机、压缩机的开关电源回路,会产生大量的高频噪声,地线阻抗稍高就会把噪声耦合进 I²C 信号里。

3. 本地温度监测的实现:快速响应优先,通信靠边站

3.1 实时性需求的本质是什么

本地温度监测里最典型的需求是过热保护。比如 HVAC 系统的压缩机排气温度一旦超过上限,几秒钟内就可能造成润滑油劣化、甚至压缩机损坏。这种场景下,温度数据的时效性比精度更重要——你不需要知道温度是 101.2℃ 还是 101.8℃,你只需要知道"它过线了,赶紧停机"。

所以本地监测的设计原则是:传感器负责持续采样,MCU 负责快速读取和判断,告警路径尽量短。

3.2 用定时器轮询实现周期读取

我在这个方案里采用的是定时器驱动的高频轮询模式,而不是完全依赖 I²C 中断。原因很简单:PJ85718DM 这类传感器的转换时间一般在几十毫秒级,如果每次转换完成都触发中断,中断频率不高还好,但一旦你要同时管理多路传感器或通信任务,中断优先级设置就变得很麻烦。定时器轮询的好处是节奏稳定,代码逻辑一目了然。

具体做法:

  • 配置一个 1ms 的软件定时器,累计 50 个 tick 后执行一次温度读取;
  • 每次读取流程是:发起转换命令 → 等待转换完成(轮询状态寄存器,加超时保护)→ 读取温度字节 → 更新全局温度变量;
  • 主循环或控制逻辑直接访问全局温度变量,做阈值判断。

伪代码如下:

#define TEMP_I2C_ADDR 0x48 #define CONV_PERIOD_TICKS 50 // 50ms volatile uint16_t g_temp_raw = 0; volatile uint8_t g_temp_valid = 0; void timer1ms_callback(void) { static uint32_t tick_cnt = 0; tick_cnt++; if (tick_cnt >= CONV_PERIOD_TICKS) { tick_cnt = 0; start_temp_conversion(); } } void start_temp_conversion(void) { uint8_t cmd = 0x51; // 示例:启动转换命令 i2c_write(TEMP_I2C_ADDR, &cmd, 1); // 转换完成后在后续轮询中读取 }

读取阶段再加一个超时保护:

uint8_t temp_read(uint16_t *raw) { uint8_t status = 0; uint16_t timeout = 10000; // 10ms 超时 while (timeout--) { i2c_read_reg(TEMP_I2C_ADDR, 0x00, &status, 1); if (status & 0x80) { // 转换完成位 uint8_t buf[2]; i2c_read_reg(TEMP_I2C_ADDR, 0x01, buf, 2); *raw = (buf[0] << 8) | buf[1]; return 1; } } return 0; // 超时 }

这个结构的核心价值在于:把"读取数据"和"消费数据"分离。控制逻辑只读 g_temp_raw,不需要关心底层到底哪一步卡住了。即使 I²C 通信偶发故障,控制逻辑也能根据 g_temp_valid 标志决定是否进入保护状态,避免误动作。

3.3 软件滤波:中值滤波比平均滤波更适合温度

温度传感器最容易出现的异常是偶发的尖峰干扰。比如压缩机启动瞬间的大电流导致电源跌落,传感器的转换结果偶尔会跳变十来度。如果直接拿这个跳变值做超温判断,可能造成误停机。

我比较推荐的做法是在每次有效读值后做一个 5 点中值滤波。原理很简单:连续记录最近 5 次采样值,排序后取中间值作为输出。因为温度变化本身是缓慢的,而尖峰干扰通常只持续一次采样,中值滤波可以干净利落地剔除它。

平均滤波也能消掉一部分噪声,但对尖峰干扰的抑制能力不如中值滤波。比如 5 个采样值里有一个跳变 +15℃,平均值会被拉高 3℃,可能超过阈值;而中值滤波基本不受影响。

滤波代码如下:

#define SAMPLE_BUF_NUM 5 uint16_t temp_median_filter(uint16_t new_sample) { static uint16_t buf[SAMPLE_BUF_NUM]; static uint8_t idx = 0; uint16_t tmp[SAMPLE_BUF_NUM]; buf[idx] = new_sample; idx = (idx + 1) % SAMPLE_BUF_NUM; memcpy(tmp, buf, sizeof(tmp)); // 简单冒泡排序 for (uint8_t i = 0; i < SAMPLE_BUF_NUM - 1; i++) { for (uint8_t j = 0; j < SAMPLE_BUF_NUM - 1 - i; j++) { if (tmp[j] > tmp[j+1]) { uint16_t t = tmp[j]; tmp[j] = tmp[j+1]; tmp[j+1] = t; } } } return tmp[SAMPLE_BUF_NUM / 2]; }

你会发现这个滤波方法对代码空间和 RAM 占用都很小,在资源紧凑的 MCU 上非常实用。唯一要注意的是,滤波输出的温度会比真实温度滞后几次采样周期,但对于几十毫秒级的采集周期来说,这种滞后完全可以忽略。

3.4 本地告警:硬件中断路径与软件保护路径双保险

阈值判断如果把逻辑写在主循环里,最坏情况可能要经过几个毫秒才能响应。对于断电保护这类动作,几个毫秒其实问题不大——毕竟机械继电器自身动作都要 5~10ms。但如果你的项目要求非常苛刻,比如需要在传感器上报超温后 1ms 内拉断 PFC 驱动信号,主循环就不够看了。

此时可以采用双路径告警:

  • 路径一:PJ85718DM 的报警输出引脚(ALERT)直接连接到 MCU 的外部中断引脚。传感器内部配置好报警阈值,超温时 ALERT 引脚自动拉低,MCU 外部中断立即响应。这个路径不经过 I²C 读数据,反应速度最快。
  • 路径二:MCU 软件轮询温度并判断阈值,作为备份保护。即使传感器 ALERT 引脚因为配置失误没生效,软件路径也能兜底。

我在实际项目里特别验证过路径一的稳定性:ALERT 引脚必须配置为开漏输出,MCU 侧加上拉电阻。如果传感器不支持内部上拉,而你又忘了外面加上拉,ALERT 引脚会一直浮空,干扰中断触发。这个问题看起来小,但在现场最容易翻车。

4. 远程温度监测的实现:Modbus 从站协议与数据上送

4.1 远程侧的需求分析

远程监测的价值在于集中管理。在一个多联机系统里,可能有一台集中控制器要巡检十几台外机的温度数据。这时候每台外机的 MCU 都是一个 Modbus 从站,集中控制器作为主站周期性读取。

远程侧和本地侧的最大区别是:数据不再"即取即用",而是需要打包、排队、加上校验,等待主站来读。你的代码必须能处理以下情况:

  • 主站可能在上电后的任意时刻发起读请求;
  • 通信线路可能被干扰,出现 CRC 错误帧,从站必须正确丢弃;
  • 主站可能修改从站的配置寄存器,从站需要做写保护或范围检查;
  • 从站忙的时候(比如正在写 Flash),不能长时间锁死总线。

基于 R7FA2A2AD3CFP 的资源,我建议直接上裸机状态机实现的 Modbus 从站,没必要为此跑 RTOS。

4.2 Modbus 寄存器映射设计

温度数据虽然只有 16 位,但按照 Modbus 协议的惯例,按有符号数处理。温度值可以放大 10 倍传输:比如 25.5℃ 存为 255。这样做的好处是:

  • 数据精度保留到 0.1℃;
  • 传输过程中不会出现小数位丢失;
  • 上位机组态软件直接显示整数,再除以 10,逻辑清晰。

我设计的寄存器表如下:

寄存器地址读写属性内容说明
0x0000只读本地温度(放大10倍)PJ85718DM 当前读数
0x0001只读远程温度(放大10倍)预留外部传感器接口,直接透传
0x0002只读状态字bit0=本地传感器在线,bit1=超温告警
0x0100读写采集周期(单位 ms)默认 500,范围 100~5000
0x0101读写超温告警阈值默认 100.0℃(即1000),生效需写 0x0103 保存

有一点需要特别提醒:寄存器地址是字地址,不是字节地址。很多第一次写 Modbus 从站的人会把 0x0000 当成报文里的第一个字节地址,结果偏移一位,读出来全是错数据。Modbus RTU 报文里通过功能码 0x03 读取保持寄存器时,请求数据中的起始地址就是字地址。调试时一定要先拿一个标准的 Modbus 调试工具(比如 ModbusPoll 或类似的软件)确认地址映射,再写上位机。

4.3 用状态机实现 Modbus 从站,避免阻塞式串口收发

最常见的错误做法是在串口中断里一直接收数据直到收完一整帧,然后才开始解析。这种做法有两个致命问题:

  1. 如果主站发送帧中间有字节间隔超过协议规定的 1.5 个字符时间,你的程序必须判断为帧超时并丢弃这一帧。在中断里做超时判断,需要额外的定时器配合,代码复杂度高。
  2. 如果把接收和解析都放在中断里,主循环中的温度采集和告警逻辑就会被阻塞,本地保护的实时性就打了折扣。

更稳妥的做法是在裸机上用状态机:

  • 串口接收中断逐字节把数据放入环形缓冲区,并记录时间戳;
  • 主循环定期检查缓冲区,如果发现帧间隔超时,就认为一帧结束,开始解析;
  • 解析完成后立即准备响应帧,放入发送缓冲区;
  • 通过串口发送中断或 DMA 逐字节发出。

这样无论通信多繁忙,主循环都能在每帧之间继续做温度采集和本地保护。我用 R7FA2A2AD3CFP 的 UART 中断实测,48MHz 下处理标准 Modbus RTU 帧(8 字节请求、8 字节响应)的时间开销不到 1ms,完全不影响 50ms 的温度轮询。

4.4 远程数据的完整传输路径

温度数据从传感器到远程主站的完整路径如下:

  1. MCU 定时器触发读取 PJ85718DM;
  2. 中值滤波后,更新本地温度变量;
  3. 主站发送功能码 0x03 读 0x0000 寄存器;
  4. 从站 CRC 校验通过后,把本地温度值填入响应报文;
  5. 从站发送响应帧,主站收到后解析显示。

如果还需要把远程数据回传给更高层(比如云平台),那就是网关的活儿了。MCU 只需要保证 Modbus 寄存器里的数据是最新的即可。

这里我额外分享一个现场经验:Modbus 从站的 CRC 计算一定要用查表法,不要用逐位计算。逐位算法虽然代码少,但在 48MHz 主频下计算一帧 CRC 耗时约几百微秒,如果你在一个主循环里又读温度又算 CRC,会明显拉长循环周期。查表法以空间换时间,128 字节的查找表对应到 RA 系 MCU 的 Flash 完全不是负担。

5. 实测冷启动、采样率与总线时序:数据不会说谎

5.1 冷启动时间对现场判断的影响

HVAC 设备上电后,传感器和主控的启动时间不一致很容易导致"首次数据无效"。我在调试时遇到过一个有意思的现象:主控先完成初始化,立刻去读 PJ85718DM,读到的温度总是 0℃(寄存器值 0x0000)。查了逻辑分析仪才发现,传感器还在上电复位阶段,I²C 从机地址还没准备好,主控发出 START 信号后,传感器根本不应答,总线上的 SDA 被拉死,直到超时。

解决办法是:主控上电后延时 100ms 再初始化 I²C 外设。如果传感器有 Ready 引脚(部分型号有),也可以轮询这个引脚,确认芯片内部校准完成后才开始通信。

另一个坑是:首次读取的温度不可信。数字温度传感器上电后第一次转换往往是在内部基准稳定之前完成的,读数可能偏高或偏低。最省事的做法是上电后连续读 5 次,丢前 2 次,取后 3 次的中值作为有效初值。

5.2 采样率上限是多少

PJ85718DM 的转换时间决定了采样率上限。假设转换时间是 100ms,那么理论最高采样率就是 10Hz。但实际工程里我不会按极限来设置——我一般配置 50ms~500ms 一个采集周期,具体取决于应用:

  • 本地过热保护:50ms 采样,反应快;
  • 远程温度上送:500ms 采样足够,没必要增加总线负载;
  • 环境温度统计:1s 以上,配合软件滤波取均值。

如果你把采集周期设为 50ms,而传感器转换一次需要 100ms,那读到的数据永远是上一次转换的结果,相当于周期显示却没更新。所以配置周期前必须查清芯片手册的转换时间表。

5.3 I²C 时序实测:上拉电阻与线长的影响

我特意在 30cm 的杜邦线连接下测了一组数据:

  • 上拉电阻 4.7kΩ,SCL=100kHz:上升沿明显变缓,但仍能通信;
  • 上拉电阻 4.7kΩ,SCL=400kHz:开始出现偶发的 SCL 毛刺,通信偶尔失败;
  • 上拉电阻 2.2kΩ,SCL=400kHz:波形改善明显,通信稳定;
  • 上拉电阻 1kΩ,SCL=400kHz:上升沿最好,但功耗略高,传感器微热。

所以在实际产品里,我倾向于在 PCB 上预留 2.2kΩ 的默认上拉位置,同时允许现场通过排针更换 1kΩ 以适配长线。软件上保持 I²C 时钟在 100kHz 或 250kHz,不盲目追求 400kHz,很多时候"稳"比"快"更重要。

6. 故障排查与数据校准:那些必须单独拎出来讲的细节

6.1 温度跳变到 0xFFFF 或 0x0000 的排查顺序

如果读回来的原始值是全 1(0xFFFF)或全 0(0x0000),几乎都是通信链路问题,而不是温度真的到了 0 或 65535 度。排查顺序是:

  1. 确认传感器供电电压是否正常,VDD 是否在规格范围内;
  2. 检查 I²C 地址是否正确,芯片地址引脚有没有悬空或短接;
  3. 用示波器或逻辑分析仪看 SCL/SDA 上是否有正常的 START/STOP 信号;
  4. 检查 SDA 是否在应答位被从机拉低——如果从机不应答,SDA 在第九个时钟周期会一直保持高电平;
  5. 查地址与读写位组合:I²C 7 位地址左移一位后,最低位是 R/W。很多人把 7 位地址当成 8 位直接填进代码,导致最低位错位,通信失败。

这些检查做完,基本能解决 80% 的通信问题。剩下 20% 往往是 PCB 布局问题,需要把示波器探头直接点在芯片引脚上看波形。

6.2 温度曲线平滑不可信的另一种可能

中值滤波能剔除尖峰,但如果你发现温度变化曲线明显"台阶化"——不是平滑上升,而是一格一格跳,那可能是数据位解析有问题。比如温度寄存器的高字节和低字节拼接错误,或者符号位扩展没做对,导致每 1℃ 的跳变在二进制上被放大了几十倍。

处理办法是:找一个已知温度环境(比如 25℃ 左右的空调房),用工业级校准温度计对照,先确认读数偏差在合理范围内,再怀疑代码逻辑。

6.3 校准经验:两点校准足够,多点校准是浪费

PJ85718DM 这类数字温度传感器出厂前已经做过校准,误差通常在 ±0.5℃ 以内。如果你的产品需要更高精度,比如 ±0.2℃,可以做两点校准:一点放在冰水混合物(0℃),一点放在恒温槽(50℃ 或 100℃),计算增益和偏移,然后写进传感器的校准寄存器或者 MCU 的 Flash。

校准公式很简单:

实际温度 = 原始温度 * 增益 + 偏移 增益 = (T2实际 - T1实际) / (T2原始 - T1原始) 偏移 = T1实际 - T1原始 * 增益

校准之后一定要做一次反向验证——在第三温度点测试,确认误差在要求范围内。如果三点都不满足,就要怀疑传感器芯片本身的一致性或者 PCB 热设计有问题了,比如传感器附近有散热片或热源导致热梯度。

7. 从单板到系统:扩展多路温度采集与备用方案

7.1 I²C 地址冲突的解决办法

一个 I²C 总线上可以挂多个 PJ85718DM,前提是每个芯片的地址引脚配置不同。假设地址引脚有 A0、A1 两根,那么最多扩展出 4 个地址。如果项目需要 8 路温度采集,最简单的方案是分两条 I²C 总线,或者加一颗 I²C 多路复用器(如 TCA9548A 类似的芯片)。

从实践来看,我更推荐多路复用器方案,因为 R7FA2A2AD3CFP 只有一组硬件 I²C 接口(或可复用的两组),如果靠 GPIO 模拟 I²C,代码复杂度和稳定性都会下降。硬件 I²C + MUX 的组合,只需要顺序切换 MUX 通道,然后按标准 I²C 流程读数据即可。

7.2 备用通信链路的考虑

对于 HVAC 等现场设备,远程通信链路难免出现故障。我在项目里会预留一个本地维护用的 UART 调试口,输出一段简单的 ASCII 字符串,包含当前温度、传感器状态和故障码。这样现场工程师带着 USB-TTL 线就能快速定位问题,不需要额外带工装。

这个调试口的协议不必很复杂,比如每秒输出一行:

T_LOC=25.6, T_REM=24.8, SENSOR=OK, ALARM=NONE

代码实现只有几行,收益却很大。尤其当你需要让生产线的测试工装去校准温度时,解析这种纯文本输出比调 Modbus 寄存器方便得多。

7.3 软件架构上的小小反思

最后聊聊我在这个方案迭代过程中调整过的思路。最初我试图把温度和通信全部耦合在一起,写了一个大循环。后来发现,温度采集的节奏、通信状态机的节奏、告警判断的节奏互相干扰,调试时非常痛苦。现在的做法是拆成三个模块:

  • 采集模块:负责传感器驱动和滤波,输出统一的温度变量;
  • 控制模块:只消费温度变量,做阈值判断和继电器控制;
  • 通信模块:只管 Modbus 帧收发和寄存器读写,不关心温度怎么来的。

模块之间通过全局变量和简单的函数接口交互。虽然看起来没有那么"高内聚低耦合",但对这个量级的项目来说,够用、好查问题,就是好架构。等你真的经历过一次现场投诉"温度乱跳",你就知道模块清晰能省多少时间。

我在实际调试中最大的体会是:PJ85718DM 与 R7FA2A2AD3CFP 这套组合的难点从来不在于某个技术点有多深,而在于本地与远程两种需求的优先级容易打架。把采集、控制、通信按责任拆开,让本地保护不依赖通信链路,让远程数据不干扰本地实时性,这套系统才能真正稳定地跑在现场。如果大家在具体实现中遇到奇怪的温度数据或通信异常,欢迎按上面几个排查链路试试,大多数情况下都能快速定位。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进