做HVAC控制器一年半,我最怕的就是“温度数据说起来都有,细看全是折腾”。空调箱的送风温度一个读数、回风温度一个读数,远程盘管那头的温度还得单独用长线再接一路传感器,三路数据在系统里打乱仗,校了这边漂那边。这个项目换了个思路:用PJ85718DM这颗数字温度传感器做主测温前端,配合STM32L031C6这颗超低功耗主控,把本地温度和远程温度放在同一条I2C总线上、同一套软件链路里统一处理,一套逻辑把HVAC和嵌入式里常见的两路温度监测需求一次收干净。如果你也在做空调控制、冷冻站末端、设备内部温升保护,或者只是想把一块远端的温度探针干净利落地接入系统,这篇内容应该能给你省不少事。
1. 方案拆解:本地与远程温度监测背后的问题
1.1 本地与远程测温的三个典型痛点
我把这个项目的关键词拆开之后,发现“本地和远程温度”这个说法看着简单,实际落在硬件上至少有三层麻烦。
第一是测温点分散但主控资源有限。HVAC末端设备往往要同时盯送风、回风、盘管、室外补偿温度,嵌入式设备也要分开机壳、板级、电池仓等位置。如果每一路都用NTC热敏电阻加ADC,硬件上就是一路一路地加运放、加分压电阻、加IO口,板子和成本都受不了。
第二是远程测温的长线问题。远程探头不可能像本地传感器一样贴在主控板上,总要拉一段线。模拟量的NTC方案里,线材本身的电阻、接插件氧化、现场变频器带来的干扰都会叠加到分压电桥里,温度误差非常难消除。我见过不少项目因为远程探头线径和长度变了,读数和现场水银温度计差出两三度,售后根本没法解释。
第三是批量校准成本。模拟测温方案每一路都要单独标定,生产线上逐台调不在正常不过,但HVAC产品出货量大,校得越多,单台工时越高,老板的脸色越差。
所以这个项目从需求出发,我会优先想一件事:有没有办法用数字输出的传感器,把本地和远程两路温度一起收回来,让软件去处理脏活,而不是让硬件和产线去扛。
1.2 PJ85718DM在方案里的角色定位
PJ85718DM在我这个方案里负责的是“测温前端”这个角色。它和普通单点数字温度传感器最大的区别在于:一颗芯片同时带本地温度通道和远程温度通道。本地通道直接测量传感器自身所在的PCB附近温度,适合放在主控板上监测环境;远程通道通过芯片上的D+、D-引脚外接一个二极管结构的测温探头,适合把探头布到风管、盘管或者设备远端。
这个双通道结构对这个项目非常重要。过去做HVAC,要么主控板放一颗、远端再放一颗,两颗传感器之间做同步对齐;要么干脆用模拟长线拉回来。现在一颗芯片就把两个测温点统一到一个I2C地址下,MCU读本地和远程温度只是在两个不同寄存器之间切换,不存在两套标定、两套驱动。远程通道输出的是数字量,线缆长度对精度的影响比模拟方案小一个数量级,只要线材规格合理,温度读数基本由二极管的特性决定。
另外这颗芯片还带有报警输出脚,可以在温度超过阈值时直接把报警脚拉低。这个功能对HVAC非常实用,后面我会专门说怎么用,它能把超温保护从MCU固件里彻底分离出来,固件跑飞了硬件保护链路还在。
1.3 STM32L031C6被选中的几个原因
说实话,这个项目如果只做温度采集,用一颗8位单片机也能跑。但我还是选了STM32L031C6,原因主要有三个。
第一是功耗。L031C6是Cortex-M0+内核,主频32MHz,片内Flash和RAM对于裸机采集任务绰绰有余。关键是它的Stop模式电流只有微安级别,配合RTC唤醒做周期采样,一个电池供电的无线温度采集节点能跑非常久。HVAC现场不一定都有220V取电条件,管道上很多地方就是用电池或者24V弱电供电,低功耗不是可选项,是硬指标。
第二是外设够用又不过剩。L0系列自带硬件I2C,虽然很多人吐槽ST的软件库复杂,但硬件I2C配合寄存器操作,稳定性比IO模拟高出不少。它还有足够的GPIO做报警检测、串口做Modbus上报、SPI接无线模块,整个方案不需要再加一颗协议转换芯片。
第三是开发和调试资源成熟。用这颗芯片的个人经验、代码片段、低功耗调试方法,社区里都能找到,踩坑成本低。如果一开始就选一颗冷门MCU,光是把时钟树和低功耗模式调明白,就可能卡掉一个星期。
2. 硬件设计与接口细节
2.1 传感器侧的关键引脚与接线方式
PJ85718DM这类数字温度传感器,引脚看起来不多,但真正影响系统成败的就集中在几个脚上。
首先是电源和地:VDD、GND。这个项目里我给传感器供3.3V,和STM32L031C6同源供电。VDD到GND之间一定要就近放0.1uF陶瓷电容,有条件再加一颗1uF或者10uF,用示波器看过电源纹波的人应该明白,数字传感器虽然抗干扰能力强,但不代表它可以在脏电源上正常工作。
其次是I2C总线:SDA、SCL。这两根线必须接上拉电阻,我实测下来3.3V系统里4.7k是最稳妥的起步值。如果你把I2C总线频率提到400kHz,或者总线上挂的设备比较多,可以换成2.2k。这个电阻不是随便选的,它要和总线上所有器件的输入电容、走线寄生电容一起决定上升沿时间,上拉太弱信号边缘钝,上拉太强功耗大还可能灌坏引脚。
然后是远程测温引脚D+和D-。D+接二极管结构的阳极,D-接阴极。实操里最常用的远程探头就是把一颗小信号三极管的基极和集电极短接起来,用发射极做另一端,这样封装成一个两脚二极管。你可以在探头端用普通二极管,也可以直接用带引脚的测温二极管。这块芯片对远程引脚的电容比较敏感,所以D+、D-的走线要尽量短,如果必须拉长线,最好用双绞线或者屏蔽线,并且把线上的分布电容控制在数据手册允许的范围内,否则远程通道的转换结果会偏大或者跳动。
最后是报警脚ALERT或THERM。这个脚通常是开漏输出,需要接一颗上拉电阻到3.3V。我第一版原理图就把这个脚省了,结果芯片报警时根本没有电平变化,算是走了弯路。
2.2 主控最小系统与传感器接口电路
STM32L031C6的最小系统不算复杂:3.3V供电、去耦电容、复位脚上拉、SWD调试接口、一个低速外部晶振给RTC用。剩下要仔细设计的就是和传感器之间的接口电路。
我的实际接线是这样的:PJ85718DM的SDA接MCU的I2C1数据脚,SCL接I2C1时钟脚,两根线各接一个4.7k电阻到3.3V。ALERT脚接MCU的一个普通GPIO并配置成外部中断输入,下降沿触发。D+和D-用一对双绞线引出到远程探头插座上,探头插座旁边不加额外电容,避免影响远程通道的测量精度。
这里有一个项目里必须提前决定的点:远程探头的距离。如果你的远程探头只有十厘米,那随便接;如果是要拉到几十厘米甚至一米的管道上,线材规格和屏蔽方式就要写进结构设计里。我会在测试章节具体讲距离对读数的影响。
2.3 硬件设计里容易踩的三个坑
这个项目打样第一版时,我亲手踩了三个坑,写出来给大家参考。
第一个坑是省上拉电阻。很多开发者觉得MCU内部有上拉,直接用内部上拉省两颗电阻。实测下来,内部上拉的阻值偏大,对于I2C这种需要开漏输出的总线来说,上升沿变缓,通信速率一快就偶发超时。尤其当总线上还有别的设备时,内部上拉更是撑不住。老老实实外部上拉是最省心的事。
第二个坑是远程引脚走线太随意。我第一次打样时把D+、D-从板子一侧绕了大半圈到另一侧的插座,结果远程通道的读数和本地通道差出1.2度。后来把走线改成短而直的差分路径,误差马上缩回0.3度以内。远程测温引脚对寄生电容和耦合噪声的敏感度,比我预想的严重得多。
第三个坑是报警脚没加上拉。开漏输出不给上拉,引脚永远拉不到高电平,MCU外部中断检测不到。这个很奇怪,数据手册的典型电路里明明有上拉,原理图阶段还是会漏。大家检查硬件时,把这颗上拉电阻写进Checklist里,比靠脑子记靠谱。
3. 软件驱动与测温逻辑实现
3.1 初始化与设备识别流程
软件层面,我先从初始化说起。STM32L031C6的I2C外设初始化分几步:打开GPIO时钟,把SDA和SCL配置成开漏复用输出;打开I2C外设时钟,配置时序参数;最后使能I2C。时钟频率这个项目里用100kHz标准模式,因为传感器对I2C速率没有特殊要求,100kHz抗干扰能力更强,现场走线稍长也不容易出错。
初始化完成之后,第一件正事不是读温度,而是先读设备的ID寄存器。PJ85718DM这类芯片一般都有设备ID或者生产ID寄存器,读出来和预期值比对,确认传感器在线、I2C地址正确、线缆没问题。这个步骤我强烈建议保留,它能帮你把“硬件没焊好”“地址配错”“总线上压根没设备”这三类问题在第一时间暴露出来。
这里给出一个基础读取函数,后面所有温度读取都基于它:
static int pj85718_read_reg(uint8_t reg, uint8_t *buf, uint8_t len) { I2C_HandleTypeDef *hi2c = &hi2c1; if (HAL_I2C_IsDeviceReady(hi2c, PJ85718_ADDR, 3, 100) != HAL_OK) { return -1; } if (HAL_I2C_Mem_Read(hi2c, PJ85718_ADDR, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 100) != HAL_OK) { return -2; } return 0; }设备识别的流程就是调用这个函数,读ID寄存器,判断返回值。如果读出来是0xFF,大概率是总线没设备或者地址不对;如果读出来是0x00,优先怀疑芯片供电或者复位时序。
3.2 寄存器配置与温度换算代码
这类双通道温度传感器的寄存器布局通常很规整,核心就那么几个。我把实际用到的寄存器列成了一张表,配置时照着填就行。
| 寄存器 | 作用 | 配置要点 |
|---|---|---|
| 配置寄存器 | 选择转换模式、分辨率 | 连续转换,12位分辨率 |
| 本地温度寄存器 | 存放本地通道温度结果 | 只读,16位格式 |
| 远程温度寄存器 | 存放远程通道温度结果 | 只读,16位格式 |
| 状态寄存器 | 指示温度转换完成、报警状态 | 轮询转换位或用报警脚中断 |
| 报警阈值寄存器 | 设置高/低温报警门限 | 按项目需求写入整数温度 |
温度换算这一步是软件里的核心。PJ85718DM在12位分辨率下,数字量存储在16位数据的高12位里,每1个LSB代表0.0625度。也就是说,从寄存器里读出来的16位原始值要先右移4位,把无效低4位丢掉,再乘以0.0625。为了避开浮点运算,我直接用整数算:(raw >> 4) * 625 / 100,效果等价于乘0.0625,不会有浮点精度问题。
完整读取函数可以写成这样:
typedef enum { TEMP_CH_LOCAL, TEMP_CH_REMOTE } temp_ch_t; static int32_t pj85718_read_temp(temp_ch_t ch) { uint8_t reg = (ch == TEMP_CH_LOCAL) ? REG_TEMP_LOCAL : REG_TEMP_REMOTE; uint8_t buf[2]; if (pj85718_read_reg(reg, buf, 2) != 0) { return TEMP_READ_ERROR; } uint16_t raw = ((uint16_t)buf[0] << 8) | buf[1]; int16_t signed_raw = (int16_t)raw; /* 右移4位后乘以0.0625,用整数运算避免浮点 */ int32_t temp = ((int32_t)(signed_raw >> 4)) * 625 / 100; return temp; }注意这里要把原始值转换成有符号的int16再右移,因为温度可能是负的。如果直接用uint16右移,零下温度就会算出一大堆离谱的正数,这在北方冬季的HVAC现场特别容易出现。
3.3 本地与远程温度读取的调度逻辑
温度读取不能是简单的“上电读一次就完”,因为HVAC场景里温度在缓慢变化,而远程探头又可能被风机启停影响。所以我在应用层做了一个简单的状态机,让采集逻辑按固定周期运转。
周期我设置为1秒。每个周期依次做四件事:读本地温度、读远程温度、做滤波、判断是否需要上报。
typedef enum { STEP_READ_LOCAL, STEP_READ_REMOTE, STEP_FILTER, STEP_REPORT, STEP_SLEEP } temp_step_t; void app_temperature_task(void) { switch (step) { case STEP_READ_LOCAL: local_raw = pj85718_read_temp(TEMP_CH_LOCAL); step = STEP_READ_REMOTE; break; case STEP_READ_REMOTE: remote_raw = pj85718_read_temp(TEMP_CH_REMOTE); step = STEP_FILTER; break; case STEP_FILTER: local_val = filter_push(&local_filter, local_raw); remote_val = filter_push(&remote_filter, remote_raw); step = STEP_REPORT; break; case STEP_REPORT: report_temperature(local_val, remote_val); step = STEP_SLEEP; break; case STEP_SLEEP: /* 等待下一个调度周期 */ step = STEP_READ_LOCAL; break; } }这个状态机的好处是,把I2C读取、滤波、上报拆开,后面想加报警判断、加统计、加日志,都只需要在对应状态里插代码,不影响其他环节。
3.4 在HVAC场景下的低功耗睡眠策略
如果你做的是电池供电的节点,那1秒一次连续读取还是太奢侈。L031C6的强项在低功耗模式,我实际项目里把周期拉长到10秒或者30秒,其余时间让MCU进入Stop模式,用RTC唤醒。
关键点在于进入Stop之前一定要把I2C总线释放干净。具体做法是把SDA和SCL重新配置成普通输入带上拉,或者直接禁用I2C外设,否则总线状态可能留在一个半半拉拉的信号里,一方面增加漏电,另一方面可能导致下次通信卡住。
void enter_stop_mode(void) { /* 释放I2C总线,避免进入低功耗后总线残留电平 */ HAL_I2C_DeInit(&hi2c1); HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); /* 唤醒后重新初始化时钟和I2C */ SystemClock_Config(); i2c_Init(); }再配合报警脚做事件唤醒:如果温度超过阈值,ALERT脚拉低触发EXTI中断,MCU不必等RTC周期,立刻醒来处理。这样既保证了低功耗,又不耽误超温保护。这个思路在HVAC领域很实用,因为现场的温度异常往往需要在几秒内响应,而日常巡检又不需要太高的上报频率。
4. 实测数据与校准处理
4.1 实验平台与采集数据记录
硬件和软件都完成后,我搭了一套简单的测试平台:一块STM32L031C6主控板,一块PJ85718DM传感器小板,远程探头用一颗小信号三极管封装成二极管接在D+、D-上,探头放进可调温的恒温槽。本地通道传感器小板也放进同一个恒温区域,让两个通道尽量处于同一环境温度下做对比。
下面这组数据是某次测试时的典型记录,为方便对比,我把本地和远程通道读数放在一起:
| 标准温度 | 本地通道读数 | 本地误差 | 远程通道读数 | 远程误差 |
|---|---|---|---|---|
| 10.0 | 10.2 | +0.2 | 10.4 | +0.4 |
| 25.0 | 25.1 | +0.1 | 25.3 | +0.3 |
| 40.0 | 39.7 | -0.3 | 40.2 | +0.2 |
| 60.0 | 60.3 | +0.3 | 60.6 | +0.6 |
| 85.0 | 84.7 | -0.3 | 85.2 | +0.2 |
注意远程通道的误差普遍比本地通道大一点,这是正常的,因为远程通道经过了一根引出线和探头本身的体热容,信号链路上多了几个环节。实际项目里只要误差控制在±0.5度以内,HVAC的控制逻辑完全够用。
4.2 误差来源分析和两点校准
很多人在软件里看到读数不对就急着改滤波参数,其实测温误差大多是系统性的,完全可以用校准解决。我这里先分析误差来源,再说校准方法。
远程通道误差的主要来源有三个:远程探头的二极管不是理想二极管,正向压降的温度特性每颗都有微小差异;D+、D-引线的线阻和分布电容会影响转换结果;还有探头与待测点之间的热阻导致读数滞后。前两个是系统误差,会在整个量程内按比例叠加。
系统误差用两点校准就能处理。我通常在10度和60度两个恒温点做基准,记下标准温度和实际读数,然后按线性公式修正:
校准后温度 = a × 原始读数 + b举个例子:10度时远程通道读9.4,60度时读61.2,那么:
a = (60 - 10) / (61.2 - 9.4) = 0.965 b = 10 - 0.965 × 9.4 = 0.929把这个a和b写进固件,每次读完远程温度后套一次线性修正,温飘和偏差基本上就能压回±0.2度以内。
提示:两点校准只适合在校准点所在的量程范围内。如果你把传感器用到150度或零下40度,校准曲线可能不再保持线性,需要重新评估或增加校准点。
另一个容易被忽略的细节是,校准要在整机状态下做,而不是单独校准传感器。因为主控板的发热、外壳的导热、探头线的实际长度都会影响最终读数。把校准流程放到整机最后一道测试工序里,比在软件里反复调偏移量有效得多。
5. 常见问题与排查技巧实录
5.1 I2C总线卡死与恢复
这个项目里最频繁遇到的问题就是I2C总线卡死,症状很典型:第一次通信正常,跑一段时间后读温度全部超时,SDA被拉低或者SCL在高频抖动。原因通常是在I2C读写过程中MCU收到复位或者看门狗触发,从机PJ85718DM还停在半截事务状态,总线就被它占住了。
遇到这种情况,我现在的处理顺序是:先把MCU的I2C外设复位,然后把SCL和SDA两个引脚临时切换成普通GPIO输出,手动在SCL上打出9个时钟脉冲,最后发一个STOP条件,让从机恢复初始状态,再把引脚切回复用模式重新初始化。这一套操作在固件里实现为一个软件复位函数,比断电重启更优雅,也不影响系统其余任务。
如果是现场设备大批量出现总线卡死,不要只在软件里打补丁。重点排查是不是I2C上拉电阻没装、总线走线太远、或者传感器供电在通信瞬间跌落。我从实践中得到的判断准则是:软件恢复是治标,把供电和上拉做规范才是治本。
5.2 远程温度跳动是怎么回事
第二个高频问题是远程温度的读数跳动,表现为某个采样周期突然跳出好几度,然后又恢复。这个问题在HVAC现场特别常见,因为远程探头线常常要穿过变频器柜、接触器区域,这些地方的电磁噪声非常凶。
硬件上我做了两层处理:一是把远程探头线换成屏蔽双绞线,屏蔽层在MCU板侧单端接地;二是在D+、D-进入传感器芯片前,不额外加电容,以免影响测量精度。软件上我加了滑动中位值滤波,窗口取5次采样,丢掉最大值和最小值再求平均。中位值滤波对付尖峰干扰比单纯平均更有效,因为它不受单个异常点影响。
还有个容易忽略的点:远程通道的温度变化率是有限的,物理世界不可能在0.2秒内从25度跳成30度。所以我在代码里加了变化率限幅,相邻两次有效读数差值超过1.5度就判定为无效数据,直接丢弃,不会进入滤波窗口。这个逻辑简单但极其好用,它几乎消灭了现场上报数值的毛刺。
5.3 供电噪声影响读数偏差
第三个问题平时不显眼,排查起来最费劲:远程温度和本地温度在变频器启动时同步偏移,误差能到0.5度以上。一开始我以为是远程引线问题,后来用示波器测传感器VDD引脚,发现变频器启动瞬间上面叠加了超过500mV的尖峰。
这个问题的根源不在I2C,而在传感器电源。PJ85718DM内部的测温基准对电源稳定性有要求,供电一跳,数字温度输出也跟着跳。处理办法是给传感器单独加一组滤波电容,我实际用了100nF陶瓷电容加10uF钽电容的组合,放在传感器和MCU板的电源入口;如果现场干扰更凶,可以再串一颗磁珠,构成LC滤波。处理好之后,同样的测试条件,温度读数纹波的峰峰值从0.5度降到了0.05度以内。
把这个问题记进来,是想提醒大家:数字传感器不是隔离传感器,电源处理不好,再好的芯片也会读出鬼温度。
为了方便排查,我把这个问题阶段碰到的高频故障整理成了一张速查表,大家可以对照处理。
| 症状 | 可能原因 | 处理手段 |
|---|---|---|
| 温度读数固定为0或满码 | I2C地址错误、传感器没供电 | 先读ID寄存器,再量电源 |
| 偶发超时、读不到数据 | 上拉电阻缺失或过大 | 检查SDA/SCL上拉,换成4.7k/2.2k |
| 远程温度跳变、毛刺 | 引线受电磁干扰 | 屏蔽双绞线,软件中值滤波,变化率限幅 |
| 温度随变频器启停漂移 | 传感器电源纹波大 | 加LC滤波,隔离供电 |
| 零下温度算出来是正数 | 符号位处理错误 | 原值先转int16再右移 |
6. 工程化落地与后续扩展
6.1 固件怎么分层才不越改越乱
温度采集这个功能看起来不大,但一旦接入到完整的HVAC控制程序里,很快就会变成一锅粥。我在这套项目里的经验是,固件必须按层拆开,哪怕是在裸机上跑也不例外。
我的分层大概是这样的:最底层是bsp层,只放I2C、GPIO、RTC这些板级初始化函数;第二层是PJ85718DM的芯片驱动,它只做器件通信和温度换算,对外暴露pj85718_read_temp和pj85718_init这两个接口;第三层是应用层,负责调度、滤波、报警判断、上报逻辑。应用层不关心传感器寄存器,驱动层不关心上报协议,谁改起来都不会牵一发动全身。
目录结构保持简单好维护:
project/ ├─ bsp/ │ ├─ bsp_i2c.c │ ├─ bsp_gpio.c │ └─ bsp_power.c ├─ driver/ │ └─ pj85718.c ├─ app/ │ ├─ app_temperature.c │ ├─ app_filter.c │ └─ app_report.c └─ main.c这样做还有一个好处:如果后面把PJ85718DM换成别的品牌的双通道传感器,只需要重写driver层的文件,应用层和bsp层几乎不用动。硬件升级和芯片替换的成本被降到最低。
6.2 量产测试与老化筛选
量产环境的测试和实验室调程序完全是两回事。实验室你盯着固化数据看一天没问题,产线上一块板子只有几秒钟的测试时间。我定的测试流程是这样的:先用治具给每块主控板供电,复位后读取传感器ID确认通信链路正常;然后在环境温度大概25度的条件下读本地温度,算偏差,偏差在±0.3度以内算合格;再接入标准远程探头,读远程温度,如果读值和标定值对不上,说明探头通道开路或者焊错。全部通过后,把每块板的温度偏移量写进片内Flash,作为出厂校准值。
这个流程里最容易翻车的是产线环境温度不稳定。很多工厂车间冬夏温差很大,必须用校准后的标准温度源做基准,而不是拿车间里的便宜温度计比对。另外,出厂校准值一定要写进Flash并加CRC校验,不然程序被重新烧写后校准系数丢了,售后又要重新跑一遍标定。
6.3 后续还能往哪些方向扩展
这套本地加远程测温的组合,扩展性比我想象中好。同一路I2C总线上如果需要加更多测温点,可以挂多颗PJ85718DM,用地址引脚A0、A1区分,一路总线最多挂四个,足够覆盖常见HVAC设备的探头数量。
报警脚可以做硬件级保护,不用MCU干预:把ALERT脚接到继电器或者三极管的控制端,温度超限时直接切断风机或者制冷设备。这样做的好处是MCU就算死机,保护链路还在工作。
如果做的是无线采集节点,可以把L031C6的休眠时间从1秒拉长到几十秒,平时电流极低,配上电池能跑很久。远程探头的校准系数、总线地址、上报周期都可以写进Flash,量产时统一灌入。整个系统做下来,从本地测温到远程探头、从低功耗采集到硬件报警,基本就是一个完整的HVAC温度监测子系统的雏形。
当前这套PJ85718DM加STM32L031C6的方案,硬件关键点已经全部落地,软件和校准也过了实测。如果让我重新设计一遍,我会从一开始就把远程探头线的布线规范和整机校准流程写进项目文档,而不是等到样机出来再临时补。测温这条链路硬件设计占七成,软件只是把硬件已经决定好的精度如实拿出来。把电源、上拉、远程引线、报警脚这四个环节按前面说的处理好,基本就能稳定扛完整个制冷季和采暖季。最后再提醒一句:远程探头的电缆在实验室测试时可以用短的,装到现场后一定要按现场实际长度重新做一次整机校准,这个细节能替你挡掉后面一大半的售后电话。