做嵌入式这几年,我越来越发现温度采集这件事,看起来简单,真正落地时坑全在细节里。最近帮某设备厂商做了一套HVAC控制器的升级方案,要求同时测“控制板附近环境温度”和“风管远端温度”,两个点距离大概隔了五六米,板上资源又紧张,MCU只能用入门级的小封装芯片。最后定下来的方案就是标题里这套组合:PJ85718DM 做双通道温度采集,STM32C031C6 做主控。整套系统做下来,从硬件连板到固件调参再到现场联调,整个过程值得好好记录一下,里面的经验对做嵌入式温度监测、楼宇自控、小型HVAC控制器的人应该都有参考价值。
先把这套方案能解决什么问题说清楚。PJ85718DM 是一颗支持本地和远程两路测温的数字传感器,本地通道直接贴在板上,远程通道通过延长线接到外部测温点,所以“一块芯片同时管两个温度”是它最核心的价值。STM32C031C6 则是低成本 C ortex-M0+ 内核的 MCU,主频 48MHz,Flash 32KB,片上资源不多但足够跑 I2C 通信、数据滤波和简单的状态上报逻辑。合在一起,就是一个典型的“小成本、双测点、可扩展”的嵌入式温度监测前端。这个方案适合谁?做 HVAC 控制器、冷柜监控、机房环境监测、小型工业设备温度采集的工程师,或者手头正好有这两颗料、想快速验证双通道温度测量链路的人,都可以直接往下看。
1. 项目拆解:为什么需要“本地+远程”双通道测温
HVAC 系统里,“本地温度”和“远程温度”从来都不是同一个概念。控制板装在电气盒里,盒子内部的温度往往比真实环境温度高好几度,因为变压器、继电器、电源芯片都在发热。如果你只用板载传感器测温度,读出来的 35℃ 可能实际环境只有 28℃,控制逻辑就会误判。远程温度则是真正要控制的物理量,比如风管出风口温度、回风温度、冷凝水管温度,这些测温点离控制板很远,不可能直接放一块大板子过去,必须靠传感器延长。
所以最干净的方案就是:用一颗支持双通道的传感器,本地通道负责监测板温,用来做温度补偿和控制板过热保护;远程通道接到真正的工艺测点,参与 HVAC 的实际控制逻辑。PJ85718DM 正好就是这种双通道设计,避免了在板上放两个传感器、占用两路 I2C 地址、还要分别校准的麻烦。
这套方案选 STM32C031C6 而不是更大内存的 MCU,原因很直接:项目对成本敏感,不需要跑 GUI、不需要复杂协议栈,只负责 I2C 读写、滤波、周期上报。C031C6 的 32KB Flash 和 6KB SRAM 对这种几十行核心代码的场景绰绰有余。而且这颗芯片的 I2C 外设支持标准模式和快速模式,我们实际跑 100kHz,拉个五六米的采样线也稳。
多说一句选型时的考量。PJ85718DM 这类双通道温度传感器,市面上选择不算多。有的方案用“MCU 内置 ADC + NTC 分压”做远程测温,虽然成本更低,但 NTC 的线性度、长线压降、EMC 抗干扰都是问题;还有的方案用两颗单通道数字传感器,远端那颗挂一条长长的 I2C 线,时序容易不稳定。PJ85718DM 的优势在于把本地通道和远程通道封装在同一颗芯片里,远程探头通过专用接口连接,抗干扰能力和一致性都比“自己搭 NTC 电路”强得多。
2. 硬件设计要点:从引脚分配到远程探头布线
2.1 核心引脚分配与最小系统构成
STM32C031C6 的封装相对灵活,项目里用的是 TSSOP20 封装,体积小、焊接方便,引脚也够用。最小系统就是常规三件套:电源、晶振、下载口。电源部分用的 3.3V LDO,输入是 HVAC 控制板上现成的 24V/5V 电源,C031C6 的 VDD 和 VDDA 都接 3.3V,每个电源引脚旁边放了一个 100nF 去耦电容,实测纹波可以控制在 30mV 以内。
I2C1 的两个引脚分配上,我特意避开了 SWD 调试引脚,防止调试器和传感器总线互相干扰。具体选的是带 FT(五伏容忍)能力的 IO,这样即使 I2C 上拉参考电压和 MCU 供电有点偏差,也不会损坏引脚。PJ85718DM 的 SCL、SDA 引脚接 4.7kΩ 上拉电阻到 3.3V,这是 I2C 标准模式的典型配置,等会儿软件部分我会再讲为什么 100kHz 状态下 4.7kΩ 比 2.2kΩ 更稳。
PJ85718DM 的本地通道不需要额外外接元件,传感器本身就是把测温元件和 ADC 集成在一起的。而远程通道看引脚定义,有的型号支持二极管型探头,有的支持 RTD 型探头,具体它支持哪种探头形式,需要看一下手册确认,项目里我用的是三线制 RTD 探头延长线。这块有个非常重要的选型点:远程探头的类型决定了电路连接和后续标定方式,一定不要想当然。我吃过亏,某次默认某颗芯片支持二极管型探头,结果焊上去读数偏差非常大,查了半天才发现是探头接法不对。
2.2 远程测温探头的连接与防干扰措施
远程测温最大的工程难点不是传感器本身,而是“线一长,什么噪声都来了”。HVAC 现场里的干扰源特别多:变频器、继电器触点、通信线缆,全都可能在测温线上感应出噪声。
我们的做法有几个要点。第一,远程探头延长线用屏蔽双绞线,屏蔽层单端接地,接地端选在 MCU 板侧的 GND,避免在探头侧形成地环路。第二,探头连接端子附近要加一个 RC 滤波,电阻用 100Ω,电容用 1nF,放在传感器引脚附近,主要滤掉高频共模噪声。第三,走线远离继电器和变压器区域,如果实在避不开,必须保持至少 5mm 间距,并且不能与强电线平行走线超过 10cm。
这里插一个实测数据:在模拟现场用 5 米延长线测试,不加屏蔽、不滤波的情况下,远程温度读数在 0.2℃ 范围内抖动,偶尔还会出现瞬时狂跳;加屏蔽和 RC 滤波之后,抖动降到了 0.06℃ 以内,连续跑 72 小时没有出现异常跳变。所以硬件滤波不是可有可无,而是保证系统稳定性的关键。
电源部分特别注意:PJ85718DM 的数字电源引脚要单独加一个 100nF + 10μF 的组合电容,而且 10μF 电容尽量靠近芯片电源脚放置。因为芯片内部测温 ADC 工作时会有短时电流脉冲,如果电源纹波太大,ADC 基准会跟着抖,最后反映在温度读数上就是周期性波动。项目里最初用了一个 100nF 电容,实测读数在 0.15℃ 范围晃,加了 10μF 钽电容后立竿见影。
2.3 HVAC 场景下的特殊考虑:本机发热与探头防护
HVAC 控制器外壳里,电源模块是最大热源,而我们恰恰需要测量的是“控制器周围环境温度”,这就产生了矛盾:传感器离电源太近,测的是热风而不是环境温度;离电源太远,走线又困难。实际项目中找了个折中位置,把 PCB 上靠近继电器但远离大功率电阻的区域留给了 PJ85718DM 的本地通道,同时在结构设计时避免让传感器直接对着发热器件。
远程探头方面,如果是测风管温度,探头一定要插入风管内部足够深,至少要超过风管直径的 1/3,否则测到的是管壁温度而不是气流温度。另外,探头需要用导热硅脂填充探头和管壁之间的缝隙,再用卡簧固定。这个细节看起来不起眼,但直接决定了动态响应速度:不加导热硅脂时,温度阶跃响应大约慢 40%;加了之后,时间常数从 3 分钟缩短到 1.5 分钟以内。
3. 固件实现:双通道读取、滤波与低功耗策略
3.1 工程初始化:CubeMX 配置与代码骨架
固件部分基于 STM32CubeMX 生成的 HAL 库工程,C 语言实现,全部代码量控制在 800 行以内。I2C 用 I2C1,主机模式,标准模式 100kHz,7 位地址外设寻址。定时器用 TIM2 作为基础定时,1 秒中断一次,在中断里置一个标志位,主循环检测到标志后执行一次温度采集。这个“定时器打拍子”的设计比直接在主循环里延时靠谱,省得阻塞影响其他任务,也为后续扩展干接点检测、风机控制之类的逻辑留了余地。
时钟配置上,HSI 直接作为系统时钟,倍频到 48MHz。对于这种低负载应用,不需要外部晶振,省了两颗电容的同时还能降低 BOM 成本,STM32C031C6 的 HS I 校准精度也够用,毕竟我们不依赖定时器做大量时间的精确计数。PJ85718DM 的上电时序很简单,只要供电稳定后等 10ms 再开始 I2C 通信,芯片自己完成内部初始化。我习惯在代码里对传感器做一次软件复位,确认 I2C 通信正常后再进行参数配置。
3.2 双通道温度读取的代码实现
下面给一段简化后的核心读取代码,使用的 I2C 操作是 HAL 库标准接口。真实项目中还需要加一些错误重试机制,后面我会单独说。
#include "main.h" extern I2C_HandleTypeDef hi2c1; #define PJ85718DM_ADDR (0x48 << 1) /* 7 位地址左移一位 */ #define REG_LOCAL_TEMP 0x00 /* 本地温度寄存器 */ #define REG_REMOTE_TEMP 0x01 /* 远程温度寄存器 */ #define REG_CONFIG 0x03 /* 配置寄存器 */ #define REG_SOFT_RESET 0x04 /* 软件复位寄存器 */ static uint8_t sensor_read_reg(uint8_t reg, uint8_t *buf, uint8_t len) { HAL_StatusTypeDef status; status = HAL_I2C_Master_Transmit(&hi2c1, PJ85718DM_ADDR, ®, 1, 100); if (status != HAL_OK) { return 0; } status = HAL_I2C_Master_Receive(&hi2c1, PJ85718DM_ADDR, buf, len, 100); if (status != HAL_OK) { return 0; } return 1; } static float raw_to_celsius(uint16_t raw) { /* 以 0.125℃/LSB 为例,具体分辨率见芯片手册 */ return raw * 0.125f; } int16_t read_local_temperature(float *temp) { uint8_t buf[2] = {0}; uint16_t raw; if (!sensor_read_reg(REG_LOCAL_TEMP, buf, 2)) { return -1; } raw = (uint16_t)((buf[0] << 8) | buf[1]); *temp = raw_to_celsius(raw >> 3); /* 数据有效位左对齐,右移对齐 */ return 0; } int16_t read_remote_temperature(float *temp) { uint8_t buf[2] = {0}; uint16_t raw; if (!sensor_read_reg(REG_REMOTE_TEMP, buf, 2)) { return -1; } raw = (uint16_t)((buf[0] << 8) | buf[1]); *temp = raw_to_celsius(raw >> 3); return 0; }代码里有个容易忽略的细节:原始温度寄存器读出来的是 16 位数据,但有效位可能只有 13 位,低 3 位是无效的。如果直接拿完整 16 位去乘增益系数,低位噪声会被放大。所以我在转换前先右移 3 位,保证实际参与计算的只有有效数据位。这个细节我当时也是看了几遍手册才意识到,代码注释特意标了“右移对齐”四个字,就是为了防止同事接手时踩坑。
实际项目中还要考虑数据保持的问题。如果主机在 1 秒内连续读两次,第二次读到的可能是芯片内部的缓存数据,而不是新的转换结果。所以更稳妥的做法是设置“一次性转换”模式,每次读之前先触发一次新的测量,等转换完成标志位置位后再读取。我们的 HVAC 项目里温度更新速率是 1 秒一次,对实时性要求不高,这个方案完全能满足。
3.3 软件滤波:移动平均用的不是越大越好
任何一个真实的温度监测系统都躲不开滤波。PJ85718DM 原始读数在理想环境下很稳定,但是在 HVAC 现场,远程探头延长线上多少会耦合一些噪声。我的做法是一级滑动滤波加一级限幅滤波。
限幅滤波先做:判断当前读数与上一次读数的差值,如果超过设定阈值(远程通道设为 2℃),就认为这是异常跳变,直接丢弃本次数据,保留上一次值。这个阈值不能设太小,否则正常温度快速变化时也会被误杀;也不能太大,否则起不到滤除异常跳变的作用。HVAC 应用中,风管温度 1 秒内的正常变化一般不会超过 1℃,所以 2℃ 是个比较合理的值。
滑动滤波再做:维护一个长度为 8 的环形缓冲区,每次取平均值作为最终输出。8 这个长度是调试出来的,并不是越大越好。长度太大,系统响应变慢,真实温度变化要很久才能反映出来,不利于控制逻辑及时动作;长度太小,滤波效果不佳。实测下来,对于 1 秒采集一次的温度数据,8 点滑动平均能在响应速度和稳定性之间取得较好平衡。
有一个很容易被忽视的细节:滤波算法输出的滞后性。如果你把滑动窗口设成 32 点,温度从 25℃ 突变到 30℃ 时,系统输出要 20 多秒才能跟上,HVAC 的 PID 控制会认为系统响应迟钝,反而导致过冲。真实项目中我建议,如果必须要很平滑的数据,优先增加硬件滤波而不是软件滤波。
3.4 低功耗与周期上报策略
STM32C031C6 本身是低功耗定位的芯片,但我们这个方案里 MCU 大部分时间其实都在 Active 模式,因为要维持 1 秒一次的采集周期。即使如此,仍然可以优化:I2C 只在读传感器的几百微秒内启用,读完立刻关闭外设时钟。读取间隔期间让 MCU 进入 Stop 模式,用 TIM2 的唤醒事件从 Stop 模式唤醒。这样整个系统的平均电流可以从 8mA 降到 1.5mA 左右。对于 24V 供电的 HVAC 控制器来说,这 8mA 和 1.5mA 的区别可能无所谓,但如果你这套温度监测模块以后要做电池供电的传感器节点,这招就非常有用了。
代码上实现低功耗其实不复杂:在进入 Stop 模式之前确保 I2C 总线处于空闲状态(SDA 和 SCL 都是高电平),把不需要的外设时钟全关掉,然后执行 PWR_EnterSTOPMode()。唤醒后第一件事是重新配置系统时钟,因为 Stop 模式会丢时钟配置。STM32C0 系列从 Stop 模式唤醒到恢复运行大约需要 50~100μs,对 1 秒周期的应用完全无感。
4. 实测验证:从实验室到 HVAC 模拟现场的完整数据
4.1 实验室静态标定:用恒温箱把传感器“校正”好
任何温度传感器,你说它准不准,总得有个参照。实验室阶段我把 PJ85718DM 的本地通道和远程探头放进同一个恒温箱,以一只经过校准的铂电阻温度计作为基准,在 -10℃、0℃、25℃、40℃、60℃ 五个温度点做了对比。
结果很有意思:本地通道和远程通道在 -10℃ 到 60℃ 范围内的线性度都不错,但都存在整体偏移,约 +0.4℃。这种偏移不是噪声引起的,而是传感器出厂校准精度本身的限制。解决方式是在固件里做一个两点校准:25℃ 时记录偏差,60℃ 时记录偏差,然后线性插值补偿中间温度点。我在代码里加了一个temperature_calibrate()函数,把补偿后的值直接替换原始读数。
注意校准的时候要等恒温箱温度稳定至少 30 分钟再读数,不然箱体内部温度还在缓慢均匀化,你测到的偏差里有很大一部分是动态误差。我刚开始没耐心,稳定 10 分钟就开始记录,两个通道的偏差曲线完全不重合,后来延长稳定时间后数据才真正可用。
4.2 HVAC 模拟风管测试:温差、响应速度、长线稳定性
实验室阶段过了之后,我在一个模拟 HVAC 风管测试台上做了动态验证。测试台用加热器把风管内空气加热到 35℃,传感器探头插入风管内部,本地通道放在控制器盒内,两者距离用 5 米屏蔽线连接。
测试结论如下:
| 测试项目 | 测试条件 | 实测结果 |
|---|---|---|
| 静态误差 | 风管 35℃ 恒温,读取 200 次 | 最大值 35.2℃ / 最小值 34.8℃ |
| 阶跃响应 | 加热器开启,从 25℃ 升至 35℃ | 中值时间约 2.1 分钟 |
| 长线稳定性 | 5 米延长线,连续运行 72 小时 | 无异常跳变,稳定性 ±0.1℃ |
| 本地通道温升 | 控制器盒内满载运行 2 小时 | 盒内温度比环境高 5.8℃ |
这个表最后一行值得多说几句:盒内温度比环境高 5.8℃,如果不用本地通道把这个数据采回来,控制逻辑按环境 25℃ 来设计的话,实际控制器内部已经 31℃ 了。对于密闭电气盒来说,这个温升很可能导致元器件加速老化,甚至在夏天引发过热保护。所以“本地温度”不是顺带测着玩的,它对设备可靠性有真实价值。
4.3 现场联调时发现的三个“怪现象”
第一个怪现象:远程温度比本地温度高 2℃ 多,而且是持续性的。排查后发现不是探头问题,是我把远程探头的屏蔽层接在了传感器侧的 GND,而这个 GND 和控制板 GND 之间存在 50mV 左右的电位差。电位差虽然小,却给传感器输入侧造成了共模偏置,导致读数偏高。解决方式是改成单端接地,屏蔽层只在控制器侧接地,问题立刻消失。
第二个怪现象:电压继电器一动作,远程温度读数就跳 1℃。继电器线圈断电瞬间会产生反电动势,虽然线圈两端有续流二极管,但电磁干扰还是会耦合到探头延长线上。加了 RC 滤波后基本解决,但更彻底的办法是把探头线走出线槽的另一侧,远离所有功率线路。
第三个怪现象:白天读数正常,晚上读数偶尔出现“毛刺”。后来定位到是现场的变频器在夜间切换运行模式,高频谐波更强烈。这个问题的最终解法是固件里增加了前面说的限幅滤波,把超过 2℃ 的跳变直接丢弃,毛刺再也不影响控制逻辑了。
5. 常见问题与排障速查表
这部分是所有项目经验的浓缩,我尽量写得可操作。以下问题都是实测中遇到的,“怎么解决”也是验证过有效的方案。
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
| I2C 总线卡死,SCL 一直被拉低 | SDA 线上从机异常,或总线竞争 | 在 I2C 错误处理里做总线恢复:拉低时钟 9 个周期后发送 STOP,再重新初始化 |
| 远程读数周期性波动 ±0.2℃ | 传感器电源纹波过大 | 芯片电源脚加 10μF 电容,检查 LDO 输出纹波是否超标 |
| 远程读数整体偏高 1~2℃ | 屏蔽层两端接地,形成地环路 | 改为单端接地,靠近控制器侧接地 |
| 远程读数偶尔瞬间跳变 | 外部电磁干扰耦合进探头线 | 硬件加 RC 滤波,软件加限幅滤波,线缆远离功率线 |
| 本地通道和远程通道读数相差恒定 | 没有做系统级校准 | 用恒温箱做两点校准,固件中线性补偿 |
| 低温段读数偏慢 | 探头没有完全接触被测表面 | 使用导热硅脂填充缝隙,加强探头固定 |
| 传感器应答成功但数据全为 0 | 可能是芯片未完成首次转换 | 上电后延时 50ms 再读取,或先写软件复位寄存器再读取 |
这里面最值得单独拿出来说的是 I2C 总线卡死问题。HVAC 现场干扰多,I2C 总线一旦被干扰到异常状态,主机的 I2C 外设可能一直等待从机释放总线,整个系统看起来就像“死机”了。我的处理方式是在每次读写循环里加超时重试,超时后检查 SCL 和 SDA 电平,如果 SCL 为高但 SDA 为低,说明总线上有设备在占用;如果 SCL 为低,说明总线被某个设备拉死。不管是哪种情况,手动把 SCL 拉低再拉高 9 个时钟周期,就能让卡死的从机释放总线。
固件里其实也需要一定的容错设计,不能因为一次 I2C 通信失败就让整个控制器复位。我的代码里连续通信失败超过 5 次才执行系统软复位,而这个 5 次的重试窗口大约是 1.5 秒,对 HVAC 控制来说不会造成感知得到的停顿。
6. 小结之外的一点私货:调试工具与长期可靠性
前面把项目从选型到联调基本讲完了,最后分享一点工具层面的经验和长期可靠性的观察。
调试这类双通道温度采集系统,我强烈建议准备一个逻辑分析仪。听上去很基础,但真的能救命。I2C 波形上你能直接看到 ACK/N ACK、总线冲突、时钟拉伸等情况,比在调试器里单步执行效率高得多。排查之前那个 I2C 总线卡死问题,我一开始在代码里加打印信息,完全看不出所以然,后来抓了一段波形,发现 SCL 在从机地址 ACK 后就一直处于低电平,才定位到是从机内部的读操作没结束导致的。
长期可靠性方面,这套系统连续运行了几个月,温度和湿度波动都经历过,发现远程探头连接端子是整个链路中最薄弱的环节。端子氧化会导致接触电阻变大,温度读数慢慢漂移。这个问题的规避方式也很简单:接线端子选带镀金的型号,装配时在端子上涂一层防氧化保护脂,理线的时候不要留太长的悬空段。凡是不想在中期维护里去更换探头的人,在这几个细节上多花几块钱、几分钟,都相当划算。
如果以后要继续扩展这个项目,我会优先做两件事:一是加入传感器断线自检,通过检测远程探头回路的开路/短路状态主动上报故障码,而不是让系统在探头异常时傻乎乎地显示一个错误温度;二是在 STM32C031C6 上再拉一路 PWM 输出,直接驱动风机的调速器,把温度采集到执行控制的链路彻底閉环。温度监测的硬件方案已经足够成熟,剩下的空间都在控制和诊断这块。这套路对做 HVAC 控制的人来说,才真正算得上从“采集”走到了“智能”。