1. 项目定位:HVAC 温控系统里,温度测不准意味着什么
搞过 HVAC 控制的人都知道,温度这一环一旦不准,后面所有的 PID 参数、压缩机启停逻辑、风机变频策略全都会跟着跑偏。今年我在一个楼宇空调机组的嵌入式项目里,要同时监测两类温度:一类是控制器电路板自身所在的环境温度,另一类是风管、水管和冷凝器表面的实际介质温度,最后还要把这些数据通过 Modbus 上报给楼宇自控系统。经过一番选型,我最终确定了 PJ85718DM 这颗双通道数字温度传感器加 STM32F765ZI 主控的方案。整条链路从寄存器读取、温度换算、滤波到协议对接,踩坑不少,这篇文章把设计思路、硬件细节和可用代码完整整理出来,方便正在做类似嵌入式和 HVAC 温度监测的朋友直接参考。
1.1 本地通道与远程通道分别解决什么问题
先理清一个概念。所谓"本地温度",指的是传感器芯片自身所在位置的温度,负责反映控制器安装环境的空气温度;"远程温度"则是通过一根细导线把测温二极管放在几十厘米甚至更远的目标点上,测到的是那个点的真实介质温度。在 HVAC 场景里这两者缺一不可:控制器板卡通常放在电气柜里,四周有变压器、继电器和功率驱动器,柜内环境温度往往比空调回风温度高出不少,如果拿柜内温度当作风管温度来做控制,系统一定会误动作;而远程通道恰好可以把测量点从高温柜内"挪"出来,直接放到风管、水管或设备外壳上,用传感器本身的本地通道负责监测柜内散热状况,逻辑上就分得很清楚。
这套双通道思路在具体工程里能少走很多弯路。我之前做过一版单传感器方案,为了测远端温度,把一整块探头小板的信号线从柜内拉到风管旁,结果导线上的压降和干扰让读数漂了将近 2℃,而且走线越长越不准。改用 PJ85718DM 这种带远程二极管接口的芯片后,远程探头只是两根线加一个小晶体管,体积小、成本低、抗干扰能力强,整体可靠性和精度都明显上了一个台阶。
1.2 为什么不是拉长探头而是双通道芯片
很多刚开始接触暖通控制的人会问:既然要测远端温度,直接把一个普通传感器放在远端,再通过 I2C 线把数据传回主控不就行了?这个思路本身没错,但在 HVAC 现场会碰到两个现实问题:一是工程上远程测温点往往在一个 EMI 很复杂的区域,旁边就是接触器、变频器甚至压缩机启动电流的强磁场,距离一长,I2C 这种高速数字总线非常容易被干扰;二是增加远端传感器意味着多了整条电源线和通信线,故障点也随之增加。
双通道测温芯片的远程机制完全不同。芯片内部把远端测温二极管接成一个小电流偏置回路,利用二极管正向压降随温度变化的物理特性来测温,传感器通过测量流过晶体管的电流比和电压差,把远端 PN 结的温度算出来。远端点只需要一个二极管结,不需要自己的电源、晶振或逻辑电路,整个测量链路的抗干扰能力比数字总线更可靠,线路损耗也更容易补偿。对 HVAC 这种需要长期连续运行、故障率必须压低的场合,这个差别很关键。
1.3 主控为什么选 STM32F765ZI
主控这颗 STM32F765ZI 是 Cortex-M7 内核,主频最高能到 216MHz,2MB Flash、512KB SRAM,性能上绰绰有余。选它的主要原因倒不是单纯求快,而是项目里除了温度采集,还有压缩机/风机的多路 PID 控制、本地 OLED 显示、SD 卡历史记录以及 Modbus 上位机通信,这些任务叠加在一起,普通 Cortex-M3 级别的芯片在采样周期和控制周期拉不开距离的时候会比较吃力。M7 内核带双精度浮点单元,跑浮点 PID 和温度滤波算法几乎不占什么 CPU 资源,这给后续加功能留了很大的余量。
另外 STM32F765ZI 的外设资源非常齐全,FMC 可以扩展外部存储器,多路 UART、CAN、SPI、I2C 都有,后期如果要接以太网做楼宇组网,也不用换平台。温度采集软件上,HAL 库对 I2C、UART 这些外设的封装已经足够成熟,能让开发重心集中在应用逻辑而非寄存器操作上,对项目周期紧张的情况非常友好。
2. PJ85718DM 的测温原理:双通道背后的半导体机制
不少人在调这类温度传感器时会犯一个错:只关心读到的数字,不关心数字是怎么算出来的。等现场读数对不上参考温度时,就不知道从哪里排查了。所以这里先把 PJ85718DM 这类本地/远程双通道传感器的测温原理讲透。
2.1 本地测温:芯片内部的 PN 结电压
本地通道的原理比较直观。芯片内部集成了一组经过校准的二极管,温度升高时 PN 结的正向压降 Vbe 会下降,大约每升高 1℃ 减少 2mV 左右。传感器先通过一个精密电流源给二极管一个恒定偏置电流,再把 Vbe 的变化量经过 ADC 量化后换算成温度值。
需要注意,这类片上二极管的绝对精度受工艺偏差影响,所以芯片出厂时都会做校准,把校准系数烧录到 OTP 寄存器里。使用的时候只要按数据手册里的默认配置读取就行,不需要自己做逐片标定。本地通道的典型精度在 ±0.5℃ 到 ±1℃ 之间,这对于监测电气柜环境温度完全够用。
本地通道最大的威胁是自热效应。传感器芯片自身工作时会有功耗,芯片封装内部的温度会比实际环境温度稍高。如果 PCB 布局里传感器紧贴着大功率 MOS 管或变压器,那本地读数甚至会偏高好几摄氏度。因此本地通道读出来的温度更准确的理解是"PCB 局部热点的温度",而不是"整个柜内的均匀温度"。工程上我一般把正偏差当成一个固定系统偏置来处理,或者直接以通风口附近的实测值作为基准做单点校准。
2.2 远程测温:外部二极管/晶体管怎么接
远程通道的物理基础同样是 PN 结。工程实践里最常见的做法是用一颗分立 NPN 晶体管,把基极和集电极短接,这样从 D+ 到 D- 看进去就是单个 PN 结,等效于一个二极管。典型型号是 2N3904,SMD 封装可以用 MMBT3904。如果你的应用里只有 PNP 晶体管库存(比如 2N3906),接法要对调:基极和集电极短接后接 D-,发射极接 D+。
之所以强调用晶体管而不是普通的普通二极管,是因为这类传感器的远程测温算法内部要测量两个不同电流 I1、I2 下的 Vbe 差值 ΔVbe,并通过 ΔVbe = (kT/q)·ln(N) 的关系计算温度。实际效应中晶体管的理想因子 β 对精度影响很大,而经过挑选的正品 2N3904 类晶体管一致性较好,容易满足数据手册里对 β 的要求。普通整流二极管的正向特性曲线形状和工艺参数跟测温晶体管差异很大,直接套用会带来明显的误差,这也是很多人第一次搭探头时读数偏十几摄氏度的常见原因。
接线关系理顺之后,探头的机械封装也决定了数据稳定性。工程安装中把 MMBT3904 用环氧灌封在一个小铜套里就成了一个标准的测温探头,再配合导热硅脂贴在管道外壁,外面包保温棉,这个做法在 HVAC 行业非常普遍,成本和成品可靠性都很好。
2.3 寄存器、分辨率和温度换算
PJ85718DM 这类双通道温度传感器的内部结构大同小异:一组配置寄存器、两组温度数据寄存器,分别对应本地和远程通道,通过 I2C 或 SMBus 访问。关于具体寄存器地址、默认上电状态和每个位的含义,必须严格以你手上那颗芯片的官方数据手册为准,不同厂家的实现细节差异很大,实在找不到资料的可以按下面这个常见布局先搭代码,再用测试工具验证寄存器值是否符合预期。
| 寄存器功能 | 常见地址 | 宽度 | 说明 |
|---|---|---|---|
| 本地温度值 | 0x00 | 16 bit | 高字节在前,含符号位 |
| 远程温度值 | 0x01 | 16 bit | 高字节在前,含符号位 |
| 配置寄存器 | 0x03 | 8/16 bit | 控制分辨率、关断模式等 |
温度数据的换算公式在不同芯片上有几套常见做法:有的芯片是 12 位分辨率,1LSB 等于 0.0625℃,换算公式为 raw = 数据寄存器值,temp = raw >> 3 × 0.0625;有的芯片是 13 位、0.0078125℃/LSB,需要先做符号扩展再做右移。我强烈建议在固件里把"右移位数"和"分辨率系数"做成两个宏,这样换芯片型号时只需要改头文件里的两个值,比把换算逻辑写死在代码里再一行行找着改要省心得多。
2.4 误差模型:为什么远程通道会有额外偏差
远程通道的误差来源比本地通道多不少,主要体现在三方面。第一个是偏置电流的失配和晶体管 β 的批次差异,这个误差在出厂校准之外只能靠选型来控制;第二个是线路阻抗,远程二极管引线越长,串联电阻越大,Vbe 的测量会受影响,很多芯片有自动串联电阻抵消机制,但范围有限;第三个是噪声和共模干扰,远程通道本质上是毫伏级小信号测量,最怕现场强磁场耦合。
理解这个误差模型最大的价值,是能帮你正确设置"远程温度告警阈值"。我在项目里遇到第一次贴片后读出的远程温度和参考温度系统性地高了 1.8℃ 左右,查了手册发现芯片默认把远程偏置电流设置在某个档位,而我的探头晶体管特性恰好在这个档位下会产生额外偏差。把配置寄存器切到合适的偏置电流档位后,偏差立刻回落到 0.2℃ 以内。这类问题不摸清原理是根本定位不了的。
3. 硬件电路设计:从 STM32 引脚到远程测量点
硬件这一块最容易出现"照着参考设计抄了原理图,抄完还是一堆问题"的情况,因为很多问题发生在 PCB 布局和现场走线上,而不是原理图本身。下面按信号链路逐个说。
3.1 I2C 连接与上拉电阻
STM32F765ZI 的 I2C1 可以映射到 PB6/PB7 或 PB8/PB9,我拿到手上的板子原理图用的 PB8(SCL)和 PB9(SDA)。注意 I2C 是开漏总线,必须有外部上拉电阻,一般 400kHz 模式下取 4.7kΩ,100kHz 模式下可以放宽到 5.1kΩ~10kΩ。有些 EVM 板载已经有上拉,接第二颗传感器到同一条总线时会因为上拉电阻并联过多导致信号沿变缓,如果出现 I2C 偶尔超时或数据掉帧,可以先查总线上是不是存在多个 4.7kΩ 上拉。
电平匹配同样要检查。PJ85718DM 这类芯片如果允许 3.3V~5V 供电,I2C 高电平会跟随供电电压。STM32F765ZI 的 I/O 是 3.3V 容限,如果传感器接在 5V 电源上,SCL/SDA 的高电平会超过 MCU 的耐压,必须加电平转换电路。我在第一版设计里想省事,传感器直接吃了 5V,结果上电没多久就发现 STM32 对应的 I/O 引脚行为异常,最后一律统一到 3.3V 供电,这个问题就再没出现过。
3.2 远程探头的选型和接线
远程探头端的晶体管选型建议先看数据手册的推荐清单。没有推荐清单的情况下,2N3904/MMBT3904 是最稳妥的开始,因为它 Beta 值和 Vbe 温度特性在宽温区内都比较规整,被大多数远程温度芯片用作校准参考。我的习惯是同一个项目统一采购同一批次晶体管,并且用万用表量一下 Vbe,筛掉明显偏离的个体,这样能显著降低批量装配后的温漂不一致问题。
接线方面,从传感器 D+/D- 到探头的两根线必须绞在一起,这样可以最大限度地抵消磁场耦合的共模干扰。距离控制在 50cm 以内最为稳妥,如果确实拉得更远,优先选用带屏蔽层的双绞线,屏蔽层单端接地。这里有个很多人会忽略的细节:在传感器端的 D+ 和 D- 之间并一个 1nF~10nF 的差模电容,可以有效滤掉高频噪声;这个电容要紧贴传感器引脚放置。别把电容放到探头那头,那样反而会串进从远端耦合的噪声。
3.3 HVAC 现场的干扰防护与布线
真正到了空调机组现场,电磁环境比实验室恶劣得多。变频器的 PWM 输出、接触器吸合瞬间的浪涌、压缩机启动的大电流冲击,都会在测量回路上感应出干扰。针对远程测温导线,我建议走线原则是"远离动力线、不平行、急拐弯处打结固定"。如果现场必须和动力线有一段平行距离,至少保持 20cm 以上的间隔,平行长度尽量控制在 1m 以内。
另一个常见干扰是地环路。HVAC 控制系统里,控制器和机组外壳往往通过不同接地路径连接,如果传感器和探头之间没有隔离,地环路上的电流会直接叠加到测温回路上。我遇到过远程温度读数出现 0.5Hz 左右周期性抖动的情况,后来发现是有 HVAC 设备的接地网串入了工频电流。解决办法是在传感器电源端加强滤波,并且把传感器的模拟地通过单点方式接到主板 AGND,避免形成大面积地环路。
3.4 电源与 PCB 布局
PJ85718DM 的电源引脚需要两个电容:一个 100nF 的高频去耦电容紧贴电源引脚,一个 10μF 的钽电容或电解电容做低频储能。去耦电容必须放在芯片同一面的电源引脚旁边,中间打过孔的做法会比直接铺铜走线差很多,电源噪声会直接影响 ADC 的参考电压。
PCB 布局上,传感器芯片不要放在 STM32 正下方,因为 MCU 工作时会把热量传导到 PCB,传感器感知到的会是叠加热源的温度。我在项目里的做法是把传感器放在板角、远离 MCU、电源模块和继电器驱动区域,并且附近大面积铺地。这个布局从源头减少了本地通道的读数偏差,比事后做软件补偿要可靠得多。
4. 固件实现:双通道温度读取的完整代码
下面给出我在 STM32CubeIDE 环境里实际跑通的代码框架,基于 STM32 HAL 库。由于 PJ85718DM 的确切寄存器定义请以官方手册为准,这里把寄存器地址和换算系数抽成了宏,方便替换。
4.1 I2C 初始化
首先在 CubeMX 里把 I2C1 配置为 PB8(SCL)、PB9(SDA),速率选 100kHz 或 400kHz,从设备地址在代码里指定。初始化完成之后,建议加一个 I2C 总线探测函数,扫描总线地址并回显,这是排查接线和地址问题的第一步。
#include "stm32f7xx_hal.h" extern I2C_HandleTypeDef hi2c1; #define PJ85718DM_I2C_ADDR (0x48 << 1) /* 由地址引脚决定,需查阅手册 */ #define REG_LOCAL_TEMP 0x00 #define REG_REMOTE_TEMP 0x01 #define REG_CONFIG 0x03 #define TEMP_SHIFT 3 /* 12位分辨率时右移3位 */ #define TEMP_LSB 0.0625f /* 1 LSB = 0.0625℃ */ static int pj85718dm_write_reg(uint8_t reg, uint8_t value) { uint8_t buf[2] = { reg, value }; return HAL_I2C_Master_Transmit(&hi2c1, PJ85718DM_I2C_ADDR, buf, 2, 100) == HAL_OK ? 0 : -1; }这里提醒一个细节:配置寄存器务必在测量开始前写入一次,哪怕默认值看起来正好合适。因为芯片上电复位后可能处于关断模式或低分辨率模式,直接去读温度寄存器读到的可能是上次的数据或全 0。我在联调阶段第一次踩到的就是这个问题,现象是芯片怎么读都返回同一个固定值,配置寄存器写完才恢复正常。
4.2 读取本地和远程温度
核心读取函数采用"先传寄存器地址,再接收两个字节"的两段式 I2C 访问。很多这类芯片支持重复起始条件,HAL 库的读操作会自动处理,不需要额外干预。
static int pj85718dm_read_u16(uint8_t reg, uint16_t *out) { uint8_t buf[2]; if (HAL_I2C_Master_Transmit(&hi2c1, PJ85718DM_I2C_ADDR, ®, 1, 100) != HAL_OK) return -1; if (HAL_I2C_Master_Receive(&hi2c1, PJ85718DM_I2C_ADDR, buf, 2, 100) != HAL_OK) return -1; *out = ((uint16_t)buf[0] << 8) | buf[1]; return 0; } int pj85718dm_read_temp(uint8_t channel, float *temp_c) { uint16_t raw; int16_t fixed; uint8_t reg = (channel == 0) ? REG_LOCAL_TEMP : REG_REMOTE_TEMP; if (pj85718dm_read_u16(reg, &raw) != 0) return -1; fixed = (int16_t)raw; fixed >>= TEMP_SHIFT; /* 12位有效数据,丢弃低3位 */ *temp_c = (float)(fixed) * TEMP_LSB; return 0; }温度值带符号,所以一定要把 uint16 强转为 int16 再做右移。如果不做符号扩展,零下温度会被解读成接近 100℃ 的正值,HVAC 冬季工况下就会闹出"室外零下 5℃ 读成 125℃"的乌龙。这一点是我自己在零下环境测试时才发现的,写在这里是希望你能避免同样的坑。
4.3 数据平滑与异常值剔除
原始读数直接用于 PID 控制时,量化噪声和偶发干扰会导致输出抖动。我通常在读取之后加一道滑动平均滤波,窗口大小根据应用调整:空气温度变化慢,可以用 16 点窗口;管道表面温度也需要至少 8 点。同时做一个简单的突跳剔除,连续两次读数差值超过 5℃ 时认为本次数据异常,丢弃并保留上一次值。
#define FILTER_WINDOW 16 float filter_update(float new_value, float *window, uint8_t *idx) { float sum = 0.0f; uint8_t i; window[*idx] = new_value; *idx = (*idx + 1) % FILTER_WINDOW; for (i = 0; i < FILTER_WINDOW; i++) sum += window[i]; return sum / FILTER_WINDOW; } float guard_update(float candidate, float last) { if (last == 0.0f) /* 首包数据直接接受 */ return candidate; if (candidate > last + 5.0f || candidate < last - 5.0f) return last; /* 突跳保护 */ return candidate; }滤波窗口和突跳阈值不是固定不变的。风管里的温度变化速率比水池里的快,如果你把采样周期缩短到 100ms,窗口就不能开 16 点那么大,否则系统响应延迟会大到让 PID 失效。我的经验是先用逻辑分析仪抓一拍真实波形,确定噪声的特征周期,再反过来定窗口大小,而不是凭感觉拍脑袋。
4.4 预留 Modbus 上报接口
HVAC 项目最终基本都要把温度数据交给楼宇自控系统,Modbus RTU 是最常见的对接协议。我在工程里把本地温度和远程温度映射成两个保持寄存器,地址分别对应 0x0001 和 0x0002,为了兼容上位机的整数习惯,温度值放大 10 倍存储(例如 23.45℃ 存为 234)。
uint16_t modbus_regs[2]; void update_modbus_registers(float local_temp, float remote_temp) { modbus_regs[0] = (uint16_t)(local_temp * 10.0f); modbus_regs[1] = (uint16_t)(remote_temp * 10.0f); }Modbus 协议栈这里没有展开,工程上可以移植现成开源协议栈,也可以自己写一个只支持 03 功能码的精简版本,CPU 占用极小。重点是给每个读数打上时间戳并把原始 raw 值同时存到日志里,这样上位机显示异常时可以回溯到底是转换错误还是上报错误。
5. 标定与实测:怎么判断数据到底准不准
代码跑通只是第一步,真正要把这套系统交到现场,必须经历一遍标定和实测。很多项目死在验收阶段,就是因为现场实测数据跟验收用的水银温度计对不上,两边各说各话。
5.1 参考基准与测试环境
标定之前先确定参考基准。HVAC 温度范围通常落在 0~50℃,精度要求高的场合需要用带 Pt100 探头的数显温度计做参考,精度可以到 ±0.1℃ 级别;如果没有,也可以用一个经过计量校准的玻璃温度计作基准,但读数时要注意读数延迟和视线角度。水银温度计要在 0~100℃ 范围内做多点核对,只用一个单点就定标,后面的斜率误差会越来越大。
测试环境我用的是一个带搅拌器的恒温水浴,把远程探头和参考探头绑在一起,保证两个探头处于同一个小范围水域里而不是一个靠上一个靠下。水浴温度稳定后等 10 分钟,让探头和水的温度彻底一致,这时再读数才有意义。本地通道的标定则要把芯片放在恒温箱里,或者用一个不通电但和实际板卡布局一致的样板来模拟热环境。
5.2 实测数据解读
我在 25℃ 恒温水浴里做了一组实测,记录如下,供参考:
| 水浴参考温度(℃) | 本地通道读数(℃) | 远程通道读数(℃) | 远程通道误差(℃) |
|---|---|---|---|
| 10.02 | 10.45 | 10.18 | +0.16 |
| 20.05 | 20.38 | 20.26 | +0.21 |
| 30.08 | 30.52 | 30.41 | +0.33 |
| 40.03 | 40.71 | 40.52 | +0.49 |
注意这张表里有两个信息:一是本地通道整体偏高 0.4~0.6℃,这符合板卡自热的规律;二是远程通道的误差随温度升高缓慢增大,说明存在斜率偏差而不仅是固定偏置。所以在系统里我建议至少做两点标定,用 (10℃, 40℃) 两组数据求出 offset 和 gain,然后对全量程做线性补偿,这样比只减一个固定偏置准得多。
5.3 误差来源的排查顺序
如果实测误差偏大,我建议按下面的顺序逐一排查,而不是直接改软件补偿系数。第一查探头类型是否符合手册要求,普遍二极管和晶体管在这类芯片面前表现差异很明显;第二查接线长度和是否绞线,超过手册建议距离时先缩短距离再测;第三查板端电容和去耦,噪声引入的误差通常在读数上表现为抖动而不是固定偏移;第四才轮到寄存器配置和换算公式。这个顺序基本覆盖了 90% 的异常,能帮你快速锁定问题层次。
6. 踩坑记录:调试期间遇到的高频问题
最后分享几个我在调试这套系统时真正遇到过、也花了不少时间才定位的问题。每一个都有明确的复现条件和解决办法,希望能让你少走弯路。
6.1 I2C 总线锁死的恢复
有一次系统运行了几个小时后,温度读数突然不再更新,用示波器看 SCL/SDA,发现 SDA 被拉低、SCL 还在规律地跳变。这种情况一般发生在从设备异常进入错误状态,或者总线被干扰导致状态机卡死。解决办法除了加大抗干扰措施之外,软件上必须实现总线恢复机制:把 SCL 和 SDA 临时配置成 GPIO 输出,在 SCL 上加 9 个时钟脉冲,同时让 SDA 保持高电平,这能让大多数卡死的从设备复位到已知状态,然后再恢复 I2C 外设重新初始化。
这里还有个配合动作:传感器本身如果有复位引脚或者支持关断模式重新唤醒,恢复总线之后最好把芯片也复位一遍,因为 I2C 释放不代表芯片内部状态机已经恢复。我最初只做总线恢复,结果频率还是偶发出现,把传感器软件复位加进去之后,问题才算真正解决。
6.2 远程通道读数跳变
实测中远程温度会突然跳高 3~5℃,过几秒又自己恢复。用示波器抓 D+/D- 波形后发现,跳变发生时那两脚上叠加了不少高频脉冲,来自旁边的继电器线圈。我最初只在传感器端加了一个 10nF 差模电容,效果有限,后来把远程双绞线换成了屏蔽双绞线,屏蔽层单端接 PCB 的 AGND,同时把继电器驱动改用 RC 吸收电路,跳变现象彻底消失。这里要特别强调,屏蔽层接地点一定要选在信号稳定的那一端,如果两端都接地反而可能形成地环路。
6.3 上电首包数据异常
还有一个非常典型的坑:每次系统上电后,第一次读回来的温度数据都明显偏离正常值,要等第二三次采样才恢复正常。分析下来是因为传感器在电源刚建立、内部 A/D 尚未完成第一个完整转换周期时,温度寄存器里存放的是上电瞬间的非法数据。解决办法很简单,在初始化流程里加一个 50ms 延时,然后主动丢弃第一包转换结果,从第二次采样开始才累积到滤波窗口里。很多数据手册里其实写了"上电后需要 X ms 才能得到有效转换结果",只是开发时容易被忽略。
最后说一个我自己体会很深的安装细节:远程探头贴敷在管道表面时,一定要用导热硅脂把探头铜套和管壁之间的缝隙填满,再压紧固定,最后包上保温棉。我见过不少工程装了探头却漏涂硅脂,隔热空气层让读数偏了好几度,现场人人以为传感器坏了,其实是安装环节的问题。把这个细节写进施工安装规范里,比什么都管用。这套基于 PJ85718DM 和 STM32F765ZI 的双通道测温方案,从原理到代码,再到现场实战,我已经完整验证过一轮,希望你的项目也能一次跑通。