做嵌入式温度采集,市面上方案很多,但真落到暖通空调(HVAC)这类场景,常常不是“读一个温度”那么简单。环境温度、盘管温度、风道温度、回风温度,往往得同时看两路甚至更多,而且探头常常不在主控板旁边。这次的项目就是围绕“一路本机环境温度 + 一路远端探头温度”来做的,核心器件是 STM32F303VC 这颗带 FPU 的 M4F 主控,搭配 PJ85718DM 双通道数字温度采集芯片。整套系统用在了某套风机盘管控制器的监控节点上,本地测控制器箱体内温度,远程通过专用探头测风道内温度,数据再通过串口转发到上位机。如果你正在做 HVAC 控制器、嵌入式环境监测或者对多通道温度采集精度有要求的项目,这篇文章里的选型思路、电路接法、固件滤波和校准方法,应该能帮你少走不少弯路。
1. 项目整体设计与方案选型
1.1 这类项目到底在解决什么问题
HVAC 温度监测和普通消费电子测温有个很大的区别:它不只是“显示温度”,而是要参与控制。比如风机盘管要根据回风温度和设定温度的偏差,决定风机转速和阀门开度;组合式空调箱要监测盘管进出口水温,判断换热效率。这些场景对温度的精度、实时性、长期稳定性都有要求,而且现场环境往往不那么友好——有强电干扰、有长线缆、有温度变化快的风道。
我这次项目的监测需求其实很清晰:两路温度。一路是控制器本身所在区域的空气温度,可以理解为“本地温度”;另一路是安装在风道或水管上的探头温度,距离主控板可能有几米到十几米,这就是“远程温度”。这两路温度需要同时采样,并且要求在 -20℃ 到 85℃ 范围内,误差控制在 ±0.5℃ 以内,分辨力达到 0.01℃。
如果按最传统的做法,用两个 NTC 热敏电阻接 MCU 的 ADC 采样,也不是不行,但你会遇到几个麻烦:NTC 的 B 值离散性导致每路都要单独标定;ADC 参考电压波动会影响精度;远端探头引线电阻会直接叠加到分压电路里,线一长误差就大;而且两路模拟信号还需要占用两个 ADC 通道。方案不是不能用,但批量生产和后期维护时的标定成本会让你很头疼。
1.2 主控为什么选 STM32F303VC
Stm32F303VC 这颗芯片在这个项目里其实有点“大材小用”的味道,但恰恰是这种冗余带来了开发上的省心。它是 Cortex-M4F 内核,主频 72MHz,带 FPU 和 DSP 指令。对温度采集来说,72MHz 算力绰绰有余,真正吸引我的是它内置的 16 位 ADC 和丰富的定时器资源。
温度采集本身用不了多少算力,但 HVAC 控制器通常不止测温,还要跑风机 PWM 控制、阀门 PID 调节、通信协议栈。F303 的 FPU 跑浮点 PID 非常顺手,不用像 M0/M3 那样手动做 Q 格式定点转换。而且它内置 7 个定时器,其中高级定时器还能输出互补 PWM 带死区,驱动风机或者比例积分阀门都方便。对于一套设备里既要测温又要做控制的场景,F303 一颗芯片全包了,省去了 AVR/M0 做控制、再挂外部 ADC 的麻烦。
另外 F303 的抗干扰能力和工作温度范围也适合 HVAC 现场。它的 VDD 支持 2.0V 到 3.6V,工业级版本工作温度 -40℃ 到 85℃,在控制柜这种半封闭环境里用完全没问题。
1.3 PJ85718DM 在这套系统里的角色
PJ85718DM 是一颗双通道数字温度采集前端芯片,这也是我选它的关键原因。它能同时处理两个测温通道:内部一个本地测温单元,直接测量芯片附近的环境温度;外部一个专用远程探头输入通道,通过引线连接远端热敏探头或二极管测温探头。
这种做法相当于把“模拟前端”这部分脏活累活都交给了专用芯片。本地温度不需要额外电路,芯片自己就能测;远程通道由芯片内部恒流源驱动外部探头,通过测量探头上压降来反推温度。因为恒流源驱动的方式不受引线电阻影响,所以远程探头线缆可以拉得比较长,这恰好解决了 HVAC 现场探头分布远的问题。
芯片和 MCU 之间走 I2C 接口,把两路温度直接以数字形式输出。我在代码里读到的直接就是二进制温度值,不用自己处理 ADC 转换、不用查表、不用做 NTC 非线性校正,这比用分立 NTC 方案省了至少一个星期的调试时间。
2. 硬件连接与 PCB 设计要点
2.1 最小系统与关键引脚分配
先说这套系统里 STM32F303VC 和 PJ85718DM 之间的连接。PJ85718DM 支持 I2C 通信,我把它挂到了 F303 的 I2C1 总线上,对应 PB6(SCL)和 PB7(SDA)这两个引脚。芯片的地址引脚通过硬件上拉设置为默认地址,具体地址值以你拿到的芯片手册为准,代码里做好宏定义就行。
除了 I2C 两根线,PJ85718DM 还有一个 ALERT 中断输出脚,我接到了 STM32 的 PB4 上并配置为外部中断输入。这个脚在温度越限时可以主动拉低通知 MCU,避免 MCU 一直轮询。实际使用中,我让 MCU 每 500ms 轮询一次温度寄存器,ALERT 的作用反而没那么大,但硬件上预留了总比没有好。
F303 这边还需要留出 UART 口做数据上报。我用的 USART1,PA9 发送、PA10 接收,通过板载的隔离收发芯片转成 RS485 信号,方便和上位机或者楼宇自控系统通信。之所以用 RS485 而不是 TTL 串口,是因为现场控制器之间距离可能几十米甚至上百米,差分信号抗干扰能力更强。
主控端引脚分配(以我实际项目为例): PB6 —— I2C1_SCL —— PJ85718DM_SCL PB7 —— I2C1_SDA —— PJ85718DM_SDA PB4 —— EXTI4 —— PJ85718DM_ALERT PA9 —— USART1_TX —— RS485_A(经收发器) PA10 —— USART1_RX —— RS485_B(经收发器)2.2 电源与去耦电路
I2C 通信对电源噪声比较敏感,尤其是温度采集这种需要稳定参考的场景。虽然 PJ85718DM 内部有基准源,不像模拟 ADC 那样直接受 VDD 纹波影响,但电源不稳仍然可能导致测量值跳变。我的做法是:MCU 的 3.3V 电源轨上放 10μF 钽电容并联 100nF 陶瓷电容,PJ85718DM 的 VDD 引脚旁边再单独加一个 1μF 陶瓷电容,尽量靠近芯片引脚放置。
I2C 总线的上拉电阻要特别注意。PJ85718DM 的 I2C 速率我配置为标准模式 100kHz,上拉电阻选了 4.7kΩ。如果总线电容比较大,比如远程探头线缆离主控板很远、走线很长导致寄生电容增加,可以把上拉电阻降到 2.2kΩ 试试,不然上升沿会变缓导致通信不稳定。
远程探头接口部分,我在 PCB 上做了一个两针插座,其中一根是探头信号线,一根是地线。信号线上串联了一个 100Ω 的电阻作为限流和抗干扰缓冲,同时在插座旁边放了一个 100pF 的电容到地,用于滤除高频噪声。这个 100pF 电容对正常测温几乎没有影响,但对抑制风道里电机产生的电磁干扰非常有效。
2.3 HVAC 场景下的布线与防护
做 HVAC 控制器的板子,布线上和普通消费电子最大的区别就是“远离干扰源”。我这套板子里有继电器驱动电路,继电器线圈通断瞬间会产生很大的反向电动势,如果温度采集电路离继电器太近,采样值就会出现毛刺。
我的处理方式是:PCB 分区布局,强电驱动区(继电器、可控硅)和弱电信号区(温度采集、通信)物理隔开,中间留出地线隔离带。PJ85718DM 和远程探头插座全部放在弱电信号区,I2C 走线尽量短且远离继电器线路。如果板子空间紧张,至少也要保证 I2C 信号线和继电器控制线不相邻、不平行走线。
对于外部接入的远程探头线缆,线缆屏蔽层在控制器端做单点接地,避免形成地环路。这里有个很多新人容易犯的错:把屏蔽层两端都接地。如果远端探头和控制器之间存在地电位差,两端接地反而会形成环流,干扰信号。我建议只在控制器端把屏蔽层接到大地或者系统地。
3. 固件架构与温度采集实现
3.1 初始化流程与驱动框架
固件这边,我用的是 STM32CubeMX 生成初始化代码,配合 HAL 库来写业务逻辑。工程主要模块包括:时钟初始化、I2C1 初始化、UART1 初始化、定时器初始化(用于 500ms 轮询节拍)和 PJ85718DM 驱动程序。
初始化顺序上有讲究。I2C 外设要先于 PJ85718DM 的用户代码运行,但芯片本身有个上电启动时间,手册里一般写着至少几百微秒到几毫秒。如果你一上电马上去读它的寄存器,很可能读回 0xFF 或者 NACK。我的做法是:在 MCU 完成时钟配置之后,加一个 10ms 的延时,再初始化 I2C,之后读取芯片 ID 寄存器做自检。如果 ID 不对,说明 I2C 通信有问题或者芯片没起来,这时候直接进入错误处理,而不是带着病跑下去。
/* PJ85718DM 初始化伪代码 */ uint8_t id = PJ85_ReadID(); if (id != PJ85718DM_EXPECTED_ID) { // 记录错误,亮故障灯,不启动温度采集 Error_Handler(); } // 配置芯片:设置测量周期、中断阈值 PJ85_Configure(PJ85_CONTINUOUS_10HZ, ALERT_ENABLE); PJ85_SetAlertThreshold(HIGH_TEMP_LIMIT, LOW_TEMP_LIMIT);3.2 本地温度读取与数据换算
PJ85718DM 的本地温度数据直接通过 I2C 读寄存器获得。芯片手册里一般会给出原始值与实际温度的换算关系。我用的这颗芯片,温度寄存器是 16 位二进制补码格式,分辨力 0.01℃,也就是说寄存器值需要除以 100 才是真正以摄氏度为单位的值。
读取步骤看起来简单,但有一个细节:I2C 读多字节时,如果芯片内部寄存器地址指针没有自动递增,你需要先发送“寄存器起始地址”,再重新发送“读命令”来连续读两个字节,否则读到的可能是同一寄存器的高低位一直在重复,温度值看起来就像在固定值附近跳动。这个问题我调试时遇到过,后来翻手册发现这块芯片支持自动递增,但我还是习惯性地在每次读取时都显式指定寄存器起始地址,这样移植到其他芯片也方便。
温度换算的浮点运算,在 F303 上可以放心交给 FPU 跑,不用优化成整数定点。实测读取并换算一次本地温度,加上 I2C 传输时间,总共不到 2ms,对 500ms 轮询周期来说毫无压力。
/* 读取本地温度寄存器并换算为摄氏度 */ float PJ85_ReadLocalTemp(void) { uint16_t raw = PJ85_ReadReg16(PJ85_REG_LOCAL_TEMP); int16_t signed_raw = (int16_t)raw; // 转为有符号数 float temp = (float)signed_raw / 100.0f; return temp; }3.3 远程通道采样与标定
远程温度通道稍微复杂一些。PJ85718DM 的远程探头本质上是个热敏器件,芯片内部通过恒定电流激励并测量探头上的电压变化来计算温度。好处是引线电阻不影响测量结果,坏处是它要求远程探头本身的特性匹配芯片内部的校正曲线,所以最好不要随便换不同型号的探头。
软件层面,远程通道的读取流程和本地通道一样,也是读两个字节的寄存器然后换算。但远程探头往往存在个体偏差,同一个温度下不同探头读回来的值可能差 0.5℃ 甚至 1℃。这个不能靠换探头解决,要在固件里加“单点偏移校准”。
偏移校准的思路:把远程探头和一个高精度的标准温度计放在同一个恒温环境里,待温度稳定后,读取 PJ85718DM 远程通道的测量值,计算与标准值的差值,把这个差值存储在 MCU 内部 Flash 的校准区。之后每次读出远程温度,就在软件里加上这个偏移量。如果后续能换探头重新校准,覆盖这个值就行。
3.4 滤波算法与防跳变处理
温度传感器读数偶尔会有毛刺,尤其是在继电器吸合、风机变速的瞬间。HVAC 现场电磁环境复杂,单靠硬件电容滤波不能完全解决问题,软件滤波一定要做。
我实际用的是“中值滤波 + 滑动平均”的组合。具体做法是:每次轮询读取温度后,把最新的 5 个原始采样值放入环形缓冲区,先去掉最大值和最小值,然后对剩下 3 个值取平均。这一套下来,单次突发干扰基本被抹掉了,而正常温度变化因为是缓慢的,滑动窗口不会造成明显的滞后。
对于温度变化速率,我还加了一个“斜率限制”:如果相邻两次滤波后的温度差超过 2℃/秒,就判定为异常跳变,丢弃本次值,继续用上一次的滤波输出。这样即使传感器短接或者断线导致读数剧烈变化,控制逻辑也不会误动作。
/* 温度数据滤波:中值 + 平均 + 斜率限制 */ float ApplyTempFilter(float new_sample, uint8_t channel) { // 1. 存入环形缓冲 ring_buf[channel][buf_index[channel]] = new_sample; buf_index[channel] = (buf_index[channel] + 1) % 5; // 2. 复制并排序,去掉最大最小 float tmp[5]; memcpy(tmp, ring_buf[channel], sizeof(tmp)); 简单排序(tmp, 5); float avg = (tmp[1] + tmp[2] + tmp[3]) / 3.0f; // 3. 斜率限制 if (fabsf(avg - last_output[channel]) > 2.0f) { return last_output[channel]; } last_output[channel] = avg; return avg; }3.5 本地与远程温度的上报
数据上报我用了 Modbus-RTU 协议,跑在 RS485 上。这样上位机、触摸屏或者楼宇自控系统可以直接通过标准 Modbus 寄存器读取两路温度,不需要自定义协议,现场集成会顺利很多。
在固件里实现一个精简的 Modbus 从站,只需要支持 03 功能码(读保持寄存器)和 06 功能码(写单个寄存器)。我用 0x0001 寄存器存本地温度(放大 100 倍后以整数存储),0x0002 存远程温度,0x0003 存设备状态字。为了让上位机读到以摄氏度为单位的小数,协议约定寄存器值是实际温度乘以 100,上位机再除回来。
/* Modbus 03 功能码响应数据组装 */ uint16_t reg01 = (uint16_t)(local_temp * 100.0f); uint16_t reg02 = (uint16_t)(remote_temp * 100.0f); // 响应帧: 设备地址 + 03 + 字节数 + 数据 + CRC164. 精度校准与 HVAC 联动逻辑
4.1 两点校准提升远程温度精度
如果项目对远程温度的精度要求超过 ±0.5℃,单点偏移校准就不够了,还得做两点校准,把增益误差也修正掉。
两点校准的操作方法:准备两个恒温条件,比如 0℃(冰水混合物)和 50℃(恒温水浴)。把远程探头分别放入两个环境中,稳定后读取 PJ85718DM 输出值,记录成 (raw_0, real_0) 和 (raw_50, real_50)。然后计算校准直线的斜率和截距:
slope = (real_50 - real_0) / (raw_50 - raw_0) offset = real_0 - slope * raw_0固件中查表或者直接用这两个系数线性变换。这样做批量校准时效率比较高,两个温区各等 10 分钟,一套流程下来大概半小时能完成一块板的校准。如果现场条件不允许做 0℃,也可以用室温(比如 25℃)和另一个较高温度做两点拟合,效果差别不大,关键是两个温区要拉开距离,比如至少相差 30℃,否则拟合出的斜率误差会被放大。
4.2 阈值告警与风机联动控制
温度采集只有配上控制逻辑才真正发挥价值。我这套系统的联动逻辑不复杂,但对稳定性要求很高。
本地温度作为控制器箱体温度保护,当本地温度超过 60℃ 时,强制降低输出功率并向上位机上报过热告警。远程温度作为风道温度的闭环反馈,风机的目标转速根据“设定温度 - 远程温度”的偏差来调节,偏差越大转速越高,偏差落在死区范围内则维持当前转速。
这套控制里最需要注意的是“防振荡”。风机转速如果对温度变化反应太灵敏,会导致系统来回振荡。我在控制代码里加的滞回比较:比如设定温度 25℃,远程温度低于 24℃ 时累计关断延时到 30 秒再降速,高于 26℃ 时延时 10 秒再提速。这个 2℃ 的滞回区间和延时,能有效避免因为气流扰动或温度小波动引起的频繁变速。
4.3 传感器断线和超温保护
HVAC 设备最怕传感器故障时控制器还在瞎跑。如果远程探头断线,PJ85718DM 远程通道会读回一个异常值,我判断的方法是:温度低于 -40℃ 或者高于 125℃,直接判定为传感器异常。
异常时的策略是:控制逻辑转向本地温度做参考,同时往上位机报故障码,并且让风机按安全转速运行。这样一来即使远程探头烧了、断了,设备不会停机,也不会满负荷乱转,给维护人员留出处理时间。这个策略是 HVAC 项目里比较重要的安全底线。
5. 常见问题与排查记录
我在这个项目调试过程中遇到过不少问题,挑几个有代表性的整理成表格,基本都是实际踩过的坑。
| 故障现象 | 可能原因 | 排查思路与解决办法 |
|---|---|---|
| I2C 通信偶尔失败,死机后恢复 | 总线被干扰毛刺触发锁死 | 上拉电阻减小到 2.2kΩ,I2C 线串联 22Ω 电阻,SCL/SDA 上并联 10pF 电容 |
| 远程温度整体偏高 1℃ 左右 | 远程探头个体偏差 | 用恒温槽做单点或两点校准,在固件里写入补偿偏移量 |
| 本地温度读数跳变 ±0.3℃ | 电源纹波过大或算法窗口太短 | 检查 PJ85718DM 电源去耦电容是否靠近引脚,滤波窗口从 3 次加长到 5 次 |
| 风道探头线缆超过 10 米后读数不稳定 | 线缆长度导致信号衰减和耦合噪声 | 改用屏蔽双绞线,控制器端单点接地,远程通道采样周期拉长到 1 秒 |
| 继电器动作瞬间温度值突变 | 电磁耦合干扰进入温度采集信号链路 | PCB 布局间隔开强弱电区域,软件斜率限制滤波处理,被干扰值直接丢弃 |
| 设备上电后第一次读温度为 0 | 芯片还没完成上电初始化 | 上电后延时 10ms 再读 ID 和温度,或者连续读两次丢弃第一次结果 |
5.1 远程探头线缆长度与屏蔽
远程探头的线缆长度,我在试验中验证过:用普通双绞线,长度在 5 米内基本不出问题;到 10 米开始出现偶发的读数漂移;15 米以上如果 PWM 风机还在旁边,读数就开始有明显跳动了。
HVAC 现场探头的安装位置往往不由我们决定,10 米以上的线缆很常见。我的建议是:优先选屏蔽双绞线,屏蔽层在控制器端可靠接地;如果探头线缆和设备动力线必须平行走,尽量保持至少 20cm 间距,无法避开时交叉走线而不是长距离平行。PJ85718DM 这类数字输出的芯片对线缆长度的容忍度已经比模拟 NTC 方案高很多,但硬件防线还是不能省。
5.2 I2C 总线锁死的处理
I2C 总线有个著名的坑:如果 SDA 线被某个从设备拉低,而主设备正好在发送时钟,总线就会进入死锁状态,SCL 继续跑但 SDA 永远低电平。这个在静电干扰或者线缆插拔瞬间特别容易发生。
模块化处理方式:初始化 I2C 之前,先把 PB6 和 PB7 配成 GPIO 输出模式,软件翻转 9 个时钟周期,让可能卡在错误状态的从设备复位,然后再切换为 I2C 复用功能。这个“软件复位总线”的步骤看起来土,但非常有效。我在项目里还开了 I2C 的时序超时中断,如果一次 I2C 传输超过 100ms 还没完成,就强制复位 I2C 外设并重新初始化,实践证明能显著降低长期运行的故障率。
5.3 校准结果的保存与恢复
校准值存 Flash 是个常规操作,但要注意写入次数限制。STM32F303VC 的 Flash 擦写寿命一般在 1 万次左右,如果每次设备掉电都写校准值,很快就会把 Flash 写穿。我的做法是:校准值只在生产测试阶段写入,正常运行时不写 Flash。设备上电后直接从 Flash 固定地址读取校准系数,如果校验和不对,就使用默认系数(偏移为 0、斜率为 1)。
另外,写 Flash 时要先擦除整个扇区,这个动作耗时且不能断电。如果程序在主循环里意外触发 Flash 擦写,轻则阻塞运行,重则导致程序跑飞。所以我建议把校准值的写入做成“显式命令触发”,只有上位机发送了特定写入指令,才进入 Flash 擦写流程,而不是自动保存。
我在实际调试这套系统时,最有感触的一点是:数字温度芯片 + M4F 主控的组合,把很多模拟电路的“玄学”问题变成了纯软件问题。以前用 NTC 做远程测温,线缆一长就要反复调分压电阻、查线阻,而现在大部分精度和稳定性问题,靠校准系数的修正和滤波算法的调整就能解决。这也让最终产品的生产标定流程变得可控。如果你手头正好有类似的 HVAC 或嵌入式监测需求,完全可以基于这两个器件直接起步,我上面提到的引脚分配、初始化时序和滤波陷阱,都是可以直接拿过去用的。按这个思路把两路温度数据跑稳定之后,之后再扩展多路探头、加无线通信模块,也都是顺理成章的事了。