最近手头在做一套HVAC控制器,需要把风机盘管、风管和新风机的温度全部纳入监控,同时还要在嵌入式主板上就近采集回风温度。折腾了一圈,最终选了PJ85718DM配合STM32F107VC这套组合,把本地和远程温度监测一起做掉了。这篇东西就把整个设计过程、硬件连接、软件初始化和踩过的坑从头到尾捋一遍,给同样在嵌入式和暖通场景里折腾温度采集的朋友做个参考。
这里说的“本地温度”,是挂在主控MCU附近、通过板级总线就能直接读到的温度,用来做回风采样和设备自身状态监测;“远程温度”则指挂在远端节点的温度,节点和主控之间通过现场总线组网,主控定期把各点温度拉回来统一处理。两种温度混在同一个系统里管,是嵌入式应用开发里很常见的需求,但传感器选型不好,本地倒还好办,远程这一路很容易在抗干扰和通信上栽跟头。这篇文章会把选型逻辑、硬件接法、寄存器配置、通信组网和调试经验都说明白,适合正在做暖通控制器、智能楼宇采集终端,或者需要在嵌入式设备里做多点温度监测的开发者参考。
1. 项目整体设计与方案拆解
1.1 嵌入式与HVAC场景的核心需求差异
嵌入式设备里的温度监测通常有两层目的:一是保护,二是控制。保护层面,比如功率器件或处理器核温超过阈值时要触发降频或断电,这个偏重响应速度和可靠性;控制层面,根据环境温度调整风扇转速或加热器输出,这个偏重采样精度和滤波平滑度。HVAC场景则更分散,一台现场控制器旁边可能有好几个温度采集点——回风温度、送风温度、盘管出液温度、室外侧冷媒温度,有些点在现场控制器主板上,有些则在几十米外的风道里或设备间里。
这两种场景结合起来,对温度采集系统的要求很明确:本地通道要实时、稳定,远程通道要抗干扰、易扩展。如果只做一个本地单点采样,随便找个温度传感器配个MCU就能跑,但加上远程多节点之后,总线选择、通信协议、节点供电这些原本不太起眼的问题就全都浮出水面了。
1.2 器件选型的底层逻辑
选择PJ85718DM,主要看重几个点。第一,它走I2C接口,和STM32自带的硬件I2C外设直接对接,不用额外引时序,软件上少一层折腾。第二,芯片提供三个地址配置引脚,一条I2C总线上理论上可以挂多个,方便在很多点位上就近接入。第三,器件内置12位ADC和窗口报警比较器,温度超过设定幅值可以直接从ALERT引脚输出中断,不用靠主控软件轮询死等。像HVAC这类需要响应速度的场合,硬线报警很省心。
STM32F107VC当时选它,则是因为它属于互联型产品线,片上同时集成了多个USART、两个I2C、两个CAN控制器和以太网MAC,远程节点既可以用CAN组网,也可以走RS485换Modbus,灵活性高。主频72MHz,在跑温度和简单控制逻辑的前提下完全够用;512KB Flash也足够放下Bootloader、应用固件和通信协议解析表。工作温度范围适合放在控制器箱体内,不用额外做散热方案。
我的体会是:如果项目只做单板温度采集,选个8位小单片机都行,但一旦涉及“远程多节点 + 协议解析 + 本地PID”这种复合需求,MCU的外设数量和Flash容量就成了隐形天花板。当时我还在F103和F107之间犹豫过,最后选了F107,光多出来的CAN控制器,就让系统扩展空间大了很多。
1.3 系统总体架构:一主多从的温度采集网格
整套系统的结构并不复杂。主控板是核心,板上焊一片PJ85718DM用作本地回风温度采样,通过I2C1接到MCU。远程侧,在每个风机盘管控制盒或风道传感器盒里放一块带PJ85718DM的小板,由节点端处理温度数据并通过CAN总线挂到总线上,主控统一接收。
这种架构的好处是:本地温度实时性最好,远程温度采集点可以灵活增减,不必改动主控硬件。每增加一个远程点,只需要在总线上增加一个从节点,主控程序里增加一个节点ID的解析表项即可。另外,把远程节点的ADC采样、滤波、协议封装等工作下沉到节点自身,主控只做汇总和决策,CPU负载能腾出来跑PID和上位机通信。
说句实在话,刚开始我也想过用一根长的I2C线把所有传感器串起来,但测试后发现线路超过几十厘米、经过驱动器干扰区之后,I2C信号就面目全非了。所以老老实实换成CAN总线做远程传输。这个选型教训在后面常见问题部分会再展开。
2. 硬件连接与寄存器级实操要点
2.1 PJ85718DM的引脚功能与I2C地址配置
PJ85718DM是比较典型的I2C温度传感器封装,引脚不算多,核心引脚包括电源VCC、地GND、I2C数据SDA、I2C时钟SCL、三个地址引脚A0/A1/A2和ALERT报警输出。三个地址引脚每个可以有L、H、S三种电平状态(S通常指高阻),三个引脚组合下来就是27种地址,实际项目中挂几十个节点也够用了。
硬件上要注意:地址引脚不能悬空随便接,电平状态必须通过电阻明确拉到高、低或保持浮空。我当时的做法是每颗芯片旁边放一颗2kΩ排阻,将地址引脚分别接VCC、GND或完全悬空,芯片上电后读到的地址就固定了。如果你把地址引脚随意悬空,读不到数据或地址冲突的情况会经常出现,这个坑值得留意。
ALERT输出是开漏结构,外部必须加一颗上拉电阻到电源,阻值4.7kΩ到10kΩ都行。这颗电阻别图省事省略,否则中断信号拉不低,MCU侧根本识别不到报警。另外,ALERT脚建议引到MCU的外部中断输入引脚,配置成下降沿触发,这样温度越限时MCU能第一时间响应。
2.2 STM32F107VC的外设分配与引脚连接
我用的连接方式比较常规:本地PJ85718DM挂I2C1,时钟线SCL接到PB6,数据线SDA接到PB7,ALERT接到PB4配置为外部中断。远程CAN控制器对应的引脚是PA11(CAN_RX)和PA12(CAN_TX),通过一颗CAN收发器接到总线。I2C地址全部由硬件引脚决定,主机这边的I2C地址可以忽略,从机地址通过读地址位确定。
STM32F107VC的电源域和引脚分配需要特别留心。控制板上有3.3V主电源和12V设备电源,传感器供电从3.3V稳压输出取,最好加一颗磁珠做隔离,避免HVAC里压缩机和风机启动时的大电流毛刺顺着地线窜进传感器。复位、BOOT0、晶振相关的引脚按手册标准接法来,这部分没什么特别,但要注意I2C引脚如果和JTAG/SWD调试口冲突,调试期间会非常难受。我的做法是让I2C1的SCL/SDA走PB6/PB7,JTAG用PA13/PA14,两边不冲突,调试和采集可以同时进行。
2.3 供电与抗干扰设计:HVAC环境下的硬仗
HVAC应用里最常见的干扰就是电机启停引发的电源跌落和辐射。压缩机启动瞬间电流可以到正常运行的数倍,地线上的压降足以让温度读数跳变几摄氏度。除了前面说的磁珠隔离,传感器板的地线要采用单点接地,并尽量远离功率地。电源去耦电容在每个传感器板上都要加,我习惯在电源管脚附近放一颗100nF陶瓷电容,同时再并联一颗10µF电解电容,实测对读数稳定性帮助很大。
还有一个容易被忽略的点:PJ85718DM和MCU之间的I2C走线不要穿过变压器或电机供电电缆附近,实在绕不开就加粗走线、缩短距离,并在SDA/SCL线上各加一颗10pF小电容做滤波。远程节点的CAN线则用双绞屏蔽电缆,屏蔽层在控制器端单点接地,这基本是暖通工程里的标准做法。总结下来,抗干扰的核心思想就四个字:远离、去耦、单点接地。
2.4 寄存器配置:分辨率、报警阈值与温度换算
PJ85718DM的寄存器结构不复杂,配置寄存器和温度寄存器是操作核心。温度寄存器是16位的,高8位是整数部分,低8位是小数部分,配合12位ADC的分辨率,每一位小数对应的温度是0.0625℃。换算的时候直接读两个字节,拼成16位数,右移4位就得到12位有效数据,再乘以0.0625就是实际温度。这个换算新手最容易错的地方是把符号位当成普通数据位,建议做成带符号处理的宏定义。
配置寄存器里最常用的设置包括:转换分辨率、故障排队次数和ALERT输出模式。HVAC场景我一般把分辨率拉满到12位,故障排队次数设为4次,这样瞬时干扰无法随便触发报警,避免现场因为断续报警搞出误会。报警阈值寄存器里写上上限和下限值,比如回风温度上限设为40℃,下限设为5℃,超出范围立即从ALERT脚拉低,主控中断里做保护动作。
3. 软件实现:初始化、采集、通讯与联动
3.1 硬件I2C初始化与读写函数封装
STM32的硬件I2C用起来要注意一个老问题:总线错误复位和超时处理。很多人一开始图方便用软件模拟I2C,但那样CPU占用率高,而且时序容易被中断打断。我的选择是用硬件I2C,同时打开错误中断,一旦检测到AF(应答失败)或BERR(总线错误),先把I2C外设复位,再重新配置一次,这样系统不至于被一次瞬时干扰打趴。
初始化顺序也有讲究:先把GPIO的复用功能模式匹配好,再初始化I2C外设时钟和参数,最后打开外设使能。顺序错了虽然没有硬件报错,但总线上可能多出一堆自己都看不懂的ACK波形。读写函数封装时,要加一个超时退出机制,不要在总线卡死时让主程序死等。我用一个宏定义超时值,比如100ms,超时直接返回错误码,主循环收到错误码后选择重试或报警。
#define I2C_TIMEOUT_MS 100 #define TEMP_RESOLUTION 0.0625f // 读取PJ85718DM温度寄存器 int8_t read_temp_reg(uint8_t dev_addr, uint8_t reg, uint16_t *raw) { // 发送寄存器地址 if (HAL_I2C_Master_Transmit(&hi2c1, dev_addr, ®, 1, I2C_TIMEOUT_MS) != HAL_OK) return -1; // 读取两个字节温度数据 uint8_t buf[2]; if (HAL_I2C_Master_Receive(&hi2c1, dev_addr, buf, 2, I2C_TIMEOUT_MS) != HAL_OK) return -2; *raw = ((uint16_t)buf[0] << 8) | buf[1]; return 0; } float raw_to_temp(uint16_t raw) { uint16_t valid12 = raw >> 4; float temp = (int16_t)valid12 * TEMP_RESOLUTION; return temp; }这段代码里有个细节:raw_to_temp函数里先把raw右移4位,再按有符号的int16_t做乘法,这样负数温度也能正确转换。如果不先转成有符号数,温度低于0℃时会得到一个错误的大正数。
3.2 本地温度采集与ALERT中断处理
本地通道的读取逻辑相对简单:主循环里每500ms发起一次读取,读到16位温度值后经过换算转成float,再放进带滑动窗口的滤波数组,取窗口平均值作为最终温度。为什么要滑动平均?HVAC现场的风机转速变化会引起温度探头附近气流脉动,瞬时读数波动很大,滑动平均后控制逻辑才稳定。
ALERT中断处理优先级要高一些,中断里不做费时的换算和滤波,只读报警标志、记录事件并置保护标志位,具体动作放到主循环处理。如果HVAC控制器里还有一个看门狗,喂狗这件事千万别放在中断里做,等主循环拿到新温度值再喂,才能保证死循环能被看门狗救回来。这一点我们踩过两次,第一次把喂狗放在定时器中断里,结果主循环挂死了看门狗照样被喂着,整个控制器在死循环里跑了半天才发现。
3.3 远程温度采集:CAN节点与协议帧设计
远程节点的软件要稍微复杂一点,主要是多了一个通信协议层。我们定义了一套紧凑的CAN协议帧:数据帧使用标准帧格式,ID按功能编码,前几个字节放节点地址和传感器类型,后两个字节放温度原始值,再配一个校验字节。每个远程节点上电后先做自检,读完PJ85718DM的配置和温度,之后每隔500ms发一帧温度数据。主控端收到后按ID解析、滤波,并把最新的节点温度写到共享结构体里。
typedef struct { uint8_t node_id; uint8_t sensor_type; uint16_t temp_raw; uint8_t checksum; } TempCanFrame;这里有个关键细节:温度原始值用整数传输,不要在节点上转成float。因为CAN帧里装浮点数既浪费带宽又增加解析复杂度,我们用12位原始ADC值或乘以100后的定点数,主控收到后再除以100转成实际温度。实测下来,整数传输可以避免很多莫名其妙的精度误差,而且不同编译平台对浮点数的表示方式还不完全一致。
CAN波特率这块也要算一下。我们的总线上规划最多挂16个节点,每个节点500ms发一帧约8字节数据,加上帧头帧尾和填充位,一帧完整CAN报文大约需要15字节的传输时间。250kbps波特率下,一帧大约需要0.6ms,16个节点轮询一圈也就10ms左右,完全足够。考虑抗干扰余量,我选的是125kbps,这样即使总线上有变频器干扰,误码率也能控制在很低的水平。
3.4 HVAC中的结果应用:温度数据参与PID联动
采集温度不是目的,目的在让HVAC系统根据温度做动作。我们的联动逻辑大致分两层:第一层是保护,回风温度或盘管温度超限时,ALERT硬中断会立即关闭风机或压缩机输出;第二层是调节,主控根据回风温度和设定目标温度的差值做PID计算,输出风机转速或比例阀开度。PID参数整定时要特别注意积分限幅和抗积分饱和,不然启动阶段会有一段明显的超调。
远程温度在这套逻辑里是补偿因素。比如新风温度低的时候,送风温度的PID目标值要做修正,避免把冷风猛往室内吹。把本地、远程温度都收进决策逻辑后,整个HVAC系统就有了多点感知能力,比单纯靠一个探头吹风要稳得多。我后来还把各个节点的温度变化率也算了进去,如果某个点温度变化率异常快,大概率是风机或者阀门出了问题,这个预警逻辑对维护人员很有用。
4. 常见问题与排查技巧实录
4.1 温度读数漂移和跳变
现象:读出来的温度值在正常值附近±5℃乱跳,传感器附近没有任何热源。
排查顺序:先看供电波形,用示波器量PJ85718DM电源脚有没有跌落毛刺;再看I2C波形,SCL/SDA上是否有振铃或幅值不稳;最后检查换算是否把高字节符号位当成了普通数据。大多数跳变问题出在地参考和符号处理上。如果电源波形干净、换算没问题,那就要重点查传感器探头本身有没有密封好,HVAC环境里湿气进入探头外壳也会导致读数漂移。
4.2 I2C通信失败
现象:初始化后读寄存器一直返回超时,逻辑分析仪上看不到ACK。
最常见原因是SDA/SCL线上缺少上拉电阻。STM32的I2C引脚是开漏输出,没有上拉根本拉不到高电平。另外如果总线上挂了多个PJ85718DM,地址设重了也会这样。解决方法:先只挂一个传感器,确认波形后用逻辑分析仪观察地址和ACK。还有一次遇到问题是上拉电阻选的太大,用了100kΩ,导致信号上升沿太慢,换回4.7kΩ后立竿见影。
I2C总线上挂多个从机时还有个先后顺序问题:上电瞬间多个从机同时拉低总线,容易造成总线锁定。我的做法是让传感器供电由MCU控制,先让MCU初始化完I2C外设再统一给传感器上电,避免竞争条件。
4.3 远程CAN节点掉线和误码
现象:主控偶尔收不到某节点的温度帧,或者解析出的温度突然跳到极值。
排查经验:先检查CAN总线终端电阻,总线两端必须各有一个120Ω电阻,少一个节点距离一远就反射严重。其次检查波特率和节点距离是否匹配。室内走线几十米内用250kbps通常没问题,但HVAC现场有变频器时,建议降到125kbps并开启CAN控制器的自动重发。如果只有某个节点掉线,检查那个节点的电源和地线压差,节点和主控之间地电位不相等,最容易导致通信异常。
我还遇到过一种隐蔽情况:某个节点对外的金属外壳和大地之间形成了地环路,导致收发器共模电压超限。解决方法是节点供电隔离,或者把CAN收发器换成带电气隔离的模块。这个经验在改造老设备时特别常见,老设备的接线端子经常没有预留隔离位置。
4.4 标定补偿与长期稳定性问题
实际项目中我们遇到的另一个坑是温度标定。PJ85718DM标称精度有界限,但装配到控制器箱子内部后,受主板热量影响,读数会比真实环境温度高2~3℃。解决方案是做一次出厂两点标定:把探头放在恒温槽里,0℃和50℃各记录一次偏差,把偏移量和增益系数写进Flash,运行时自动修正。
长期稳定性方面,传感器老化会在两三年后体现出来。HVAC项目通常要跑十年以上,我的建议是控制器程序里保留定期自检功能,比如每个季度自动对比本地传感器和远程节点传感器的读数,如果某个节点偏差持续超过阈值,上位机会标记该节点需要检修。用这套方法,我们的系统已经提前发现过两个需要更换的传感器,避免了夏季高峰期设备误动作。
| 故障表现 | 排查方向 | 常用解决手段 |
|---|---|---|
| 本地温度跳变 | 电源纹波、地参考、换算符号 | 加磁珠/电容,检查符号位 |
| I2C超时 | 上拉电阻、地址冲突、总线竞争 | 换4.7kΩ上拉,错开供电时序 |
| 远程节点偶发掉线 | 终端电阻、波特率、地环路 | 双端120Ω,降波特率,加隔离 |
| 读数整体偏高 | 内部发热、装配位置 | 出厂两点标定,软件补偿 |
| 读数整体偏低 | 探头接触不良、探头未露出 | 检查探头安装,导热硅脂填充 |
5. 实测体会与后续扩展方向
5.1 实测结果与个人体验
整个系统调试完跑到现在的感受是:PJ85718DM在HVAC工况且传感器供电干净的前提下,读数稳定性还是令人满意的。本地通道温度波动控制在±0.3℃以内,远程节点在有屏蔽双绞线和终端电阻的CAN链路上,误码率低到可以忽略。设备连续通电运行了一个月,还没有出现过一次因干扰导致的误报警。
这里要特别提一句防守心得:硬件的每个“小细节”都别省。比如ALERT上拉电阻、I2C线上那个10pF滤波电容、CAN终端电阻,单看都是几毛钱的东西,但合在一起决定了整个系统在恶劣环境下的生存能力。我们曾为了省一个终端电阻导致一个节点频繁掉线,排查了一个星期才找到原因,这个教训至今记忆犹新。温度监测系统看起来简单,做得稳不稳,全看这些不起眼的细节是否到位。
5.2 后续扩展方向
这套架构如果要往上走,我建议往这几个方向扩展。一是远程节点升级为支持多通道的传感器板,一块节点带两路PJ85718DM,分别测风管出风和室内温度,节点MCU的成本几乎不变,但采集点密度翻倍。二是把CAN总线改成无线LoRa方案,用于无布线的改造场景,LoRa的穿透力在楼宇里表现不错,但要注意数据延迟比CAN高不少,适合做巡检用途而非实时保护。三是把温度数据直接推到上位机或云端,做成集中监控大屏,这时候STM32F107VC的以太网MAC就能派上用场了,直接做TCP上报,不用额外挂协议转换模块。
还有一个我正在尝试的方向:把每个节点的温度数据在本地做一段时序存储,记录最近24小时的温度曲线。这样主控如果离线一段时间,节点恢复通信后能补传历史数据,对故障复盘非常有帮助。只要节点端MCU留出足够Flash或者外接一个小容量SPI Flash,这个功能实现起来并不复杂。
最后再分享一个小经验:HVAC这种长期通电的工业类应用,传感器和MCU之间的所有通信都要做成周期心跳式的,不要做成事件触发式。温度变化触发上报,听着很美,但一旦传感器异常,整个系统就变成静默故障;周期上报虽然流量大一点,但主控可以很快发现节点消失并报警。把每个节点的“上次在线时间”记录好,节点超过三个周期没上报,就直接在界面上标红,维护人员一眼就能定位问题节点。这套设计思路,值得所有做温度监测的朋友参考。