做嵌入式或者暖通空调控制器这块的朋友看到“PJ85718DM STM32F101ZG”这套组合应该第一时间就能反应过来这是一个非常典型的“温度采集前端芯片 主控MCU”的多点测温方案要解决的是HVAC设备里本地与远程温度同时监测的问题。本地温度指的是控制板附近的环境温度或者板卡自身温度远程温度指的是通过线缆延伸到压缩机壳体、管道表面、电机绕组这些位置的温度。这类项目在空调机组、冷库控制器、楼宇集控设备里非常常见难点从来不在把温度读出来而在于怎么读得准、读得稳并且在现场布线长、干扰大的真实工况下不丢点、不漂移。这篇文章就从这个标题出发把从需求拆解、硬件选型、软件初始化到现场调试踩坑的完整链路讲透。无论你是刚接触HVAC控制器的开发者还是已经在用多路温度监测想改进精度的老手都能从这里拿到可以直接落地的设计和排查思路。1. 先拆场景嵌入式与 HVAC 对“温度监测”的需求差在哪1.1 本地温度和远程温度本质上解决的是两个不同的问题很多第一次做这类项目的朋友会问测温度不就是把传感器放上去读一个数而已吗为什么要分本地和远程实际在嵌入式设备和HVAC系统里这两个词背后是完全不同的测量对象和工程约束。本地温度一般指设备主板上、控制盒内部或者MCU附近区域的温度。它离主控近线缆短干扰小甚至可以直接用芯片自带的片上温度传感器测量。对HVAC设备来说本地温度通常代表控制板的电气环境温度如果这部分温度过高要考虑散热和降额对嵌入式设备来说本地温度往往就是处理器、FPGA这类热点器件的结温或者壳温。远程温度就完全不一样了。在HVAC系统里远程温度探头通常放在制冷管路的吸气端、排气端、冷凝器进出口或者压缩机油池这些位置距离主控板可能有几米到十几米远。这些地方环境恶劣有油污、冷凝水、电机振动还和高压动力线近距离并行。在嵌入式设备里远程温度可能指功率器件附近、电池包内部或者机箱远端角落的温度。信号线长了之后线缆电阻、寄生电容和电磁耦合都会对测量造成影响所以远程测温必须用抗干扰能力强、适合长距离传输的传感方式。两者对比起来非常直观对比维度本地温度远程温度测量对象板级环境、芯片自身管路表面、绕组、功率热点传感器位置主控板附近或芯片内部线缆延伸到远端信号强度强噪声影响小弱易受干扰和压降影响线缆长度通常小于10cm常见1到10米精度要求一般5%以内即可满足逻辑判断需要较高稳定性涉及保护判断典型刷新频率可以很快几十毫秒通常会加滤波百毫秒级搞清楚这些差异之后后面选型、布线和校准的思路就顺了。1.2 为什么选“前端采集芯片 MCU”架构而不是 MCU 直采这是我在很多项目里反复和同事强调的一点涉及远程温度监测的HVAC设备不要试图用MCU内置ADC加运放来直接解算二极管信号。虽然MCU的片上ADC已经不错了但远程温度探头输出的信号本质上是PN结的正向压降变化温度系数只有大约-2mV/℃这个信号经过长线缆传输后叠加了共模干扰、线缆电阻压降用普通运放和ADC去采很难在整个工作温度范围内保证精度。PJ85718DM这类温度采集前端芯片把最麻烦的模拟部分全部包掉了。它内部集成了恒流源、多路通道切换、模数转换、温度计算逻辑和寄存器接口。外部只需要接一个二极管或者BJT做远程探头芯片就能在内部完成两个不同电流下的电压差测量把ΔVbe换算成温度值最后通过I2C或者SMBus总线输出一个纯数字结果。主控MCU这边做的事情就简单多了轮询寄存器、做阈值判断、跑显示和通信协议。这种“前端芯片负责模拟MCU负责逻辑”的分工好处非常明显模拟部分被隔离在单独一颗芯片里调试时可以独立测量和校准主控MCU哪怕是入门级的STM32F101ZG也完全跑得动不需要为高指标去选带高速ADC的昂贵器件。项目的开发周期、校准难度和物料成本都会下来而且后期如果更换探头类型只需要改前端芯片端的配置不必动MCU采样程序。2. 硬件选型和电路搭建围绕 PJ85718DM 把链路打通2.1 弄清芯片内部工作原理才知道寄存器在配置什么在动手画电路之前先把PJ85718DM这类芯片的工作机制讲清楚。做项目时不理解内部结构配置寄存器的时候很容易一头雾水。芯片内部一般分为三块本地测温单元、远程测温单元和数字接口单元。本地测温单元就是芯片自己封装里的PN结传感器上电即可读取不需要外部器件。远程测温单元提供若干对外引脚典型的是D和D-用来连接外部二极管或者晶体管的PN结。芯片内部会控制电流源在两个不同幅值的电流下分别测量正向压降然后利用PN结方程中ΔVbe与温度成线性的特性计算出绝对温度值。这个测量方式的关键在于PN结在制造时有一定的离散性芯片出厂时的校准参数通常对应某一类理想二极管尤其是晶体管集电极-基极短接后的等效PN结和芯片标定模型最接近。所以我在实际项目中更倾向于用BJT来做远程探头而不是随便拿一个普通二极管顶上去后者的误差在校准前会明显偏大。数字接口部分则承担所有寄存器读写和报警功能。芯片通过I2C或SMBus与外部MCU通信内部有本地温度寄存器、远程温度寄存器、配置寄存器和报警阈值寄存器。注意具体的寄存器编号、通道数量、分辨率步进值每颗芯片都可能不一样甚至不同批次都有差异动手前必须把原厂数据手册里寄存器映射表拿出来逐行核对。项目上最怕的就是拿着上一颗芯片的驱动代码直接套然后被一个偏移字段折腾一整天。2.2 PJ85718DM 与 STM32F101ZG 的电气连接要点STM32F101ZG是STM32F1系列里比较“老成”的一颗144引脚封装256KB Flash48KB RAM主频最高36MHz。这颗芯片不带USB不带高级定时器性能看上去不算亮眼但在做HVAC控制器这种场景里跑温度采集协议、处理RS485通信、驱动LCD显示和按键扫描绰绰有余。具体的连接方式我按实际项目里的典型接法列出来PJ85718DM引脚连接目标设计要点VDD3.3V电源就近放0.1uF和1uF去耦电容GND系统地单点接地避免和动力地混用SCLSTM32的PB6或任意GPIO接上拉电阻到3.3VSDASTM32的PB7或任意GPIO接上拉电阻到3.3VALERTSTM32的外部中断引脚根据需求决定是否使用D/D-远程温度探头接双绞线或屏蔽双绞线A0/A1等地址脚接GND或VDD配置I2C器件地址供电电压要认真看数据手册很多这类芯片可以工作在3V到5V之间但STM32F101ZG的GPIO是5V容忍的I2C上拉电阻拉到3.3V就行芯片电平正好匹配。如果前端芯片是5V供电而MCU是3.3V那么I2C线上务必确认是否要加电平转换别想当然直接连。我见过有同事把5V芯片的SDA直接进3.3V MCU引脚结果高电平超过GPIO耐受范围长期运行后引脚损坏的案例。I2C上拉电阻的选择也讲究。标准模式100kHz用4.7kΩ没问题总线不长的时候波形很好。如果追求400kHz快速模式上拉电阻可能要降到2.2kΩ左右具体要看总线上挂了多少器件、线缆有多长。上拉太弱上升沿太慢通信容易偶发失败上拉太强灌电流变大低电平可能压不到0.3V以下。我习惯的做法是先按4.7kΩ设计预留电阻位调试时用示波器看SCL和SDA的上升沿有需要再换成2.2kΩ。远程温度探头的接法要注意D和D-的极性。对BJT来说实际接法是让芯片激励电流从发射结流过也就是把集电极和基极短接后接D发射极接D-。如果接反了读数会变成一个离谱的大正数或者大负数看起来像是坏掉了其实是极性错了。STM32F101ZG这边我推荐用GPIO模拟I2C而不是用芯片内置的硬件I2C外设。原因很简单模拟I2C不受外设状态机限制时钟、时序自己控制调试起来直观改个引脚还能灵活应对PCB布局。对于PJ85718DM这种单器件的低速应用模拟I2C的开销可以忽略不计反而避免了硬件I2C在各种上拉和时钟配置下出现总线锁死的老问题。2.3 远程测温线缆的布置决定项目成败的细节远程温度通道的数采精度除了芯片本身很大程度取决于探头和主控之间的线缆。现场布线不规范再好的芯片读出来的数据也在漂。HVAC设备里尤其明显因为动力线、压缩机启动电流都会在空间里产生强干扰。线缆选择上优先用双绞线或者屏蔽双绞线。D和D-是一对差分信号双绞可以让两条线上感应的共模干扰趋向一致被芯片的差分测量机制抑制掉。线径不要太细至少24AWG以上线缆越长线径要越粗避免线阻引入误差。屏蔽层一端接地即可不要两端都接否则容易形成地环路低频干扰反而变大。走线时绝对不要把D和D-和220V动力线绑在同一个线槽里也不要平行走太长距离。我之前在项目现场处理过一个疑难问题远程温度读数总是周期性跳变排查了很久最后发现是安装师傅把温度探头线缆和接触器控制线捆在了一起接触器一吸合读数就跳。把线分开走并且套上金属软管之后问题彻底消失。另外HVAC现场环境潮湿探头接线端子要做防潮处理。裸露的接线端子一旦凝露绝缘电阻下降远程温度读数就会慢慢漂移。浸过导热硅脂的探头端子湿气的破坏力会小很多。3. 软件落地初始化、读取、换算与NTC补充方案3.1 上电后先别急着读温度按步骤初始化PJ85718DM这类芯片上电后会进入默认状态但默认状态未必符合项目需求。我一般把初始化分成三步等待稳定、写配置、校验应答。第一步是等待稳定。芯片上电后内部的基准源和ADC需要一点建立时间数据手册上通常会给。如果上电后立刻去读温度很容易读到一次跳变的异常值。我在代码里习惯加上200ms到500ms的延时宁可在开机阶段慢一点也别把错误数据当成初始温度拿去参与控制逻辑。第二步是写配置寄存器。这里包括使能哪些远程通道、选择转换精度、配置连续转换还是单次转换、设置报警阈值和滞回。比如选择12位分辨率温度步进就是0.0625℃选择9位分辨率步进就是0.5℃。精度高代表转换时间长精度低代表速度快具体看项目需要。HVAC场景下我通常选11位或者12位稳定且够用。第三步是读回校验。写完配置后把配置寄存器再读回来对比写入值和读回值是否一致。这一步能发现I2C总线是否有通信问题、芯片是否选对了地址。很多驱动调试卡在“写的A读回B”往往就是地址错误或者上拉电阻有问题。这里给一个基于模拟I2C的寄存器写入示例具体寄存器编号按实际芯片手册替换#define PJ_I2C_ADDR 0x4C #define REG_CONFIG 0x03 int pj_write_reg(uint8_t reg, uint8_t value) { i2c_start(); if (i2c_write_byte((PJ_I2C_ADDR 1) | 0) ! 0) { i2c_stop(); return -1; } if (i2c_write_byte(reg) ! 0) { i2c_stop(); return -1; } if (i2c_write_byte(value) ! 0) { i2c_stop(); return -1; } i2c_stop(); return 0; }注意如果芯片支持SMBus协议并用SMBus时序访问发送过程中每个字节后会有ACK应答驱动代码里要正确处理“收到NACK”的情况。NACK出现的位置不对往往说明从机地址错误、总线被占用或者芯片没有正常供电。3.2 温度读取与二进制补码换算初始化完成之后读温度就简单了。芯片上电默认连续转换模式下MCU只需要周期性地读两个字节。读回来的16位原始数据要经过一个固定的换算。以12位分辨率为例子高字节是符号和整数部分的高8位低字节的低4位是小数部分高4位无效或者为符号扩展。需要把低字节右移4位后再和高字节拼接然后做符号扩展再乘以0.0625得到实际温度。C语言代码可以这样写#define REG_LOCAL_TEMP_MSB 0x00 #define REG_LOCAL_TEMP_LSB 0x01 int16_t pj_read_raw_temp(void) { uint8_t msb pj_read_reg(REG_LOCAL_TEMP_MSB); uint8_t lsb pj_read_reg(REG_LOCAL_TEMP_LSB); int16_t raw (int16_t)((msb 8) | lsb); /* 12位分辨率时数据放在高12位低4位忽略 */ return raw 4; } float pj_read_temp_c(void) { int16_t raw pj_read_raw_temp(); /* 乘以0.0625即 1/16转换成分数温度 */ float temp raw * 0.0625f; return temp; }这里有个容易搞混的地方不同分辨率下LSB对应的温度步进不同。9位分辨率步进是0.5℃10位是0.25℃11位是0.125℃12位是0.0625℃。如果驱动里写死了0.0625但芯片实际配成了9位模式读出来的温度误差会非常明显。所以读取函数里的除法系数必须和配置寄存器里的分辨率保持一致。我在项目上的做法是在初始化函数里定义一个分辨率枚举换算系数根据枚举查表取得这样即使后期把分辨率从12位改成11位也不会漏改换算函数。3.3 本地温度也可以直接用 STM32 的 ADC 采 NTCPJ85718DM本身自带本地温度传感器这是最省事的选择。不过有些项目里本地测温点不一定在芯片附近而是想测板上某个特定位置的温度或者想省掉一路前端芯片通道。这种情况就可以用STM32F101ZG内置的12位ADC直接采样热敏电阻。NTC测温的电路通常是NTC串一个固定电阻分压ADC读中间点电压。温度换算用B值公式R R0 * exp(B * (1/T - 1/T0))换算成摄氏度的代码可以这样写float ntc_temp_c(uint32_t adc_value, uint32_t adc_max, float r_pull, float r0, float b_value, float t0_k) { float ratio (float)adc_value / (float)adc_max; float r_ntc r_pull * (1.0f - ratio) / ratio; float t_k 1.0f / (1.0f / t0_k (1.0f / b_value) * logf(r_ntc / r0)); return t_k - 273.15f; }NTC方案虽然便宜但是要注意分压电阻的精度和温漂。固定电阻建议用1%精度的低温漂电阻比如25ppm甚至更低。NTC本身要在允许的电流范围内工作分压电流过大NTC会自热温度读数会偏高并且和环境温度有关。一般让NTC上的功耗控制在1mW以下设计时算一下就行了。顺便说一句NTC测量精度上限不高能做到±1℃已经很好比PJ85718DM这类半导体前端差不少。如果项目里本地温度也要参与压缩机保护这类关键逻辑我仍然建议优先用前端芯片的本地理通道NTC只用在显示和辅助判断级别。4. 实测与常见问题排查从波形到现场4.1 远程温度读数偏大或偏小先找串联电阻问题远程二极管测温的物理基础是不同电流下PN结压差随温度变化的关系。芯片内部通过ΔVbe来算温度本意是消除PN结绝对压差带来的工艺误差。但如果D和D-的引线上存在不可忽略的串联电阻比如线缆太长、线径太细、端子接触不良串阻会在大电流和小电流两次测量中产生不同的压降最终被当成“温度变化”解算出来导致读数偏大或偏小。处理办法分几层。首先在设计阶段尽量缩短线缆长度、加粗线径把串阻压到几欧姆以内。如果项目确实需要比较长的线比如超过5米优先考虑使用具有串联电阻抵消功能的检测模式现在不少测温前端芯片都有这个功能原理是在大电流和小电流之间插入一个零电流测量状态测出串阻压降再扣除。如果芯片不支持自动抵消那就只能软件校准。常见的做法是让被测点处于一个已知温度的介质里比如冰水混合物或者恒温槽读出偏移量然后写进offset寄存器。更讲究一点可以在两个不同温度点标定计算出斜率误差并修正。HVAC项目里现场实测前我至少会做一个环境温度校准让探头和本地传感器在同环境里放置足够长的时间对比两者读数差把差值作为基础偏置记录下来。实际项目中还有一个经常踩的坑远程探头用了普通二极管而不是BJT的E-B结。普通二极管的工艺曲线和芯片出厂校准模型匹配度差温度系数虽然接近但绝对特性不一致。用BJT并把集电极和基极短接是和大多数远程温度芯片的校准条件一致的。我遇到过一次用户反馈远程温度比实际高8℃多换用合格BJT探头之后误差回到1℃以内。4.2 开路、短路与极性反接的快速识别远程温度探头的故障通常表现为三个典型读数接近正满量程、接近0或者完全乱跳。接近正满量程一般是开路接近0一般是短路乱跳外加数值明显不合理可能是极性接反或者接触不良。我习惯在软件里加一个“传感器健康检查”函数读到温度后先做合理性判断再进入后续逻辑。合理性判断包括三个条件温度是否在物理可行范围内、多次采样之间的变化率是否过快、原始值是否落在故障标志区。如果连续N次判断为故障才真正上报故障状态。这么做可以避免瞬时干扰导致的误报警。实际现场开路故障最常见。D/D-端子松动、线缆中间断裂、探头导线和端子根部断芯都会导致开路。远程温度线反复弯折后断芯很隐蔽万用表量静态阻值可能正常但设备运行中振动后时断时续。遇到这类问题我的排查经验是让设备处于振动运行状态用示波器抓D/D-两端电压看是否出现间歇丢失信号。4.3 HVAC 现场布线的几个实操教训这块最值得多说几句因为现场环境远比实验室复杂。第一个教训是屏蔽层接地方式。远程温度线用屏蔽双绞线屏蔽层要单端接地而且接地端尽量选在主控板的地。如果两端都接地在厂房地电位差大的地方会形成地环路电流反而会在屏蔽层上感应出干扰耦合进D/D-。我之前测试过两端接地时远程温度波动范围接近±3℃改单端接地后波动立刻降到±0.2℃以内。第二个教训是不要跟动力线共用穿线管。压缩机的启动电流可以达到几十安培甚至更大动力线和信号线在同一个金属管里平行走几米即使D/D-是差分测量也难以完全抑制强磁场耦合。规范做法是信号线单独穿管离动力线至少30厘米以上交叉时要垂直交叉不要平行。第三个教训是探头安装位置的热阻问题。远程传感器如果只是简单贴在管道表面没有做热接触处理测出来的是探头周围空气温度而不是管壁温度。正确做法是在管道表面清理干净后涂抹导热硅脂把探头压紧固定外面再包保温棉。这个环节往往直接影响整个系统的控制逻辑尤其当远程温度用来做压缩机排气温度保护时探头热阻太大温度响应滞后可能起不到保护作用。4.4 远程温度读数跳变的排查顺序做这类项目的过程中不可能不遇到远程温度读数跳变的问题。我总结了一套排查顺序按先软件后硬件、先简单后复杂的思路来确认读取频率。是不是读得太快芯片转换还没完成就读了寄存器检查数据手册里的转换时间MCU读取周期要比转换周期长。我之前犯过这毛病用10ms周期去读一个40ms转换周期的芯片读数一直在旧值和半新半旧的值之间跳。确认滤波设置。芯片内部的数字滤波如果没打开建议开启。用示波器看总线波形。确认SCL/SDA上是否有振铃上升沿是否过缓SDA低电平是否真的低于0.3V以下。波形异常往往是上拉电阻不对或者总线过长。断开远程探头用短导线短接D/D-测试。如果短接后读数稳定问题在探头或线缆如果还是跳问题在芯片侧或PCB布局。检查PCB布局。D/D-走线是否离开关电源的开关节点太近有没有跨过继电器驱动线PCB上的近距离耦合在实验室可能不明显到现场加上外部干扰源就可能暴露。我实际遇到过最诡异的一个跳变问题是D和D-的PCB走线中间穿过了一个继电器驱动走线继电器一动作读数跳一次。把远程温度线重新绕开那段之后问题消失。这种问题不用示波器看很难定位单靠逻辑分析I2C寄存器值也能找到一点规律比如跳变和某个IO动作在时间上高度相关。5. 从“读到温度”到“控住温度”后续逻辑怎么扩展5.1 报警与中断机制让芯片自己盯阈值如果PJ85718DM支持ALERT引脚一定要用起来。MCU轮询温度当然也可以但ALERT引脚能像硬件看门狗一样温度超出阈值时主动拉低触发MCU外部中断比纯轮询更及时也省CPU。配置报警阈值时要注意设置滞回区间。比如远程温度上限设置为80℃如果只设置一个80℃阈值那么温度在80℃附近轻微抖动时ALERT引脚会反复拉高拉低MCU中断会被频繁触发。正确的做法是把“报警阈值”和“恢复阈值”分开比如81℃触发报警79℃才解除报警。这2℃的滞回能极大减少误报警也保护继电器和压缩机不会频繁启停。中断处理代码里也建议只置一个事件标志然后回到主循环里做后续动作不要在中断服务函数里做长时间的温度换算、显示刷新或者通信发送。嵌入式实时系统的大忌就是在中断里干重活F101ZG主频不高一旦中断服务函数里跑了上千条指令系统实时性就毁了。5.2 温度联动控制从单一监测到闭环逻辑有了本地和远程温度数据整个系统的价值才真正体现出来。HVAC场景里最典型的应用是压缩机保护逻辑远程排气管温度过高说明系统可能缺冷媒或者冷凝压力异常控制器需要降频运行严重时直接停机。嵌入式场景里则是处理器散热管理本地温度过高时降频远程功率器件温度过高时关断输出。这里必须强调一点HVAC温度变化是一个惯性很大的过程传感器和被测介质之间的热阻决定了温度信号本身是滞后的。控制逻辑如果对温度变化反应过猛控制器就会来回震荡。我常用的处理方法是加滑动平均或者中值滤波。滑动平均简单中值滤波对尖峰干扰更有效。下面这个中值滤波的实现在UI和报警逻辑里都能用float temp_median_filter(float new_value, float *buffer, uint8_t size) { uint8_t i, j; float tmp[size]; for (i 1; i size; i) { buffer[i - 1] buffer[i]; } buffer[size - 1] new_value; for (i 0; i size; i) { tmp[i] buffer[i]; } for (i 0; i size - 1; i) { for (j 0; j size - 1 - i; j) { if (tmp[j] tmp[j 1]) { float t tmp[j]; tmp[j] tmp[j 1]; tmp[j 1] t; } } } return tmp[size / 2]; }滤波窗口大小要根据控制周期的需求调整窗口太大信号滞后明显窗口太小滤波效果不足。HVAC场景我一般用5点中值滤波控制周期1秒既滤掉偶发毛刺又不至于让排气管温度响应迟缓到影响保护。5.3 日志与数据存储大容量 Flash 的价值所在STM32F101ZG的256KB Flash在这个项目里除了放代码还能干一件很有用的事存储温度日志。HVAC设备在运行中如果发生了保护停机维护人员最想知道的是停机前几分钟的温度变化曲线。用片内Flash做环形日志存储以时间戳加温度值的方式按顺序写入掉电不丢失现场用显控面板翻查起来非常方便。写Flash时注意擦写次数限制不能频繁写同一个扇区要设计环形擦写或者磨损均衡。我们项目里设置为每5分钟记录一条温度数据256KB Flash划分一个扇区存储足够覆盖好几天的数据。软件层面加启动标志和校验和掉电写入中断后可以识别最后一帧是否完整避免重启后读到半截数据。这部分功能虽然不直接影响温度监测本身但对产品化程度有非常大的提升。做项目做到后面你会发现客户关心的不只是当前温度更关心历史数据和故障回溯。控制器能多存一些有效信息现场问题的定位成本就低得多。最后几个值得长期保持的习惯做这套PJ85718DM和STM32F101ZG的测温项目下来我个人体会最深的一点是温度监测系统最怕的从来不是“读不到”而是“读到错的值”。前者至少能触发报警后者却会让控制器在错误数据的基础上做出一连串错误动作。所以每次软件发布前我都会在硬件自检里保留一套探针故障识别逻辑宁可误报一个探头故障也不能让错误温度参与控制。还有一个很容易被忽略的点PCB设计时把D/D-端子放在板边并且做成可插拔的端子。这样现场安装方便调试阶段还能快速拔下探头来判断干扰是来自外部线缆还是来自板内。我在多个项目的售后处理中明显感觉到可插拔设计把排查时间从小时级缩短到分钟级。如果后续这个项目需要往更高要求扩展可以考虑在前面加无线远程温度节点把PJ85718DM只保留在主板上作为本地温度测量远端探头换成支持无线协议的独立采集模块。不过在多数HVAC设备里有线远程测温仍然是最经济、最稳定的方案把现有这套系统的校准和滤波做好远比增加通信协议更实际。