
1. 从一颗温度传感器说起为什么HVAC场景需要本地远程双路测温做过嵌入式暖通空调控制板的人都有一个共识温度采样看起来简单实际上是最容易翻车的一环。板上主控芯片自己发热、功率器件附近温度梯度大、远程探头走线长引入噪声任何一个环节没处理好最终都会变成用户抱怨空调忽冷忽热或者机组频繁启停。这次要聊的方案核心是用PJ85718DM这颗温度传感芯片配合STM32F437ZG主控搭一套能同时覆盖本地板载测温和远程探头测温的采集链路。PJ85718DM 是一颗支持多路远程测温通道的传感器件STM32F437ZG 则是 ST 家 F4 系列里资源比较充裕的一颗 Cortex-M4 芯片带 FPU、主频能跑到 180MHz做多通道温度采集、滤波、通信上报绰绰有余。为什么非要本地远程两路一起做因为 HVAC 应用里这两个温度代表的物理意义完全不同本地温度反映的是控制板自身工作环境温度用来做芯片结温补偿、板级过温保护、以及判断机箱内部散热是否正常。远程温度反映的是被控对象——比如出风口、回风口、水管表面、房间某处的真实温度这才是控制算法的输入。如果只测本地你测的是板子热不热不是房间冷不冷如果只测远程你又不知道自己的采样电路是不是因为板子发热而产生了漂移。两者结合才能既保证控制精度又保证系统自身的安全边界。这套方案适合谁参考做空调主控板、新风系统、地暖控制器、冷库温控、机房精密空调的嵌入式工程师尤其是那些正在从单点测温往多点测温远程补偿升级的团队。下面我会把选型逻辑、硬件连接、软件驱动、滤波策略、实测踩坑全部拆开讲。2. PJ85718DM 与 STM32F437ZG 的搭配逻辑选型不是拍脑袋2.1 为什么是 PJ85718DM 而不是普通 NTC 或数字温度芯片很多人第一反应是测温用 NTC 热敏电阻加个分压电路不就行了便宜又简单。这话对一半。NTC 在单点、近距离、精度要求不高的场合确实够用但放到 HVAC 的多路远程测温场景问题就来了。NTC 是模拟器件每一路都要占用一个 ADC 通道还要配分压电阻、滤波电容。你要测 4 个远程点就得 4 路 ADC 4 套模拟前端PCB 面积和物料成本都上去了。更麻烦的是 NTC 的一致性差每颗的 B 值都有偏差量产时要么逐颗校准要么接受精度损失。PJ85718DM 这类专用温度传感器件的思路不一样它把测温前端集成在芯片内部通过串行接口类似 I2C/SMBus 的时序跟主控通信一颗芯片就能挂多路远程测温通道。远程探头通常是二极管接法的三极管比如常见的 MMBT3904 接成二极管用利用半导体 PN 结正向压降与温度的线性关系来测温。这里的关键原理是PN 结在恒定电流下的正向压降大约以 -2mV/℃ 的斜率随温度变化。芯片内部用两路不同电流去激励同一个 PN 结得到两个压降相减之后就把工艺偏差抵消掉了剩下的差值正比于绝对温度。这就是所谓的ΔVbe 测温法也是这类远程温度传感器的核心。对比一下三种方案方案精度多路扩展远程能力成本适用场景NTC ADC中差每路占ADC一般易受干扰低单点、近距离数字温度芯片本地高差无中板载测温PJ85718DM 类远程传感器高好多通道强差分抗扰中多点远程本地选 PJ85718DM 的核心理由就是它同时解决了多路扩展和远程抗干扰两个问题而且精度比 NTC 高一个档次。2.2 STM32F437ZG 在这里扮演什么角色STM32F437ZG 是 F4 系列里的高配型号LQFP144 封装1MB Flash、256KB SRAM带硬件 FPU 和 DSP 指令。用它做温度采集主控主要看中三点第一I2C 外设资源充足。F437 有多个 I2C 接口可以一路挂 PJ85718DM另一路留给其他外设互不干扰。而且它的 I2C 支持时钟拉伸和仲裁跟传感器通信稳定性好。第二FPU 让滤波计算不费劲。温度采样最怕噪声必然要做滑动平均、一阶低通甚至卡尔曼滤波。没有 FPU 的芯片做浮点滤波会占用大量 CPUF437 带单精度 FPU这些计算几乎不占时间。第三通信接口全。HVAC 主控通常要往上位机或网关上报数据F437 自带多个 USART、CAN、USB本地测温做完直接就能通过 CAN 或串口发出去不用额外加通信芯片。一句话总结选型逻辑PJ85718DM 负责把温度变成数字STM32F437ZG 负责把数字变成可用的控制量并送出去。分工清晰各司其职。2.3 硬件连接中最容易忽略的三个细节接线图看起来简单但实际布板时有几个坑必须提前避开。第一个是远程探头的走线。远程二极管探头到芯片之间的走线最好用差分对形式走D 和 D- 尽量等长、靠近、包地。如果走成两根随便拉的线几十厘米下来引入的共模噪声足以让读数跳好几度。我见过一个案例探头线走了 1.5 米没做屏蔽读数在电机启动瞬间能跳 8℃后来改成双绞线加磁珠才压下去。第二个是本地测温的布局。PJ85718DM 自身的本地温度通道测的是芯片附近温度如果把它放在功率 MOS 或 LDO 旁边测出来的就是局部热点而不是环境温度。正确做法是让传感器远离热源或者明确知道自己在测哪个点的温度并做补偿。第三个是去耦电容。这类传感器对电源纹波敏感VCC 引脚旁边必须放 0.1μF 陶瓷电容最好再并一个 1μF。电容要尽量靠近引脚走线短而粗。别小看这一颗电容省掉它读数抖动会明显变大。3. 驱动层实现从 I2C 时序到温度寄存器解析3.1 先搞清楚 PJ85718DM 的寄存器地图在写代码之前必须先把芯片的寄存器结构摸清楚。PJ85718DM 这类远程温度传感器的寄存器通常分几类本地温度寄存器存本地通道的测温结果一般是 16 位高字节整数、低字节小数。远程温度寄存器每个远程通道一个格式类似。状态寄存器标志哪个通道超温、开路、短路。配置寄存器设置转换速率、滤波强度、报警阈值。阈值寄存器高低温报警门限每个通道独立设置。温度数据的格式要特别注意。这类芯片常见的是11 位或 12 位有效数据左对齐放在 16 位寄存器里低几位是标志位或保留位。比如读回来0x1A80高 8 位0x1A 26低字节0x80表示 0.5℃合起来就是 26.5℃。如果直接把 16 位当整数除结果就全错了。提示不同批次的传感器数据格式可能有细微差异动手前务必对照具体型号的数据手册确认位定义不要凭经验想当然。3.2 STM32 侧 I2C 驱动的关键配置用 STM32F437ZG 的 HAL 库驱动 I2C几个参数必须配对hi2c1.Instance I2C1; hi2c1.Init.ClockSpeed 100000; // 100kHz 标准模式长走线时更稳 hi2c1.Init.DutyCycle I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 0; hi2c1.Init.AddressingMode I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode I2C_DUALADDRESS_DISABLE; hi2c1.Init.GeneralCallMode I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode I2C_NOSTRETCH_DISABLE; // 允许时钟拉伸这里有两个经验点。时钟速度不要一上来就拉满。虽然芯片可能支持 400kHz但远程探头走线长的时候高速率下信号完整性变差容易通信失败。先用 100kHz 跑通稳定了再考虑提速。NoStretchMode 一定要关掉即允许时钟拉伸。这类传感器在转换期间可能会拉低 SCL 让主机等待如果主机不允许拉伸通信就会出错。我踩过一次这个坑现象是偶尔读不到数据查了半天才发现是时钟拉伸被禁用了。3.3 温度读取与换算的完整代码逻辑读取流程一般是启动转换 → 等待转换完成或定时轮询→ 读温度寄存器 → 换算成摄氏度。#define PJ85718_ADDR (0x48 1) // 7位地址左移 #define REG_LOCAL_TEMP 0x00 #define REG_REMOTE1_TEMP 0x01 #define REG_STATUS 0x02 float pj85718_read_temp(uint8_t reg) { uint8_t buf[2]; HAL_I2C_Mem_Read(hi2c1, PJ85718_ADDR, reg, I2C_MEMADD_SIZE_8BIT, buf, 2, 100); int16_t raw (int16_t)((buf[0] 8) | buf[1]); raw 5; // 低5位是标志位右移丢弃 float temp raw * 0.125f; // 每LSB代表0.125℃ return temp; }换算系数0.125是怎么来的如果有效数据是 11 位、量程覆盖 -64℃ 到 191℃那么 1LSB 256/2048 0.125℃。这个系数必须根据实际位宽算不能照抄。读多路的时候建议一次性把本地和所有远程通道都读出来存到一个结构体里方便后续统一滤波和上报typedef struct { float local; float remote[4]; uint8_t status; } TempFrame_t;3.4 通信异常的处理不能省I2C 通信失败是常态不是异常。探头没插、线松了、芯片没上电都会导致读失败。驱动里必须处理这几种情况超时HAL 的读函数带超时参数超时后要返回错误码不能死等。NACK从机没应答说明设备不在或地址错要记录并重试。数据合理性检查读回来的温度如果超出物理可能范围比如 -100℃ 或 200℃大概率是通信错误或探头开路要丢弃这一帧而不是拿去控制。我一般会在驱动层加一个简单的重试机制连续读 3 次取中间值如果 3 次都失败就上报故障。这样既过滤了偶发干扰又不会因为一次失败就误报。4. 让读数稳下来滤波策略与本地远程的协同补偿4.1 原始读数为什么一定会抖温度传感器的原始读数抖动来源有三类第一类是量化噪声。传感器分辨率有限比如 0.125℃那么真实温度在 25.0 到 25.125 之间时读数就在这两个值之间跳。第二类是电气噪声。远程探头走线像一根天线附近的开关电源、电机、继电器动作都会耦合进来。这种噪声幅度可能达到 1-2℃。第三类是热噪声和自热。芯片自身工作会发热转换过程也有微小功耗导致读数有缓慢漂移。三类噪声叠加原始读数抖个 0.5-2℃ 很正常。直接拿去控制执行机构就会频繁动作用户体验极差。4.2 滑动平均、一阶低通、中值滤波怎么选三种常用滤波各有适用场景滤波方式原理优点缺点适用滑动平均N 个采样求平均简单、平滑滞后、占内存缓变温度一阶低通y α·x (1-α)·y省内存、可调α 难调实时控制中值滤波取 N 个的中位数抗脉冲干扰强计算量大有突发噪声我的做法是组合使用先用中值滤波干掉突发脉冲比如继电器动作那一下的跳变再用一阶低通做平滑。中值窗口取 5低通系数 α 取 0.1-0.2。#define MEDIAN_N 5 #define ALPHA 0.15f float filter_temp(float new_sample, float *state) { static float buf[MEDIAN_N]; static uint8_t idx 0; // 中值滤波 buf[idx] new_sample; idx (idx 1) % MEDIAN_N; float sorted[MEDIAN_N]; memcpy(sorted, buf, sizeof(buf)); // 简单冒泡排序取中值 for (int i 0; i MEDIAN_N - 1; i) for (int j 0; j MEDIAN_N - 1 - i; j) if (sorted[j] sorted[j1]) { float t sorted[j]; sorted[j] sorted[j1]; sorted[j1] t; } float median sorted[MEDIAN_N / 2]; // 一阶低通 *state ALPHA * median (1 - ALPHA) * (*state); return *state; }α 怎么定经验公式是看采样周期和你想达到的响应时间。如果采样周期 100ms希望 2 秒内跟上真实温度变化α 大约取 0.05-0.1。α 越小越平滑但越滞后越大越灵敏但越抖。这个值必须实测调没有万能数字。4.3 本地温度如何补偿远程读数这是这套方案最有价值的部分。远程探头测的是目标点温度但探头本身和它的走线也会受环境温度影响。如果控制板附近温度变化大远程读数会跟着漂。补偿思路是用本地温度作为参考对远程读数做偏移修正。具体做法是在系统稳定时记录本地温度和远程读数的关系建立一个简单的线性补偿模型T_remote_corrected T_remote_raw - k * (T_local - T_local_ref)其中k是补偿系数T_local_ref是标定时的本地温度。k 的取值需要通过实验确定让本地温度变化观察远程读数漂移多少两者比值就是 k。注意补偿系数不是拍脑袋定的必须在实际硬件上标定。不同 PCB 布局、不同探头线长k 值都不一样。标定时让系统在恒温箱里跑几个温度点记录数据拟合出来。如果不想搞这么复杂还有个简化做法只在本地温度超过某个阈值时对远程读数做固定偏移。比如本地超过 60℃ 时远程读数减 0.5℃。虽然粗糙但比不补偿强。4.4 采样周期的取舍采样周期不是越快越好。太快了噪声大、功耗高太慢了响应迟钝。HVAC 场景里温度本身变化很慢房间温度变化的时间常数通常是分钟级。所以采样周期取250ms 到 1s完全够用。我一般取 500ms配合上面的滤波读数既稳又不会明显滞后。如果要做超温保护可以单独设一个快速通道本地温度用 100ms 采样一旦超过门限立即触发保护不等滤波。安全和舒适要分开处理。5. 实测踩坑记录那些数据手册不会告诉你的事5.1 探头开路时读数为什么会看起来正常这是最坑的一个问题。远程二极管探头如果没接好开路芯片读回来的可能不是明显的错误值而是一个看起来合理的温度比如 60℃ 或 85℃。因为开路时 PN 结没有电流芯片内部的 ΔVbe 检测会得到一个异常但有限的电压换算出来就是个假温度。如果不做开路检测系统会拿这个假温度去控制后果很严重。解决办法是读状态寄存器这类芯片通常有专门的 OPEN 标志位。每次读温度时顺便读状态发现开路标志就上报故障不要用这个通道的数据。我见过一个项目探头线被老鼠咬断了系统还在正常显示 65℃直到用户投诉才发现。状态寄存器一定要用起来。5.2 多路通道之间的串扰PJ85718DM 支持多路远程通道但通道之间不是完全隔离的。如果某一路探头走线特别长、噪声特别大可能通过芯片内部影响其他通道的读数。实测发现当一路探头线长超过 2 米且没屏蔽时相邻通道读数会出现 0.3-0.5℃ 的同步波动。解决办法有两个一是缩短走线或加屏蔽二是分时采样不要同时转换所有通道一路一路来减少相互影响。5.3 上电初期的读数不可信芯片刚上电时内部电路还没稳定前几次读数往往偏差很大。我一般会在初始化后丢弃前 3-5 次采样等读数稳定了再进入正常流程。另外如果系统刚从冷库拿出来上电芯片温度和实际环境温度差异大也需要一段稳定时间。这个时间通常是几十秒到几分钟取决于热质量。5.4 电源波动对精度的影响有一次测试发现系统在电机启动瞬间温度读数会跳 1-2℃。查下来是电源被拉低传感器供电不稳导致转换基准漂移。解决方法是给传感器单独加一级 LDO或者在电源入口加大电容储能。如果成本允许用独立的低噪声 LDO 给模拟部分供电数字部分另走一路效果最好。5.5 温度换算的符号问题负温度处理容易出错。如果温度寄存器是补码格式直接当无符号数处理-10℃ 会被算成 246℃。换算前一定要确认数据格式该做符号扩展就做符号扩展。// 假设 11 位有效数据需要符号扩展 int16_t raw (buf[0] 8) | buf[1]; raw 5; if (raw 0x0400) { // 第11位是符号位 raw | 0xF800; // 扩展符号位 } float temp raw * 0.125f;这个细节数据手册里往往一笔带过但实际调试时能卡你半天。6. 从采集到上报把温度数据用起来6.1 数据结构设计要面向后续使用采集到的温度不能只是几个 float 变量要设计成方便上报和记录的结构。我通常用一个帧结构包含时间戳、各通道温度、状态标志、校验typedef struct { uint32_t timestamp_ms; float local_temp; float remote_temp[4]; uint8_t channel_status; // 每位对应一个通道 uint16_t crc; } TempReport_t;这样一帧数据既能本地记录又能直接打包通过 CAN 或串口发出去接收端解析也方便。6.2 上报周期与变化触发结合如果固定周期上报数据量大且大部分是重复的。更好的做法是周期上报 变化触发结合正常情况下每 5 秒上报一次如果任一通道温度变化超过 0.5℃立即上报一次。这样既保证了数据连续性又能在温度快速变化时及时反映还省了带宽。6.3 超温保护的独立通道控制用的温度和保护用的温度要分开处理。控制用的走滤波追求平滑保护用的走原始值或轻滤波追求快速。保护逻辑建议这样设计本地温度超过 85℃立即降频或关断功率输出。远程温度超过设定上限触发报警但不一定立即停机看具体应用。任一通道开路或通信失败上报故障控制算法切换到安全默认值。提示保护阈值要留足余量。传感器本身有误差滤波有滞后环境有极端情况阈值定得太贴近实际工作点容易误触发。6.4 长期运行的漂移与自检系统跑几个月后传感器可能产生缓慢漂移。可以设计一个简单的自检机制在系统空闲时比如夜间让已知发热源工作一下观察本地温度是否按预期上升以此判断传感器是否还正常。这个自检不需要很精确只要能发现完全没反应或反应异常大这类明显故障就够了。7. 一些实际调试中的个人体会这套 PJ85718DM STM32F437ZG 的方案我从打样到稳定运行大概花了两周时间其中大部分时间不是在写代码而是在跟噪声和异常读数作斗争。分享几个印象最深的体会。第一硬件问题永远优先于软件问题。读数不稳的时候先别急着改滤波算法拿示波器看看电源和信号线十有八九是硬件的问题。我一开始花了两天调滤波参数最后发现是去耦电容焊错了位置。第二数据手册要读三遍。第一遍看个大概第二遍对照寄存器写驱动第三遍在出问题时回去查细节。很多坑其实手册里都写了只是第一遍看的时候没在意。第三留好调试接口。板子上一定要留出串口或 SWD 接口能实时打印原始读数、滤波后读数、状态寄存器。没有这些调试就是盲人摸象。第四别迷信精度指标。手册上写的 ±1℃ 是在理想条件下的实际系统里能稳定在 ±2℃ 以内就不错了。控制算法要按实际精度设计不要按手册指标设计。最后说一个实用小技巧如果远程探头走线实在避不开干扰可以在探头两端并联一个小电容比如 100pF能明显改善高频噪声。但电容不能太大否则会影响测温响应速度100pF 到 1nF 之间比较合适具体值实测确定。