做了这么多年嵌入式和暖空调系统,我越来越觉得“温度检测”这四个字被很多人低估了。刚开始接这类项目时,我也觉得无非就是读个传感器数值,可一旦把探头拉到风道里、把线缆沿着动力线走,或者把主控板塞进一个又闷又热的电控箱里,本地温度、远程温度就开始跟你玩各种花活。最近一个项目里,我换用PJ85718DM这颗数字温度传感器配合STM32L152ZD超低功耗单片机,把HVAC设备里的本地温度监测和远程温度监测一次性做利索了,过程中踩的坑不少,积累的经验也很有代表性。这篇内容不打算只讲个思路,而是把器件选型逻辑、硬件接法、固件代码、现场调试以及排查经验都摊开来讲,适合正在做楼宇自控、空调箱控制、机房环境监控、冷链设备或者恒温恒湿箱的朋友参考。尤其是远程探头的接线和误差处理这两块,建议仔细看完。
1. 项目整体设计与方案选型思路
1.1 一本地一远程,需求到底拆成了什么
先别急着选芯片,得把“本地温度”和“远程温度”各自对应的真实用途理清楚。在嵌入式系统里,本地温度通常指主控板附近环境温度,或者功率器件散热片的温度。它的用途有两个:一个是给系统做保护,比如监测到主控板温度超过85℃就降低PWM输出或者强制关机;另一个是给测量链路做补偿,比如仪表放大器受温度影响产生的漂移,需要在算法里修正。远程温度在HVAC系统里就更直接了,它指的是风道送风、回风、冷冻水进水管、冷凝器管壁或者室外环境温度,控制系统要用它来判断是否启动压缩机、风机要不要提速、是否需要除霜,以及节能模式下怎么调节输出。
这个需求真正落地的时候,我总结出四个具体难点。第一,远程探头和主控板通常不会挨得很近,1米到3米的引线距离在暖通设备里属于日常操作,线缆的电阻、寄生电容会直接影响模拟信号质量。第二,电控箱里往往有交流接触器、变频器、开关电源这些强干扰源,合闸瞬间和PWM切换瞬间都会在温度信号上叠加噪声。第三,设备不会永远全速运行,很多场景要求控制器具备低功耗待机能力,传感器和MCU都要能睡下去、定时醒来干活。第四,本地和远程两路温度要参与同一个控制策略,比如本地温度正常但远程温度过高,系统要能识别出探头所处环境异常,两路采样在时间上必须尽量同步,不能一路是现在值、另一路是几百毫秒前的旧值。
1.2 为什么选PJ85718DM加STM32L152ZD这套组合
选型阶段我其实先试过传统方案,热敏电阻加MCU内置ADC。优点只有一个,便宜,但缺点一箩筐。热敏电阻的B值每一批都有差异,非线性得靠查表或公式拟合,远程引线的电阻误差还要单独做补偿,为了校准一条产线得上好几台标准温度源,生产效率很低。后来换了数字温度传感器PJ85718DM,一下把模拟链路的工作量全收走了。它内置本地测温通道和远程测温通道,远程通道对外只需接一个二极管结型探头或特定热敏电阻,传感器内部完成激励、采样、A/D转换,主机通过I2C直接读寄存器。分辨率支持到12位,0.0625℃的量化精度在暖通控制里已经非常充足,还自带温度告警寄存器,超温时可以主动输出中断信号。
STM32L152ZD这边,选它不是因为性能堆料,而是三个点刚好都对得上。第一,它是一颗超低功耗Cortex-M3芯片,stop模式下的电流能压到微安级别,非常适合需要常待机、定时测量、意外唤醒的HVAC控制器。第二,它内部集成LCD驱动,面板需要显示温度和告警代码时,不用额外挂显示驱动芯片,省板子面积也省成本。第三,384KB的Flash和80KB的RAM足够跑一套Modbus协议栈加一段历史温度记录,项目后期想扩展功能不用换主控。之前有人问为什么不用更便宜的8位机,说到底还是因为系统里除了温度检测,还要管继电器输出、模拟量采集、通信协议,任务密度不低,M3这颗核干起来游刃有余,低功耗特性也不会拖后腿。
1.3 系统整体架构和数据流怎么组织
这里画不出流程图,我用文字讲清楚数据走向。PJ85718DM作为从机挂在I2C总线上,负责把两路温度原始值按一定周期转换好;STM32L152ZD作为主机,定时启动一次转换,然后读取本地温度寄存器和远程温度寄存器。读回来的原始值先做合法性判断,再经过滤波算法和线性校正,换算成以0.01℃为单位的整数温度。然后一路丢给LCD和串口用于显示和调试,另一路丢给控制逻辑,与设定的温度阈值进行比较,最终通过GPIO控制继电器、压缩机和风机。远程探头放在风道或管道上,本地通道就在主控板上,两个温度在同一次转换周期内完成,控制逻辑根据温差做判断时,不会出现“一个数据是现在的、一个数据是半秒前的”这种尴尬。
这种架构的另外一个好处是故障隔离清晰。传感器不响应I2C通信、远程探头断线、温度超出量程,都能通过寄存器值和通信返回值识别出来。模拟方案里那种“读到一个电压值但不知道到底准不准”的模糊状态,在这里基本不会出现。
2. 核心硬件电路设计与接口实现的细节
2.1 PJ85718DM的引脚、地址和上拉电路
具体到板子上,我把PJ85718DM接到STM32L152ZD的I2C1外设,SCL用PB6,SDA用PB7,这是这颗MCU上非常常用的硬件I2C引脚映射。芯片的地址引脚A0和A1直接接地,7位地址就是0x24,左移一位后在I2C通信里写0x48。如果总线上还要挂其他器件并且地址撞了,可以通过A0/A1的组合换地址,这个设计很灵活,硬件上只需要留两个0Ω电阻位。
SDA和SCL必须配上拉电阻。我实测下来,4.7kΩ是总线长度不长时的最佳起点,如果线缆稍微长一点或者总线上挂了多片器件,可以降到2.2kΩ。这里有个细节要提醒:上拉电阻越小,边沿越陡,信号完整性越好,但静态电流也越大,低功耗模式下这部分电流是白花掉的。所以不要盲目追求小电阻,够用就好。电源去耦方面,传感器电源引脚旁边我放了100nF陶瓷电容,再并了10μF钽电容。之前偷懒只放小电容,远程探头靠近变频器输出线时读数偶尔跳0.5℃,后来把大电容补上就稳定了。I2C总线看似是数字信号,但传感器内部A/D转换的基准对电源纹波还是敏感的,电源干净一点,测量结果就稳一点。
2.2 STM32L152ZD的时钟、低功耗与I2C外设配置
STM32L152ZD的时钟设计上,我给系统配了32.768kHz的LSE晶振,通过PLL倍频后让主频跑到32MHz。主频不用太高,HVAC控制算法里没有特别密集的浮点运算,32MHz跑Modbus和I2C都绰绰有余,而且高频段功耗上升明显,省电才是这颗芯片的主线任务。LSE晶振的负载电容选择要仔细看手册,我用的是6pF负载电容的晶振,反馈电阻取1MΩ级别,布局时晶振尽量靠近OSC32引脚,走线越短越好。遇到过不止一次晶振不起振导致I2C时序混乱的问题,排查起来特别费时间,所以layout阶段就要把晶振位置留好。
I2C外设直接配成标准模式100kbps。很多人觉得能跑400k就一定要开高速,但远程温度探头引出机箱后,线缆的寄生电容会限制通信速率,100k模式在这种场景下容错性最好。如果传感器离主控很近、线缆也短,再开快速模式不迟。低功耗策略上,系统进入停止模式前,先把I2C总线释放、关闭传感器连续转换,再通过一个RTC唤醒定时器定期醒来采样。如果远程探头出现超温告警,传感器会把告警引脚拉低,这个信号可以接到MCU的EXTI中断引脚,把MCU从停止模式唤醒去做故障处理,比定时轮询醒来更快,也更省电。
2.3 远程温度探头的连接方式和抗干扰处理
远程探头最常用的接法是用一颗三极管把基极和集电极短接,等效成一个二极管,然后通过双绞线连接到传感器的远程正负输入引脚。原理是这样的:二极管的PN结正向压降随温度变化大约-2mV/℃,传感器内部用恒流源激励这个结,测量压降变化,再减去本地通道的基准,就能算出来远端温度。这样设计的好处是探头本身不需要太复杂的电路,一个三极管加两根线就能工作,很适合把探头做进风道探头壳或盘管表面夹具里。
引线处理是重头戏。第一,必须用屏蔽双绞线,屏蔽层在传感器供电端单点接地。我之前直接用过普通双绞线,设备一出厂就发现远程读数在变频器启动时跳得厉害,换成屏蔽线后问题消失。第二,在传感器的远程输入引脚之间可以并联一个1nF的C0G电容,用来滤除射频噪声。有些现场干扰极其恶劣,还可以加一个小共模电感,但要注意不能破坏传感器的激励电流精度。第三,远程探头一旦引出机箱就等于给外部静电开了个口子,接口处要加TVS管。我吃过一次亏,现场操作人员热拔探头接口,芯片当时没坏,但过了几天远程读数整体偏低几度,最后查下来是内部模拟开关被静电打出了轻微损伤。加TVS之后再没出现过同类问题。
2.4 与HVAC强电回路的接口布局要点
HVAC控制板上温度传感器属于弱电信号,旁边就是220V继电器、交流接触器和开关电源,布局上一旦处理不好,传感器电路再精致都会被干扰。核心原则是地要分开再单点汇合。传感器和MCU的模拟地单独画一块,功率地的回流走另一边,然后在电源输入端或者主滤波电容附近汇合,不要造成大电流回流从传感器底下穿过的局面。I2C上拉电阻的电源建议直接从MCU的数字电源引脚取,而不是从继电器驱动电源或者电机驱动电源取,否则电压瞬变会直接耦合到I2C信号里。
告警输出这条线最好也单独规划。PJ85718DM的告警引脚通过一个中断控制器接到MCU的EXTI引脚,同时可以接到一个LED指示。现场维护人员看到LED快闪就知道是温度告警,看到慢闪就知道是通信故障,调试效率提高不少。我曾经偷懒把告警引脚只接MCU不接指示灯,结果设备报温度异常时,HVAC系统里又多了一个“手动万用表定位法”的排查故事。
3. 固件实现细节与关键代码
3.1 I2C初始化与传感器配置寄存器配置
固件整体用C语言基于HAL库开发,但我不打算把整个工程代码贴出来,那样没有意义,掌握关键初始化过程和配置位的含义更重要。I2C初始化和传感器配置的骨架大概如下:
void i2c1_init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; // 100k标准模式 hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0x30; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; HAL_I2C_Init(&hi2c1); } void pj85718_init(void) { uint8_t cfg = 0x00; // 写入配置寄存器:正常测量、12位分辨率、关断位清0 HAL_I2C_Mem_Write(&hi2c1, 0x48 << 1, 0x01, 1, &cfg, 1, 100); }配置寄存器里最需要注意的是关断位、转换位数和告警模式。低功耗场景下,MCU进入停止模式之前最好把传感器也设为关断或者单次转换模式,否则传感器一直在运行,电流白流。我习惯的做法是:正常工作时传感器连续转换,进入待机前写一次关断配置,然后靠RTC唤醒后再重新启动转换。还有一点,如果传感器配置成Fault Queue模式,即连续出现几次超温才触发告警,可以避免单次毛刺误触发,这个位对HVAC控制很有用。
3.2 温度原始值的读取与整数转换逻辑
读取两路温度寄存器的代码思路是统一的,一个寄存器就是一个16位数据,低4位在12位分辨率下通常无效,要灵活处理,具体以数据手册为准。我在代码里做了转换函数,内部统一用0.01℃为单位存储所有温度值:
static uint16_t pj85718_read_raw(uint8_t reg) { uint8_t buf[2]; HAL_I2C_Mem_Read(&hi2c1, 0x48 << 1, reg, 1, buf, 2, 100); return ((uint16_t)buf[0] << 8) | buf[1]; } static int16_t pj85718_to_mc(uint16_t raw) { int16_t val = (int16_t)(raw >> 4); // 12位原始值归一化 val = (int16_t)(val * 16); // 每个LSB代表0.0625℃,转成0.01℃单位 return val; }全部温度计算都用整数,不使用浮点库,这是嵌入式处理温度控制的一个非常实用的习惯。浮点库会把代码体积拉大、计算速度拖慢,而整型的0.01℃精度在暖通控制里完全够用。显示或者Modbus上报时,再把内部单位转成0.1℃或者带符号整数格式,转换也只是一次除法和取余操作。
3.3 滤波算法与两路温度的归一化处理
原始寄存器值直接拿去控制,一定会抖。温度信号本质上变化慢,但数字传感器偶尔会跳出一个毛刺值,可能是干扰脉冲转换到了采样值里。我用了滑动中值滤波加一阶低通的组合。中值滤波专门对付偶发脉冲,取5个值排序后拿中间值,一个孤立毛刺会被直接丢弃;一阶低通负责把随机噪声平滑掉。代码如下:
#define FILTER_LEN 5 static int16_t temp_median_filter(int16_t new_value) { static int16_t buf[FILTER_LEN]; static uint8_t idx = 0; int16_t tmp[FILTER_LEN]; buf[idx++] = new_value; if (idx == FILTER_LEN) idx = 0; memcpy(tmp, buf, sizeof(tmp)); // 简单的冒泡排序,取中间值 for (uint8_t i = 0; i < FILTER_LEN - 1; i++) { for (uint8_t j = i + 1; j < FILTER_LEN; j++) { if (tmp[j] < tmp[i]) { int16_t t = tmp[i]; tmp[i] = tmp[j]; tmp[j] = t; } } } return tmp[FILTER_LEN / 2]; } static int16_t temp_lpf(int16_t measured, int16_t last) { // shift右移实现一阶低通,系数约1/8 return last + ((measured - last) >> 3); }时间一致性处理上,我让本地和远程两个通道的转换由同一个PJ85718DM芯片完成,读取时连续读两个寄存器,中间间隔只有几个I2C时钟周期,所以温度值天然同步。这比分别在两个ADC通道采样要可靠得多。如果项目要求更高,可以通过配置寄存器开启同步转换模式,让芯片内部同时完成本地和远程采样。
3.4 告警、掉电保存与控制逻辑联动
告警阈值不要只靠传感器自带寄存器实现,固件里也要再算一遍,双保险。远程温度和本地温度可以定义一个温差告警。如果远程温度明显高于本地温度三五度以上,并且持续时间超过一定值,说明探头可能被加热器直烤或者安装位置出了问题;反过来,如果两个温度几乎一样且长时间不变,可能探头已经脱落。这类判据对后期维护非常有价值,不然售后只能拿着万用表去现场慢慢量。
温度数据记录也顺便做了。STM32L152ZD内置16KB的EEPROM,用来保存设备参数和最近十几分钟的温度历史绰绰有余。掉电前把当前温度值和告警标志写入EEPROM,重新上电后先读出上一状态,再决定设备是继续运行还是进入故障保护,这种设计在HVAC控制器里非常实用。以下是控制联动的一个片段:
void hvac_control_update(void) { int16_t t_local = temp_local_ready; // 0.01℃单位 int16_t t_remote = temp_remote_ready; if (t_remote >= T_COOL_START && t_local <= T_PROTECT_HIGH) { compressor_on(); } else if (t_remote <= T_COOL_STOP) { compressor_off(); } if (t_local >= T_OVER_HEAT_ALARM) { alarm_flag |= ALARM_OVER_HEAT; hvac_safe_shutdown(); } }这段代码很简单,但体现出两路温度共同参与决策的场景。远程温度负责正常控制循环,本地温度负责安全保护,两者互不干扰又相互制约。
4. 现场调试里最常遇到的四个坑
4.1 远程读数来回跳0.5℃,线缆背锅
这是一种很常见的“现场病”:设备通电,风机不转,远程温度读数却不停跳动,幅度在0.3到0.5℃左右。我用示波器观察传感器远程输入引脚,噪声幅度随变频器启动明显变大。排查下来,根因是远程探头线没有用屏蔽线,而且布线正好沿着220V动力电缆平行走了一米多。解决办法是换用屏蔽双绞线,屏蔽层在传感器端单点接地,再在远程输入引脚对之间并联一个1nF电容。整改后读数的跳动幅度降到了0.1℃以内。
这里顺带提一个测试技巧:现场调试时双手去碰远程探头的引线,如果数据跟着乱跳,说明接口处没有做ESD防护或者引脚处于不确定状态。信号正常的情况下,手碰不该有明显影响,如果有,就必须查防护和接线是否牢固。
4.2 本地温度比实际环境高2℃,这是自热加布局的双重问题
PJ85718DM芯片本身功耗不高,自热效应很小,但如果你把传感器放在电源模块旁边,或者PCB走线把大电流引到传感器底下,本地温度就会比环境温度高出一大截。我在恒温箱里做过对照实验,传感器旁边一颗功率电阻长期消耗1W以上热量,在空气流动性差的机箱里,本地读数和真实环境温度最多差了2.8℃。换成模拟前端方案也一样会被烤,不是芯片的锅。
解决思路有三个:布局上把传感器摆到板子边缘通风处,避开功率电感和散热片;不要在传感器正下方走I2C或PWM信号线,更不要走电源线;如果结构限制实在避不开,就在固件里做零偏修正,在相同环境温度下记录偏差并扣除。注意这种修正只在固定工况下可用,散热结构一改就得重新标定。
4.3 校准到底做几个温度点才够
HVAC应用不需要做全温区校准,除非你要测量零下40℃的室外冷媒管温度,那另说。对大多数0到60℃范围的设备,两个温度点校准足够:一个低点,一个高点。现场简单做法是0℃用冰水混合物,高温用50℃恒温水浴,把远程探头和标准温度计放一起,等读数完全稳定后记录差值,然后用两点算出斜率修正和零点偏移,写进固件的一行校正系数里。
还有一个细节经常被忽略:校准时探头必须和校准介质充分接触,不能隔着空气层读数。探头螺纹要拧紧,夹具要压牢,然后耐心等热平衡,至少10分钟再记录数据。我见过有人把探头往水里一放30秒就开始记数,校准结果自然不靠谱。
4.4 现场报障速查表
我把这段时间积累的典型故障整理成了一张表,方便大家直接对号入座。
| 现象 | 直接原因方向 | 处理建议 |
|---|---|---|
| 远程读数明显偏高且缓慢变化 | 探头接触不良、安装位置靠近热源 | 重新固定探头,检查导热硅脂是否干涸 |
| 本地读数比环境高且稳定 | 板级自热或布局问题 | 调整传感器摆放位置,做固件零偏修正 |
| 两路读数全为0xFFFF | I2C通信中断、地址不匹配 | 检查A0/A1引脚,确认设备地址寄存器 |
| 数据正常但控制乱动作 | 滤波不足或告警阈值过近 | 加大中值滤波长度,重新设定温度迟滞 |
| 睡眠唤醒后I2C卡死 | 从机锁死、总线保持低电平 | 释放总线,切换GPIO输出并发送9个时钟脉冲 |
| 远程读数比实际低好几度 | 远程探头内部结二极管损坏 | 更换探头,检查TVS是否击穿 |
这张表最大的价值在于提醒你,出现异常别第一时间换整板,先量信号、看寄存器、确认物理安装,把问题缩小到具体环节,再动手处理。
4.5 调试工器具和记录习惯
如果手头没有I2C逻辑分析仪,也至少要把传感器原始寄存器值通过串口打出来。只打印转换后的温度值有一个问题,你没法判断是温度真的变了,还是通信读到了错数据。打印原始16位值和两路计算后的温度值,比对起来才能定位问题。我在调试程序里加了一个调试命令,输入特定字符可以连续打印20帧原始数据,配合上位机画曲线,误差分析效率非常高。
日志记录的习惯也很重要。设备在现场跑的时候,定期把温度写进EEPROM并带上时间戳,后面出了问题回放日志,往往几分钟就能定位。有一次客户反馈设备早上开机温度异常,我回看日志发现凌晨那次超温告警时间正好和市电波动时间吻合,属于外部电源问题而非控制器故障,省了返厂检查的时间。
5. 后续扩展方向与个人建议
5.1 从温度监测延伸到设备健康诊断
温度数据在系统里不只是参与闭环控制的,它天然是设备健康度的一个指标。我后来在一个类似项目里增加了一段自诊断逻辑:每次设备启动时记录本地温度和远程温度的差值,并保存到EEPROM中。如果连续多次启动时这个差值都在变大,说明探头周围的导热结构可能老化,或者散热效率在下降,系统可以提前给出维护提示。这个功能改动不大,但对运维来说价值很高,故障能暴露在演变成停机之前。
还可以把温度变化率也考虑进来。比如远程温度在几秒内掉了几度,可能是风机突然停了导致风道温度变化,也可能是探头本身出了问题。把温度变化率的趋势纳入告警判断,可以减少不少误报。
5.2 给正在做类似项目的朋友几条实在建议
器件型号选完之后,先不要直接画板,用现有的开发板加杜邦线把最小系统搭起来,先熟悉传感器的寄存器操作,拿到稳定读数之后再设计PCB。我见过太多人板子画完才发现I2C地址因为上拉和地址引脚冲突读不出来,一层板子等于重来。
远程探头的线缆和连接器建议提前确定供应商并且固定封装,不要开发阶段用一种接口、量产又换另一种,接口一变,线缆感抗和防护参数全部要重测。另外,如果产品要往外卖到不同环境,建议预留校准参数存储区,最好支持通过串口命令在线重新标定,这样工厂生产时可以在线写入校准系数,不用每台都动固件。
最后说说我对这套方案的整体体会。PJ85718DM加STM32L152ZD并不算特别新奇的组合,但它把“传感器就近数字化”和“主控低功耗常待机”这两个核心需求刚好接在了一起。调试过程中真正帮到我的反而不是什么高深算法,而是把引线、屏蔽、去耦、滤波、校准这些基本功一项一项做扎实。搞嵌入式温度测量,有时候更像是在跟现场的噪声和热场打交道,能把物理层的干扰管住,上层逻辑写起来会轻松很多。