做嵌入式和 HVAC 项目,温度监测永远是躲不开的基础活儿。看起来是读个温度、传个数据,但真要把本地测温、远程采集、抗干扰和稳定性同时拿捏住,里面的门道比想象中多不少。最近我在一个温控项目里把 PJ85718DM 数字温度传感器和 STM32F745ZG 控制器这套组合完整跑了一遍,覆盖了板上本地测温与远端分线测温两条链路。今天把这套方案里从硬件选型、寄存器操作到调试踩坑的过程整理出来,给正在做类似嵌入式温控或 HVAC 系统的小伙伴一个可以直接抄作业的参考。
先说结论:这个方案适合中小型 HVAC 控制箱、风机盘管温控器、嵌入式设备机柜温度监测这类场景。PJ85718DM 负责把温度物理量变成数字信号,STM32F745ZG 作为主控负责读取、换算、滤波、报警,以及通过串口扩展出远程测温通道。整套系统不依赖上位机也能独立运行,既能用于产品原型验证,也能直接往工程现场设备上移植。
1. 项目概况与方案选型逻辑
1.1 标题背后的真实任务拆解
"本地与远程温度"这句话看着简单,落地时其实包含了几层不同的需求。本地温度一般指控制器电路板附近的环境温度,或者被测对象直接贴在板载传感器上的温度。远程温度则指传感器与主控不在同一块电路板、距离从几十厘米到几十米不等的情况。
在实际 HVAC 项目里,本地测温通常管的是控制柜内部的过热保护,比如功率器件发热监测、电源模块温升监控;远程测温通常管的是风道温度、回风温度、冷却水温度这些分布在设备各处的关键节点。远程和本地的核心差别在于信号传输路径,本地传感器和主控之间可能就是几厘米的 PCB 走线,远程传感器则要经过连接器、线缆、甚至过 RS485 总线转接,这就带来了压降、干扰、通信时序等一系列问题。
标题里同时出现嵌入式和 HVAC 两个词,意味着这套系统既要兼顾嵌入式设备常见的低功耗、小体积诉求,又要满足 HVAC 场景对长期可靠运行、宽温度范围、抗干扰能力的硬性要求。PJ85718DM 这类数字输出传感器比模拟热敏电阻更适合这种混合场景,因为它的信号不必经过长距离模拟传输,直接以数字量在本地完成转换,抗衰减能力天然占优。
1.2 为什么是 PJ85718DM 与 STM32F745ZG 的组合
选 PJ85718DM 并不是因为它参数有多炸裂,而是因为它踩中了这个项目几乎所有需求点。它是一款带 I2C 接口的数字温度传感器,内部集成了温度传感单元、ADC 和数字逻辑电路,输出已经是换算好的温度数据,直接通过 I2C 读取寄存器就能拿到结果,不需要额外搭信号调理电路。相比 NTC 热敏电阻加 ADC 的方案,省掉了分压电阻、滤波电容和校准流程;相比热电偶方案,省掉了冷端补偿和放大电路;相比单总线传感器,它的 I2C 接口在多传感器组网时更规整,地址配置更灵活。
STM32F745ZG 则提供了足够的外设余量和性能冗余。它的主频跑到 216MHz,读温度这种任务占用资源极少,剩下的算力可以留给 PID 控制、人机交互或者通信协议栈。它自带多路 I2C、USART 和 SPI,这意味着本地传感器挂一组总线,远程传感器通过串口转 RS485 再接一组,硬件上不需要额外扩展芯片。F7 系列还带硬件浮点单元,做温度换算和滤波计算时不用手工转定点,代码写起来清爽很多。
作为对比,我也在项目前期评估过用低端 M0 加模拟 NTC 的方案,成本确实能压下来,但精度标定和长线传输两个问题很难绕过去。NTC 在 25℃ 附近精度尚可,到了零下或者 80℃ 以上离散性就很明显,需要逐点校准;而模拟信号过 10 米线之后,线阻和噪声叠加出来的误差可能达到 1℃ 以上,这在 HVAC 场景里是不可接受的。数字传感器直接把 ADC 放在探头位置,传输链路里跑的是数字电平,抗干扰能力完全不同。
1.3 本地与远程测温的系统架构
整套系统的拓扑其实很常规:STM32F745ZG 作为核心节点,I2C1 总线上挂载本板 PJ85718DM 传感器,用于监测控制板环境温度;USART3 外接 RS485 收发器,连接远端测温模块,每个远端模块上也有一颗 PJ85718DM,通过自定义协议或 Modbus RTU 协议与主控交换数据。
之所以远程通道走 RS485 而不是直接把 I2C 线拉长,是因为 I2C 是开漏结构,总线电容一大时序就容易畸变,超过两米就很不稳妥。RS485 是差分信号,抗共模干扰能力强,标准传输距离能到 1200 米,实际工程里拉个几十米完全没问题。对于 HVAC 现场,风机、压缩机启动时会产生很强的电磁干扰,差分传输在这种环境里优势明显。
上位机或者触控屏如果需要查看所有温度点,可以通过 STM32F745ZG 的另一个串口或者以太网口接入。不过在我这个项目里,主控本身就能完成显示、报警和远程通信,上位机只是辅助,所以架构上没有引入额外网关设备。
2. 温度采集硬件链路核心细节
2.1 PJ85718DM 的寄存器与配置要点
PJ85718DM 的寄存器结构不算复杂,但有几个寄存器位直接决定测量结果,配置错了后面全乱。核心寄存器包括配置寄存器、温度寄存器、ID 寄存器以及报警阈值寄存器。
配置寄存器里最需要注意的是分辨率设置位。这类传感器常见支持 12 位到 16 位分辨率,对应转换时间从几十毫秒到几百毫秒不等。分辨率越高,数据跳动越小,但读数更新越慢。HVAC 应用里温度变化速率本身不快,风道温度变化通常以分钟为单位,所以可以选择高分辨率模式换绝对值稳定性;但如果系统还要兼顾快速的过温保护,就得分情况处理——保护逻辑看的是粗分辨率快速数据,显示逻辑看的是滤波后的最终值。我实际用的配置是 14 位分辨率,转换时间约 100ms 左右,既能满足 0.1℃ 级别的显示需求,又能保证报警响应不至于太迟钝。
温度寄存器通常是 16 位,数据格式为二进制补码,高字节在高位。读取时需要注意一次性读两个字节,避免两次独立读取之间寄存器发生更新导致数据错位。在 I2C 读时序上,可以用连续读操作一次把高字节和低字节取回来,这比分两次读要稳妥得多。
PJ85718DM 还带报警输出引脚,可以在温度超限时直接拉低外部引脚,不需要主控持续轮询。这个功能在现场很有用——主控深度睡眠时,传感器自己盯着温度,超限了通过中断唤醒主控,既省电又不会错过报警。
2.2 STM32F745ZG 外设分配与管脚规划
主控外设分配上,我建议按功能分区规划,避免后期布线时引脚冲突。I2C1 管脚分配给本地传感器,USART3 管脚分配给 RS485 通信,另留一个普通 GPIO 接传感器报警中断输出,一个 GPIO 控制 RS485 收发方向。
管脚分配看似简单,实际有几个容易忽略的点。第一,I2C 的 SDA 和 SCL 必须选在支持开漏输出的管脚上,F7 系列大部分管脚都能配,但不要在复用时选到只作模拟输入的管脚。第二,RS485 方向控制引脚要选一个高速翻转能力强的 GPIO,因为每次收发都要切换方向,翻转太慢会丢字节。第三,报警中断引脚最好支持 EXTI 外部中断,这样主控在低功耗模式下也能被唤醒。
电源去耦方面,STM32F745ZG 的 VDDA 和 VREF+ 引脚需要单独的滤波电容,我用了 1μF 和 100nF 并联。传感器 PJ85718DM 的电源引脚同样要就近放 100nF,如果传感器和主控距离较远,远端还得再补一个 10μF 电解电容,防止线缆电感引起供电跌落。
2.3 I2C 上拉电阻与远距离接线的实测经验
I2C 总线工作在标准模式 100kHz 或快速模式 400kHz 时,上拉电阻阻值需要根据总线电容来定。本地 PCB 上走线短,总线电容通常只有几十皮法,用 4.7kΩ 上拉没问题;如果总线上挂了多个传感器,总线电容变大,就要换成 2.2kΩ 甚至 1kΩ,否则上升沿太缓,时序容易不满足。
这个项目里本地总线上就一颗传感器,我直接用了 4.7kΩ。但如果后续想扩展多个本地节点,建议一开始就把上拉电阻预留成 2.2kΩ,或者用可调电阻位方便调试。
远程传感器走 RS485 时,线缆选型比很多人想象的重要。我实测用普通网线(双绞线)效果就不错,双绞结构能有效抑制共模干扰。屏蔽层单端接地,不要两端都接,否则地环路会产生新的干扰。线缆终端要加 120Ω 终端电阻,尤其线长超过 10 米时,不加终端电阻会出现数据反射,远端模块偶发死机,排查起来非常费劲。
3. 固件开发实录:初始化、读取与滤波
3.1 基于 HAL 库的 I2C 初始化流程
固件部分我用的是 STM32CubeIDE 加 HAL 库,开发效率比直接操作寄存器高很多,而且代码可读性好,方便后面维护。初始化 I2C 的关键代码并不复杂,但有几个参数值得说明。
I2C_HandleTypeDef hi2c1; void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 400000; // 快速模式 400kHz hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } }这里我把时钟频率设成 400kHz,原因有两个:一是传感器本身支持快速模式,二是 400kHz 下读一次温度大概只需要几十微秒,对整体任务调度压力很小。DutyCycle 选择 2:1 模式,这是快速模式的标准配置,能保证 SCL 高电平时长满足 I2C 规范。
I2C 时钟频率不是设多少就一定能跑多少,实际还取决于总线上拉电阻和管脚 slew rate。如果布线很长或者上拉电阻偏大,400kHz 下波形可能已经畸形,这时候要把频率降回 100kHz 检查。我的经验是:先把频率降到 100kHz 做基础通信验证,确认能稳定读取后,再逐步调高到 400kHz,这样可以快速定位问题是硬件信号质量还是固件配置。
3.2 传感器数据读取与温度换算
读取 PJ85718DM 温度数据的典型流程是:先写寄存器地址选择温度寄存器,然后连续读取两个字节。HAL 库提供了现成的函数,直接用就行。
uint8_t temp_reg = 0x00; // 温度寄存器地址 uint8_t temp_buf[2]; int16_t raw_temp; float temperature; // 写寄存器地址 HAL_I2C_Master_Transmit(&hi2c1, PJ85718DM_ADDR, &temp_reg, 1, 100); // 连续读取两个字节 HAL_I2C_Master_Receive(&hi2c1, PJ85718DM_ADDR, temp_buf, 2, 100); raw_temp = (temp_buf[0] << 8) | temp_buf[1]; // 根据分辨率换算,14 位分辨率对应左移 2 位补零 if (raw_temp & 0x8000) { raw_temp |= 0xC000; } temperature = raw_temp * 0.0078125f;这段代码里最需要注意的是符号扩展。如果东经测量得到负温度,原始数据是二进制补码,高位可能不是 1,直接按无符号数换算会把零下温度算成一个小正数,整个逻辑就崩了。我的做法是先判断最高位是否置位,如果是负温度,就把高两位也填成 1,再做符号扩展,然后乘以分辨率对应的温度系数 0.0078125℃/LSB。
0.0078125 这个系数来自分辨率:14 位分辨率下,满量程对应 128℃,每个 LSB 代表 128 / 16384 ≈ 0.0078125℃。如果改用了 16 位分辨率,系数就变成 128 / 65536 ≈ 0.001953125℃。切换配置后,变量长度和换算系数必须同步更新。
3.3 远程温度采集与协议实现
远程测温的固件部分,核心是一个可靠的帧协议。我采用类似 Modbus RTU 的格式:设备地址、功能码、数据、CRC16 校验。主控作为主机定时轮询每个远端节点,节点收到读温度命令后返回温度数据。
#define REMOTE_NODE_ADDR 0x01 uint8_t cmd_buffer[8] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00}; uint8_t resp_buffer[16]; uint16_t crc_calc; // 计算并填充 CRC16 crc_calc = CRC16_Modbus(cmd_buffer, 6); cmd_buffer[6] = crc_calc & 0xFF; cmd_buffer[7] = (crc_calc >> 8) & 0xFF; // 发送查询命令 RS485_SetDir_TX(); HAL_UART_Transmit(&huart3, cmd_buffer, 8, 100); RS485_SetDir_RX(); // 等待并接收响应 HAL_UART_Receive(&huart3, resp_buffer, 7, 200);RS485 方向切换是整个通信最容易出错的地方。必须保证在发送完最后一个字节之后再切换方向,不能提前切。现场遇到过一个情况:数据发送完了,但方向引脚翻转太早,最后一个字节被截了一半,远端节点收不到完整命令,一直不回复。解决办法是在发送完调用一个微小延时,等移位寄存器完全清空后再拉低方向控制脚。
协议层建议做超时与重试机制。工业现场通信瞬间失败太正常了,一个节点没响应不代表整个系统出问题。我习惯每个节点连续失败三次才判定离线,单次失败只记录不报警,避免风机启停的瞬态干扰导致误报。
3.4 滤波与报警判决逻辑
温度数据直接拿来用,大概率会看到小数点后两位一直在跳,特别是传感器贴近风机或者压缩机附近时。这里我用了两级处理:先做中值滤波,再做移动平均。
中值滤波的作用是剔除离异点,比如某次读数因为 I2C 干扰跳了 5℃,取中间值能把这异常点过滤掉。移动平均的作用是平滑剩余的小幅波动,让显示和控制输出都稳定下来。
#define FILTER_WINDOW 5 float temp_buffer[FILTER_WINDOW]; uint8_t temp_index = 0; void Temp_Filter(float new_value) { temp_buffer[temp_index] = new_value; temp_index = (temp_index + 1) % FILTER_WINDOW; } float Temp_GetAverage(void) { float sum = 0; for (uint8_t i = 0; i < FILTER_WINDOW; i++) { sum += temp_buffer[i]; } return sum / FILTER_WINDOW; }报警逻辑上,我加了滞回控制。比如高温报警阈值设为 60℃,但报警解除要等温度降到 55℃ 以下才执行。这为了防止温度在阈值附近抖动时继电器反复吸合断开,那会直接缩短 HVAC 设备里继电器和压缩机的寿命。实际项目中,滞回区间设在 3~5℃ 比较合适,太小没有效果,太大会让控制温度失真,用户会觉得系统响应迟钝。
4. HVAC 场景适配与稳定性调优
4.1 HVAC 环境里测温要防的几类干扰
HVAC 设备和干净的实验室环境完全是两回事。风机启动瞬间电流冲击大,会在电源线上拉出明显的毛刺;压缩机启停时电流更大,干扰可能通过地线传导到传感器和主控。这些干扰对数字传感器最典型的影响就是 I2C 通信偶发失败,或者温度读数瞬时跳变。
经验做法是在传感器供电入口加磁珠和电容组成滤波器。磁珠能抑制高频干扰,电容削弱电源纹波,成本加起来不到几毛钱,但对稳定性提升很大。远程节点如果离变频器近,建议在 RS485 收发器前端加 TVS 管,防止共模电压击穿芯片。
还有一个容易忽略的点:传感器探头本身不能和强电走线平行敷设。现场如果实在绕不开,必须采用双绞线并且保证屏蔽层单端接地。我见过一个项目,温度探头线和 220V 电源线绑在一起走了三米,读出来的温度始终比实际高两度,把线分开后问题立刻消失。
4.2 传感器噪声判断与采样自检
当一个温度点在读数上频繁乱跳时,先别急着加滤波,应该先判断是真实温度波动还是干扰。最简单的办法:把采样周期拉长,观察长时间趋势。如果趋势是平滑的,只是单个采样点瞬时跳动,那么大概率是电气干扰或 I2C 误码;如果趋势本身就在振荡,那么滤了波也白搭,要从控温逻辑和气流组织上找原因。
我在固件里加了一个简单的自诊断功能:连续读取三次温度,如果最大值和最小值之差超过 5℃,就判定本次采样无效并重新初始化 I2C。这个逻辑救了不少次,尤其在调试阶段,总线卡死和高干扰条件下都可以自动恢复,而不是死等用户断电重启。
传感器故障检测也不能省。PJ85718DM 如果断线或者损坏,I2C 读出来的数据可能一直是 0xFF 或 0x00,温度值要么固定在最高量程要么固定在最低量程。固件里需要识别这种异常状态并上报故障码,而不是把一个错误的温度值送去控制。HVAC 系统要是按错误温度持续运行,很快就可能触发设备保护或者烧坏风机盘管。
4.3 低功耗与系统可靠性取舍
嵌入式设备通常要求低功耗,HVAC 设备则更看重长期可靠性,这两者在温度监测逻辑上会有矛盾。低功耗要求传感器和主控尽量多睡,可靠性要求系统随时盯着温度。PJ85718DM 在设计上比较友好,它自身在测量间隔间可以进入低功耗状态,主控只需要周期性唤醒读取即可。
实际项目中我把采样周期默认设在 1 秒,这个频率既能保证温度变化被及时捕捉,又不会让总线持续忙碌。在需要快速响应过温保护的应用里,采样周期缩短到 200ms,但滤波窗口同步缩小,保证报警延迟不超过 1 秒。
看门狗一定要开。STM32F745ZG 的内核看门狗和窗口看门狗都支持,我在主循环里喂独立看门狗,一旦固件跑飞能在 500ms 内复位。代价是复位后通信会短暂中断,远程节点需要时间重连,但总比整个控制器死机在现场要好得多。
5. 调试与常见问题实录
5.1 I2C 总线卡死的现场处理
I2C 最经典的故障就是总线被拉死,现象是 SCL 或 SDA 一直为低电平,HAL 库调用返回超时。原因通常是通信过程中有人拔线、电源瞬断,或者某个从设备异常拉低了总线。
复位主控不一定能解决问题,因为问题可能出在从设备一侧。我的处理流程是:先把 SCL 引脚切换为普通 GPIO 输出,手动翻转 9 个时钟周期,相当于给挂在总线上的设备发送一个 STOP 条件,让它们释放总线。然后再切回 I2C 模式重新初始化,绝大多数情况都能恢复。
在远端节点上,也要做类似的总线恢复逻辑。嵌入式现场调试不可能每次出问题都派人带着电脑去重启,自动恢复能力是必须的。我甚至在主控固件里加了 I2C 连续失败检测,失败超过 10 次就主动切断传感器电源 100ms,然后重新上电初始化。这招对传感器死锁的情况效果很好。
5.2 温度数据固定不变的排查
如果读回来的温度值一直不变,比如固定在 25℃ 或者固定在量程边缘,先别急着怀疑传感器坏了,可能是地址错误。PJ85718DM 的 I2C 地址由引脚电平决定,不同接法对应不同地址,代码里写错地址后,读出来的其实是空数据,或者系统根本没跟传感器建立通信。
排查方法是逐个扫描总线上所有地址,打印出每个地址的应答情况。接线松动导致的偶发故障也常见,尤其是远程传感器通过连接器连接时,现场振动几十分钟后接触不良就会出现。用螺丝紧固端子和加弹簧垫圈能减少这类问题,后期维护大头往往不在芯片本身,而在连接器。
5.3 远程节点通信时好时坏的根源
远程节点刚调试时一切正常,跑几小时后偶尔掉线,再换一台又正常,这种问题多半出在三方面:终端电阻没接、偏置电阻缺失、波特率偏差。
RS485 的 A/B 线之间必须接 120Ω 终端电阻,并且只在总线两端各接一个。只接一端或忘记接,长线反射会让数据波形出现畸变,远端节点处于接收状态时会误码。偏置电阻则保证总线空闲时 A 高于 B,不至于让接收端检测到无用电平。波特率方面,如果远端模块用了内部 RC 振荡器而不是晶振,频率偏差可能达到 1% 以上,长帧传输时累计误差就会导致接收失败。选型时尽量选带晶振的模块,或者用主控这边做主从同步。
排查这类问题最好的工具是示波器,直接抓 A/B 两线的差分波形。业余条件下也可以用万用表量空闲电压,正常情况下 A-B 之间的电压应在 200mV 以上,明显低于这个值就要检查偏置电阻。
5.4 常见问题速查表
| 故障现象 | 可能原因 | 排查与解决方法 |
|---|---|---|
| I2C 读取超时 | 总线被从设备拉低 | 手动翻转 SCL 9 个时钟,触发 STOP 条件后重新初始化 |
| 温度恒定在 0xFF | 传感器地址错误或断线 | 扫描总线地址,检查焊接和线缆连接 |
| 温度跳动超过 2℃ | 电磁干扰或电源纹波过大 | 加磁珠滤波,检查传感器供电去耦电容 |
| 远程节点偶发无响应 | RS485 终端电阻缺失或没接地 | 两端接 120Ω 终端电阻,检查 A/B 线电压 |
| 报警频繁误触发 | 阈值太靠近正常工作区 | 增大滞回区间,调整报警阈值 |
| 远程节点重连慢 | 主控看门狗复位导致握手超时 | 缩短节点重连超时,增加快速重连机制 |
每个问题背后几乎都有信号完整性和电源质量的影子,不要只看表面现象。硬件问题先排除,再怀疑固件,这个顺序能省很多时间。
这套方案从画板到调通大概用了三周时间,真正花时间的并不是传感器驱动那几十行代码,而是把远程链路在风机、压缩机的电磁干扰下稳下来。我个人体会是:数字温度传感器的引入大幅简化了硬件设计,但对应的坑也从模拟域转移到了数字通信域。PJ85718DM 加 STM32F745ZG 这个组合,算是在性能、成本和开发效率之间找到了一个平衡点。如果你正在规划类似的嵌入式温度监测或 HVAC 改造项目,把 I2C 总线恢复、RS485 终端匹配和传感器自检三件事提前做进设计里,后面调试会顺畅很多。