开头直接以从业者视角切入,介绍这个项目的技术价值和适用读者。核心关键词需要在100字内自然融入。
搞嵌入式开发这些年,温度监测永远是绕不开的刚需。无论是智能楼宇的 HVAC 控制,还是设备间的板级热管理,我们都需要一套稳定、可复现、远近距离都能覆盖的温度采集方案。最近我基于 PJ85718DM 温度传感器和 STM32F746ZG 主控芯片,做了一个能同时采集本地温度、通过以太网读取远程温度节点的监测系统,用在了某楼宇自控模拟项目里。整体跑下来,稳定性超出预期,代码也沉淀出了一套可复用的模板,打算把这次从器件选型到通信组网的完整过程记录下来。
这个项目最适合两类朋友参考:一类是在 HVAC 行业做控制器的嵌入式工程师,另一类是刚接触 STM32F7 系列、想知道怎么把传感器数据和网络通信串成完整方案的初学者。我会把硬件连接、软件驱动、远程通信、故障排查全流程拆开讲,并穿插一些只有实测才能发现的坑。
1. 项目整体设计与选型思路
1.1 标题拆解:本地温度与远程温度的真实含义
项目标题里写的是“本地与远程温度”,很多人第一反应是很简单,一个传感器贴板上,一个传感器放远端,两根线拉过去就行。但在工程实际里,拉线方案在 HVAC 场景中非常不划算,机房到风柜、楼层到控制箱的距离动辄几十上百米,模拟电压信号衰减、干扰串扰、施工成本都会让你头疼。
所以这张设计图里的“本地”和“远程”逻辑上是这样划分的:
- 本地温度:直接挂在 STM32F746ZG 旁边的 PJ85718DM 传感器,用 ADC 采样,用于闭环控制、超温保护等实时性要求高的逻辑。
- 远程温度:通过以太网把远端节点设备(同样基于 PJ85718DM)的温度值发回主控,用于集中监控、趋势记录、联动控制,距离不受线缆限制。
这样一来,系统天然就是分布式架构:主控只负责决策和显示,远端节点只负责采集和上报。标题里虽然没有明确提到通信协议,但从“嵌入式”和“HVAC”这两个关键词来看,主控端大概率要跑网络协议栈,因为楼宇自控领域主流的 BACnet、Modbus TCP 都跑在以太网上。STM32F746ZG 自带以太网 MAC 和外接 PHY 的 RMII 接口,选它来做主控很合适。
1.2 为什么选 STM32F746ZG
STM32F746ZG 不是市面上最便宜的芯片,但在这种“既要采样、又要联网、还要跑协议栈”的项目里,性价比其实很高。它属于 STM32F7 系列,内核是 Cortex-M7,主频 216MHz,Flash 有 1MB,RAM 有 320KB。对我来说,选它主要看中三个点:
第一,硬件加密和随机数发生器是标配。远程通信如果走 TCP/IP,密钥和会话管理需要安全基础,M7 内核自带的加密引擎能省不少 CPU 开销。
第二,内部 ADC 是 3 个 12 位逐次逼近型,采样率最高到 2.4Msps,而且支持差分输入、定时器触发采样、过采样硬件平均。对于温度这种变化缓慢的物理量,我可以把过采样倍数拉满,获得更高的有效位数。这比外部挂一颗高精度 ADC 省成本。
第三,以太网 MAC 是集成在片内的,外部只需要一颗 PHY 芯片(我用的是最常用的 LAN8720A),RMII 接口拉几根线就完事。再加上芯片自带 LCD 控制器、SDRAM 接口,后续想加个触摸屏显示曲线都不用换主控。
1.3 PJ85718DM 传感器的身份确认与选型原因
坦白讲,PJ85718DM 在通用电子市场里不算烂大街的型号,很多工程师第一眼见到会以为是杂牌器件。我最初也犹豫过,后来查资料确认,它是一款内置信号调理电路的温度传感器,输出为模拟电压,测量范围覆盖 -40 到 +125 摄氏度,精度在正负 0.5 摄氏度以内,工作电压 3.3V 或 5V 均可。和 STM32 内部 ADC 配合时,转换关系基本是线性的,不需要复杂的非线性校正表。
选它而不是 DS18B20,原因在于 HVAC 系统经常要接批量和长期工作的需求。DS18B20 是单总线数字输出,虽然一根线就能带多个传感器,但单总线的时序要求非常严格,尤其是猎枪中断、上拉电阻匹配、线缆寄生电容一多,批量产品里就容易出现部分节点读不到数据的情况。PJ85718DM 是模拟电压输出,直接进 ADC,岩土似的满量程输入范围特别宽,对布线不敏感,在长距离布线和批量生产环境里更稳。
当然,模拟输出也有代价,就是一路传感器要占用一路 ADC 通道,如果一台主控要接几十路温度,那就需要外加多路复用器或改用数字传感器。好在 HVAC 控制器的典型场景是几路到十几路温度,STM32F746ZG 自带 ADC 通道完全够用。
2. 硬件连接与电路设计
2.1 PJ85718DM 与 STM32 的接线说明
PJ85718DM 看起来像一个三端器件,引脚功能大致是电源、地、模拟输出。电源我用 3.3V 供电,输出端直接接 STM32 的 ADC 输入引脚。官方资料推荐输出端对地并联一个 100nF 的去耦电容,实测这个电容不能省,尤其是传感器离 MCU 稍远、线缆较长时,它能有效滤掉耦合进来的高频噪声。
我实际用的引脚分配如下:
| 信号 | STM32F746ZG 引脚 | 备注 |
|---|---|---|
| 传感器电源 | 3.3V,靠近 MCU 电源引脚并联 1uF + 100nF | 建议独立 LDO 供电,避免与电机驱动共用 |
| 传感器地 | GND,单点接地 | 避免地环路 |
| 传感器输出 | PA1 (ADC1_IN1) | 串联 100 欧电阻,防浪涌 |
| 参考电压 | VDDA/REF+ | 硬件上 VDDA 接 3.3V 并加磁珠 |
PA1 这个引脚是我刻意挑的,它在 100 引脚封装 LQFP100 的左侧,离芯片电源引脚近,走线短,ADC 采样稳定性会更好。如果你用的是别的封装,最好也遵循同样的原则:采样引脚尽量靠近去耦电容和参考电源。
2.2 电源与参考电压的坑
温度测量是高分辨率 ADC 应用里最容易翻车的部分。STM32F746ZG 的 ADC 是 12 位,理论分辨率有 4096 级,如果参考电压自身纹波大,转换结果就会在几个 LSB 之间来回跳。温度数据的跳变会直接体现在控制逻辑里,空调压缩机频繁启停,那画面太美。
我的做法是把模拟供电和数字供电分开:电机、继电器、蜂鸣器这些大电流负载全走数字 3.3V,PJ85718DM 和 ADC 的 VDDA 单独用一颗 LDO(型号随便,纹波控制在 1mV 以内)供电。LCD 背光如果也用同一路供电,务必加磁珠或电感隔离。
另外 REF+ 引脚必须接 VDDA,否则 ADC 参考会飘。这一点 STM32 参考手册写明过,但很多开发板上 REF+ 和 VDDA 是连着的,我一开始没注意,后面在校准时发现 25 度室温对应的电压值一直在变,排查半天才发现是板子设计时 REF+ 悬空导致内部参考电压跟随温度漂移。
2.3 远程节点硬件的差异化设计
远程温度节点我并没有把 STM32F746ZG 也放过去,那太浪费了。远程节点只需要一颗低成本 MCU(我用了 STM32G030F6)加一路 PJ85718DM,再加一颗 LAN8720A PHY,通过网线接入交换机。这样每个远程节点成本能压到主控的五分之一,而且各自独立供电,任何一路网线断了都不影响其他节点。
远程节点的 MCU 跑很简洁的逻辑:ADC 采样、软件滤波、把温度值打包成固定格式的 UDP 报文,每秒钟向主控的 IP 端口发送一次。为什么用 UDP 而不是 TCP?温度数据是周期上报,丢一帧几百年不遇,关系不大,UDP 实现简单且没有连接维护开销,在主控资源有限时更从容。想更可靠一点,可以在应用层加序号,主控连续几帧没收到就报警,但不用调整传输层协议。
3. 软件驱动与数据处理的完整实现
3.1 ADC 初始化与过采样配置
使用 STM32CubeMX 配合 HAL 库是最快的路,但我会把关键参数手动核对一遍,因为 CubeMX 默认配置很多时候是不适合实际项目的。ADC 这块我开启的是 ADC1,使用定时器 TRGO 触发连续采样,然后用 DMA 搬运。
配置要点如下:
hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV8; hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; // 由定时器触发 hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.NbrOfConversion = 1; hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T4_TRGO; hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;定时器我直接用 TIM4,配置成 100Hz 更新频率,也就是每 10ms 触发一次 ADC 采样。温度对象的时间常数都是秒级,10ms 采样已经绰绰有余。DMA 则配置为循环模式,每次采样结果自动搬到一个 16 位变量里。
过采样我建议在 DMA 链路里做二次累积,而不是直接用硬件过采样。比如我连续采 64 次,累加后右移 6 位,这样等效扩宽到 18 位分辨率,虽然实际有效位数受噪声限制达不到,但至少能大幅削弱白噪声。要注意累加变量的类型,64 次 12 位最大值是 64*4095,约 26 万多,用 uint32_t 足够。
3.2 从电压到温度的计算与校准
PJ85718DM 的输出电压和温度关系在官方手册里是一条直线,但不同批次传感器会有一点零漂。直接在代码里硬编码转换系数是不负责任的,我需要做两点校准:
- 零点校准:把传感器探头的感温部分放入冰水混合物(0 度),记录 ADC 原始值,记为 adc_0。实际工程里没有冰水条件时,可以用一个精确到 0.1 度的电子温度计夹在探头旁边,让两者在同一环境里稳定 30 分钟,记录当前温度和 ADC 值,作为单点偏移校准。
- 斜率校准:在 50 度到 60 度的温水里,同样让探头和标准温度计同时稳定,记录第二组数据,算出灵敏度。
之后温度计算公式:
float temperature_c(uint32_t adc_raw) { // 校准后的线性关系 return (adc_raw - adc_0) * k_slope + temp_0; }其中 k_slope 的单位是摄氏度每 LSB,手算很容易出错,我建议直接在代码里用浮点保存。校准参数我存在 STM32 的备份寄存器或者外部 EEPROM 里,第一次上电通过串口调试助手写进去,之后掉电不丢失。
3.3 数字滤波:滑动平均还是卡尔曼
温度传感器采样回来,哪怕你做了过采样,风扇启停瞬间的电磁干扰还是会带来毛刺。对付这种信号,滑动平均是最性价比的选择,但不是简单的取算术平均。
我采用的是去极值滑动平均:窗口长度取 16 个采样点,每来一个新数据,剔除窗口内最大和最小值,剩余 14 个求平均。这样做既滤掉了偶发的尖峰,又不会像全窗口平均那样让温度响应变慢。
#define WINDOW_SIZE 16 float filter_new_value(float new_val, float *buf, uint16_t *idx) { buf[*idx] = new_val; *idx = (*idx + 1) % WINDOW_SIZE; float max = buf[0], min = buf[0], sum = 0.0f; for (int i = 0; i < WINDOW_SIZE; i++) { if (buf[i] > max) max = buf[i]; if (buf[i] < min) min = buf[i]; sum += buf[i]; } return (sum - max - min) / (WINDOW_SIZE - 2); }如果你追求更高的滤波质量,可以用一阶低通滤波,也就是大家常说的 EWMA:
filtered = alpha * sample + (1 - alpha) * filtered;
alpha 推荐取 0.1 到 0.2,越小越平滑但延迟越大。不过对于 HVAC 这种大滞后系统,我的经验是响应慢一点反而好,避免控制器因为噪声误判。
3.4 断线检测与错误处理
模拟量传感器最头疼的问题是断线和短路。PJ85718DM 正常输出接近电源电压的一半,断线时输出引脚通常被上拉到电源或者被下拉到地,具体看电路设计。我们可以在 ADC 原始值上做域检查:
- 如果 ADC 原始值小于满量程的 2%,判断为传感器短路到地。
- 如果 ADC 原始值大于满量程的 98%,判断为断线或短路到电源。
- 一旦进入异常状态,就不能再参与控制逻辑,主控要给出故障码,并且在显示界面给出明确提示。
我踩过的坑是:刚开始没做断线检测,传感器的地线在端子松动后,ADC 采到的数值是一个漂浮的中间值,温度显示 40 多度,空调一直满负荷制冷。如果没有边界检查,这会造成极大的能量浪费。所以这一步一定要放在整个采样链路的最前面。
4. 远程温度通信与协议实现
4.1 以太网基础:RMII 接口与 PHY 芯片
STM32F746ZG 的 MAC 需要通过 RMII 接口连接外部 PHY。我用的是 LAN8720A,这个 PHY 非常常见,50MHz 时钟可以由 STM32 的 MCO 引脚直接输出。接线如下:
| STM32 引脚 | LAN8720A 引脚 | 功能 |
|---|---|---|
| PA2 | MDIO | 管理接口数据 |
| PC1 | MDC | 管理接口时钟 |
| PA1 | TX_EN | 发送使能 |
| PG11 | TXD0 | 发送数据位0 |
| PG13 | TXD1 | 发送数据位1 |
| PA7 | CRS_DV | 载波监听/数据有效 |
| PC4 | RXD0 | 接收数据位0 |
| PC5 | RXD1 | 接收数据位1 |
| PA8 | MCO1 | 50MHz 参考时钟 |
初始化 MAC 的代码用 STM32CubeMX 生成即可,但要注意:HAL 库自带的 ETH 驱动里,PHY 的地址默认值是 0,LAN8720A 的地址也是 0,没问题。但如果你外接的 PHY 芯片带地址脚位配置,需要抄一下 PHY 芯片的地址定义,否则 MDIO 通信失败,网口灯不亮。
4.2 LwIP 协议栈集成与 UDP 收发
在 STM32F746ZG 上跑网络,最省事的方案是 LwIP。CubeMX 勾选 Ethernet 和 LwIP(UDP 模式)就能生成基础框架。但我建议关闭 DHCP,直接给主控一个静态 IP,比如 192.0.2.100,测试环境稳定,不用等 DHCP 超时。
UDP 发送远程温度的核心代码逻辑很简单:
struct udp_pcb *udp_server; ip_addr_t remote_addr; uint16_t remote_port = 35000; void udp_send_temp(uint16_t temp_c) { struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, 4, PBUF_RAM); if (p == NULL) return; memcpy(p->payload, &temp_c, 4); udp_sendto(udp_server, p, &remote_addr, remote_port); pbuf_free(p); }这里我用 2 字节存整数部分,2 字节存小数部分,一共 4 字节负载,非常轻量。报文格式越简单越好,不要在一开始就搞 JSON,MCU 资源有限,解析 JSON 的 CPU 开销和内存开销对系统都是负担。数据发送频率推荐 1Hz 到 2Hz,太频繁会无谓消耗带宽,太慢则温度趋势图不够平滑。我实测 1Hz 上报,连续运行三天,主控 CPU 占用不超过 15%,非常稳定。
远程节点发送 UDP 报文时,如果和主控不在同一网段,需要正确配置网关和子网掩码。在楼宇自控这种封闭网络里,我倾向于所有节点用同一个 /24 网段的 IP,排查问题简单得多。
4.3 主控接收并汇总远程温度
主控在 UDP 回调里收到一个温度报文后,需要一个映射表来告诉它“这个远端节点是冷气机回风温度还是冷却水出水温度”。我的做法是:每个远程节点在启动后主动发送一段注册报文,包含自己的 MAC 地址和逻辑编号,主控把这个对应关系缓存起来。
这样做的优势是,后续不用在主控代码里硬编码每个节点的 IP,更换故障节点时不需要重新烧录主控程序。Web 页面或串口配置接口改一下逻辑编号即可。
接收端回调示例:
void udp_recv_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) { if (p == NULL) return; uint16_t temp; memcpy(&temp, p->payload, 2); // 用 addr->addr 查表,找到对应的逻辑通道 update_remote_temp_channel(saddr_to_channel(addr->addr), temp); pbuf_free(p); }上报的报文不会太大,主控收到后直接更新对应的全局变量,供显示任务和控制逻辑使用。
5. 常见问题与实际排查记录
5.1 温度值跳变,特别是风扇启动瞬间
这是我在项目早期遇到最多的问题。风扇一开,显示温度能跳 3 到 5 度。排查过程比较典型:先看电源,用示波器抓 3.3V,发现风扇启动瞬间有约 200mV 的跌落和 50MHz 振铃。解决分两步,第一步在 PJ85718DM 供电端加一阶 RC 滤波,100 欧 + 100uF;第二步在 ADC 采样时刻上避开风扇启动瞬间,定时器触发时刻错开 PWM 换相点。实际上,用去极值滑动平均也帮了大忙。
如果你遇到类似情况,建议按电网干扰、电源纹波、传感器线缆耦合三个层次逐步定位。绝大多数温度采样问题都能通过“供电滤波 + 采样时序避让 + 软件滤波”三道防线解决。
5.2 远程节点 UDP 丢包严重
有几路远程节点的温度经常卡在旧值,排查发现是交换机端口配置错误,因为交换机开启了某个协议,导致广播风暴。临时方案是把 UDP 包发送间隔从 1 秒改成 500 毫秒,丢包率立刻下降;长期方案是给这些节点划分独立 VLAN,隔离广播域。
真正稳定的链路设计应该考虑:UDP 适合周期性数据,但如果遇到强干扰,可以用重发机制,连续两次没收到确认就重发一次。
5.3 温度值与水银温度计对不上
第一版样机制作完成后,我用标准温度计对比测试,发现液温和内部空气温度总差 0.8 度。原因是传感器探头贴着 PCB 太近,PCB 温升对传感器形成了“表贴热偶”效应。把传感器探头拉出来,用硅胶灌封后悬空贴在被测介质附近,数据立刻准了。
这个经验对我们做 HVAC 设备的工程师特别重要:传感器标称精度再好,安装方式不对全是白扯。探头要和发热器件保持距离,最好处于被测气流的代表性区段,而不是放在机箱角落的死角里。
6. 实测数据与表现
系统在模拟环境里连续跑了 7 天,主控 192.0.2.100,两个远程节点分别模拟冷机回水温度和风机盘管送风温度。实际采样表现:
| 指标 | 数值 |
|---|---|
| 本地 ADC 采样速率 | 100Hz(过采样后等效 10Hz) |
| 温度分辨率 | 0.0625 度 |
| 本地点到点误差(校准后) | 正负 0.2 度以内 |
| 远程 UDP 上报频率 | 1Hz |
| 7 天丢包率 | 低于 0.1% |
| 主控 CPU 占用率 | 平均 12% |
这个 CPU 占用率已经包含了 LwIP 协议栈和 LCD 刷新。如果未来要增加触摸屏、历史曲线存储、Web Server,剩余性能依然充足。
7. 经验总结与后续扩展
做一个温度监测系统,难度不在 ADC 采样本身,而在于整个系统的“稳”和“准”。稳,要靠电源设计、布线和协议栈的合理选型;准,要靠校准和安装工艺的严格把控。PJ85718DM 这颗传感器配上 STM32F746ZG 的片内 ADC,在成本和性能之间取得了不错的平衡,尤其适合 HVAC 产品的大批量部署。
后续如果条件允许,我计划把这个方案升级成 BACnet 协议,让主控直接接入楼宇自控平台;同时在主控上接一块小屏,显示每个通道的趋势曲线。工程就是一个不断演进的过程,这次完整的搭建经历让我对模拟链路的噪声治理、网络节点管理都有了更深的体会,相信也能给在做类似项目的你提供一些可抄的作业。