1. 项目缘起与整体设计思路嵌入式温度监测这个方向看起来简单真做起来坑不少。我这次拿到的需求是做一个能同时覆盖本地和远程温度采集的节点目标场景是 HVAC暖通空调系统的环境监控。HVAC 场景对温度采集有几个硬性要求多点位、响应快、长期稳定、能远程上报。市面上常见的方案要么是单点 DS18B20 拉一堆线要么是模拟 NTC 加热敏电阻精度不够要么是走无线模块但功耗和成本压不下来。权衡之后我选了 PJ85718DM 这颗 I2C 数字温度传感器搭配 STM32F745VG 做主控理由后面细说。先把这个项目是什么、能干什么讲清楚。PJ85718DM 是一颗 I2C 接口的数字温度传感器本地测温精度在常温区间可以做到 ±0.5℃ 以内同时它支持一路远程二极管测温通道可以外接一个分立三极管或者二极管作为远端感温元件。这意味着单颗芯片就能同时拿到板载本地温度和远端探头温度两个数据对于 HVAC 这种需要在出风口、回风口、管道内壁分别布点的场景非常合适。STM32F745VG 是 ST 家的 Cortex-M7 主控主频 216MHz带 FPU 和 DSP 指令I2C 外设资源丰富跑多路传感器轮询加数据上报绰绰有余。这个方案解决的核心问题是用一颗传感器 一颗主控实现本地与远程双通道温度监测并通过主控的通信外设把数据送到上位机或云端。适合谁参考做楼宇自控、机房环境监控、冷链运输监测、工业设备温度巡检的嵌入式开发者都能直接抄这套架构。哪怕你是刚接触 I2C 和 STM32 的新手跟着走一遍也能把数字温度传感器的完整链路摸清楚。整体设计上我分了四层传感层PJ85718DM 本地 远程通道、主控层STM32F745VG 负责 I2C 采集、数据处理、告警判断、通信层串口转以太网或 4G 模块做远程上报、应用层上位机或云平台做可视化与历史存储。这个分层的好处是每一层职责清晰调试的时候可以逐层隔离问题不会出现温度不对但不知道是传感器问题还是通信问题的尴尬。为什么不用模拟传感器加 ADC 的方案模拟方案理论上成本更低但 HVAC 现场电磁环境复杂变频器、接触器、风机启停都会在模拟线上耦合噪声ADC 采回来的值抖动大还得做复杂的滤波和校准。数字传感器把 ADC 和信号调理都集成在芯片内部I2C 传输的是数字量抗干扰能力天然强一个档次。而且 PJ85718DM 内部有出厂校准省掉了产线标定环节这对批量部署来说省事太多。为什么主控选 F745 而不是更便宜的 F103F745 的 I2C 外设支持更灵活的时序配置在长线缆、多从机场景下更容易调通而且 M7 内核处理浮点温度换算和滤波算法几乎不占 CPU留给通信协议栈的余量充足。如果只是单点采集F103 确实够用但 HVAC 项目通常要挂多路传感器加多路继电器输出F745 的余量更让人放心。2. PJ85718DM 与 STM32F745VG 核心细节解析2.1 PJ85718DM 的本地与远程测温原理PJ85718DM 的核心是一个带隙温度传感单元加一个 16 位 ADC。本地通道测的是芯片自身结温这个值在芯片贴板之后会受 PCB 热传导影响所以布局时要注意远离发热元件。远程通道的原理稍微绕一点芯片会交替输出两个不同电流的激励源到远程二极管上二极管在不同电流下的正向压降差与绝对温度成正比芯片通过测量这个压差反推出远程温度。这个原理叫 ΔVBE 法是业界远程测温的标准做法。远程通道的关键参数是串联电阻补偿。远程二极管和芯片之间如果走线较长线路电阻会引入误差PJ85718DM 支持通过配置寄存器设置补偿电阻值抵消这部分影响。我实测下来走线在 20cm 以内、线径 0.2mm² 的情况下不补偿的误差大概在 1~2℃补偿之后能压到 0.5℃ 以内。这个细节很多初次做远程测温的人会忽略导致数据一直偏高。本地和远程通道的转换速率可以独立配置。转换速率越高功耗越大但响应越快。HVAC 场景温度变化慢我一般把本地设成 1 次/秒远程设成 1 次/2 秒兼顾响应和功耗。芯片还支持单次转换模式需要读数时才触发一次适合电池供电的低功耗节点。2.2 STM32F745VG 的 I2C 外设配置要点F745 有三个 I2C 外设都支持标准模式100kHz和快速模式400kHzI2C1 和 I2C2 还支持快速模式1MHz。PJ85718DM 最高支持 400kHz所以我用快速模式就够了。配置的时候有几个点必须注意第一是上拉电阻。I2C 是开漏总线必须外接上拉。400kHz 下我一般用 2.2kΩ如果总线电容大挂的从机多、走线长要适当减小到 1.5kΩ 甚至 1kΩ。上拉太小会增加功耗太大会导致上升沿变缓、通信失败。我踩过的坑是用 10kΩ 上拉跑 400kHz波形上升沿明显变圆偶尔 NACK换成 2.2kΩ 后一次通过。第二是时钟延展。PJ85718DM 在转换期间可能会拉低 SCL 做时钟延展F745 的 I2C 外设默认支持这个特性但要在初始化时确认I2C_CR1的NOSTRETCH位没有被置位。如果误开了禁止时钟延展通信会随机失败。第三是 DMA。如果挂多路传感器轮询建议给 I2C 配 DMA把数据搬运从 CPU 手里解放出来。F745 的 DMA 控制器和 I2C 配合很成熟配置好之后 CPU 占用率能从 15% 降到 3% 以下。2.3 本地与远程通道的数据融合策略两个通道的数据拿到之后不能直接上报得做融合处理。我的做法是本地温度作为参考基准远程温度作为主测量值两者做交叉校验。如果远程温度与本地温度差值超过 20℃正常场景下不应该这么大就判定远程探头可能脱落或短路触发告警。这个逻辑在 HVAC 场景很实用因为远程探头装在管道里时间长了可能松动。数据融合还包括滑动平均滤波。我用了 8 点滑动窗口每来一个新数据就挤掉最旧的取平均后上报。这样能平滑掉偶发的干扰尖峰又不会引入太大延迟。窗口大小可以调温度变化快的场景用 4 点慢的场景用 16 点。注意滑动平均的窗口大小要和采样率匹配。如果采样间隔是 1 秒窗口 8 点意味着 8 秒的延迟对于 HVAC 这种慢过程完全可接受但如果你做的是快速温度巡检就得缩短窗口。3. 实操过程与核心环节实现3.1 硬件连接与上电检查先讲接线。PJ85718DM 的 SDA、SCL 分别接 F745 的 I2C1_SDAPB7和 I2C1_SCLPB6VCC 接 3.3VGND 共地。远程通道的 D 和 D- 接一个 NPN 三极管的基极和发射极或者用专用远程二极管封装三极管集电极接地。这里有个细节远程二极管必须用低 VBE 的型号普通整流二极管压降太大会导致测温范围受限。上电之后先别急着写代码用示波器看 SCL 和 SDA 的静态电平。正常应该是都被上拉到 3.3V。如果某根线是低电平说明有器件把总线拉死了先排查焊接和器件方向。我遇到过一颗传感器焊反SDA 一直被拉低查了半天才发现是封装方向搞错了。上电检查通过后用逻辑分析仪抓一次 I2C 波形确认地址对不对。PJ85718DM 的 7 位地址是 0x48具体看型号后缀有的批次是 0x49写地址是 0x90读地址是 0x91。如果抓不到 ACK先降速到 100kHz 试试还不行就检查上拉和电源。3.2 I2C 底层驱动编写我用的是 HAL 库但 HAL 的 I2C 函数在某些场景下会有超时问题所以关键读写我改成了寄存器级操作。初始化代码如下static void I2C1_Init(void) { hi2c1.Instance I2C1; hi2c1.Init.ClockSpeed 400000; 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; HAL_I2C_Init(hi2c1); }注意NoStretchMode必须设为 DISABLE允许从机做时钟延展。DutyCycle在快速模式下选 2:1 或 16:9 都行2:1 兼容性更好。读温度的函数封装成两个一个读本地一个读远程。PJ85718DM 的本地温度寄存器是 0x00远程温度寄存器是 0x01都是 16 位高字节在前。温度值的高 8 位是整数部分低 8 位是小数部分分辨率 1/256℃。换算公式float raw_to_celsius(uint16_t raw) { int16_t signed_raw (int16_t)raw; return signed_raw / 256.0f; }这里有个坑原始数据是有符号的负温度时高字节是 0xFF 开头如果直接当无符号处理会得到巨大的正数。必须先转成 int16_t 再除。3.3 远程通道补偿电阻配置远程通道的补偿电阻配置寄存器是 0x0B写入的值对应芯片内部的补偿档位。具体对应关系要看数据手册的表格我实测下来 20cm 走线对应写入 0x03 比较合适。配置流程是先写配置寄存器进入待机模式再写补偿寄存器最后写配置寄存器退出待机并启动转换。void pj85718_set_remote_compensation(uint8_t comp) { uint8_t cfg; i2c_read(PJ85718_ADDR, 0x09, cfg, 1); cfg | 0x80; // 进入待机 i2c_write(PJ85718_ADDR, 0x09, cfg, 1); i2c_write(PJ85718_ADDR, 0x0B, comp, 1); cfg ~0x80; // 退出待机 i2c_write(PJ85718_ADDR, 0x09, cfg, 1); }补偿值不是越大越好过补偿会导致读数偏低。我的经验是先用一个已知温度的水浴做标定从 0 开始逐档增加找到误差最小的档位。如果现场没有水浴条件可以用冰水混合物0℃做单点校准虽然不如多点标定准但比不校准强得多。3.4 数据上报与远程通信本地和远程温度采集完之后通过 UART 送到以太网模块或 4G 模块。我用的协议是简单的自定义帧帧头 0xAA 0x55然后是本地温度2 字节、远程温度2 字节、状态字节、校验和。状态字节的 bit0 表示本地通道有效bit1 表示远程通道有效bit2 表示远程探头告警。上报周期我设成 5 秒一次这个频率对 HVAC 监控足够又不会把通信流量撑爆。如果检测到温度超过阈值比如远程温度超过 60℃立即触发一次紧急上报不等周期到。void build_report_frame(float local, float remote, uint8_t status, uint8_t *frame) { int16_t l (int16_t)(local * 256); int16_t r (int16_t)(remote * 256); frame[0] 0xAA; frame[1] 0x55; frame[2] (l 8) 0xFF; frame[3] l 0xFF; frame[4] (r 8) 0xFF; frame[5] r 0xFF; frame[6] status; frame[7] frame[0] ^ frame[1] ^ frame[2] ^ frame[3] ^ frame[4] ^ frame[5] ^ frame[6]; }校验和用异或简单够用。如果通信环境恶劣可以换成 CRC16但会增加计算量。F745 有硬件 CRC 外设用起来也不费劲。3.5 上位机数据解析与可视化上位机我用 Python 写了个简单的解析脚本串口读数据、解析帧、存 CSV、画实时曲线。核心逻辑是状态机先找 0xAA再找 0x55然后收 6 个字节校验通过就入库。import serial import struct ser serial.Serial(COM3, 115200, timeout1) buf bytearray() while True: data ser.read(64) buf.extend(data) while len(buf) 8: if buf[0] 0xAA and buf[1] 0x55: frame buf[:8] checksum 0 for b in frame[:7]: checksum ^ b if checksum frame[7]: local struct.unpack(h, frame[2:4])[0] / 256.0 remote struct.unpack(h, frame[4:6])[0] / 256.0 status frame[6] print(fLocal: {local:.2f}C, Remote: {remote:.2f}C, Status: {status:02X}) buf buf[8:] else: buf buf[1:]这个脚本跑起来之后温度曲线能实时刷新历史数据存成 CSV 方便后续分析。如果要做成正式产品上位机可以换成 Grafana 或者自己写个 Web 界面但调试阶段这个脚本足够用了。4. 常见问题与排查技巧实录4.1 I2C 通信失败排查表现象可能原因排查方法解决措施完全无 ACK地址错误、器件未上电逻辑分析仪抓波形确认地址和电源核对数据手册地址测量 VCC偶发 NACK上拉电阻过大、总线电容大示波器看上升沿时间减小上拉到 1.5kΩ 或 1kΩ通信一段时间后死锁从机时钟延展未处理检查 NOSTRETCH 配置确保允许时钟延展数据全 0xFF器件未初始化、寄存器地址错读器件 ID 寄存器核对初始化流程和寄存器映射温度值跳变剧烈电源噪声、地线干扰示波器看电源纹波加 0.1uF 去耦电容单点接地这张表是我调试过程中实际遇到并记录下来的基本覆盖了 90% 的 I2C 问题。新手遇到通信失败先按表排查能省大量时间。4.2 远程测温精度问题远程通道最容易出的问题是读数偏高。原因通常有三个串联电阻未补偿、远程二极管型号不对、走线受干扰。排查顺序是先确认二极管型号必须是低 VBE 的小信号管比如常见的 2N3904 的基极-发射极结就可以用。然后检查补偿寄存器是否配置。最后看走线远程通道的两根线要尽量靠近走最好双绞远离功率线。我遇到过一次远程温度比实际高 8℃ 的情况查了半天发现是远程二极管旁边有个稳压芯片在发热热量通过 PCB 传到了二极管上。把二极管挪远之后恢复正常。这个教训是远程探头虽然叫远程但如果物理上离热源太近测的还是热源温度不是环境温度。4.3 数据上报丢包问题串口转以太网模块在数据量大时可能丢包。我的解决方法是加一个发送缓冲区采集到的数据先入队通信模块空闲时再发。队列深度设成 32 帧正常情况下根本用不满但突发告警时能保证不丢数据。另外上报帧之间要加间隔不能连续猛发。我设的是每帧之间至少 50ms 间隔给通信模块留出处理时间。这个值可以根据模块性能调整但不要低于 20ms否则容易触发模块的内部缓冲溢出。4.4 长期运行稳定性嵌入式设备最怕跑几天就死机。我在主循环里加了看门狗喂狗还加了 I2C 总线恢复机制如果连续 10 次读失败就手动发送 9 个 SCL 脉冲把总线解锁然后重新初始化 I2C。这个机制救过我好几次尤其是在现场有强干扰源的情况下。电源部分也要注意。HVAC 现场通常有 24V 直流供电我用一个隔离 DC-DC 降到 5V再用 LDO 降到 3.3V 给主控和传感器。隔离电源能有效阻断现场的地环流对通信稳定性提升明显。如果成本允许I2C 总线也可以加隔离器但一般没必要做好电源隔离就够了。提示看门狗喂狗不要在中断里做要在主循环里做。如果在中断里喂狗主循环卡死了狗还在被喂看门狗就失去意义了。5. 实操心得与扩展思路这套方案我前后调了大概两周从硬件焊接、驱动编写、精度校准到长期稳定性测试走完了完整流程。最大的体会是数字温度传感器虽然省心但省心是建立在正确配置和合理布局基础上的。I2C 的上拉、时钟延展、远程补偿这几个点任何一个没处理好都会导致数据不可信。如果后续要扩展我有几个方向可以考虑。一是增加多点位远程通道用多路模拟开关切换不同的远程二极管一颗 PJ85718DM 就能轮询多个测点成本增加很少。二是加入本地告警逻辑温度超限时直接驱动继电器切断设备不依赖上位机提高系统可靠性。三是把数据上报改成 MQTT 协议直接对接云平台省掉自建服务器的麻烦。对于刚入门的开发者我的建议是先把单点本地测温跑通确认 I2C 通信和温度换算没问题再加远程通道。远程通道的调试难度比本地高一个量级如果一上来就两个通道一起调出了问题很难定位是哪个环节的毛病。分步走每步都验证是最稳妥的路子。最后分享一个小技巧PJ85718DM 的配置寄存器里有一个单次转换位写 1 触发一次转换转换完成后自动清零。用这个模式配合外部中断可以在需要的时候才唤醒传感器特别适合低功耗场景。我试过用这个模式把整机平均功耗从 15mA 降到 3mA 以下对于电池供电的巡检节点来说续航能翻好几倍。