搞过HVAC项目或者做过嵌入式环境监测的兄弟应该都有同感:温度测点又多又分散,回风口、送风管、室外新风、室内几个房间,每个点都拉一堆模拟量线回来,调试起来简直想骂人。最近我在做一个空调机组的温度监测改造,方案上选了PJ85718DM这颗单总线数字温度传感器,搭配STM32F107VC做主控,把本地显示和远程网络上报一次做完了。
这套组合解决的核心问题有两个:一个是就地侧,主控板旁边挂几个传感器,OLED直接把所有温度打出来,现场调试不用开电脑;另一个是远程侧,传感器通过一根双绞线分布在几十米外的风管和房间里,全部温度汇总到MCU后,再走以太网发给上位机。也就是说,本地温度和远程温度都纳入了同一套监测体系。
如果你是做嵌入式设备、楼宇自控或者暖通配套控制的,这篇文章可以直接当参考笔记用。我不打算只贴代码,而是把选型逻辑、硬件设计、单总线驱动、LwIP网络上报、调试现场踩过的坑从头到尾说透,项目里遇到同类需求可以直接抄作业。
1. 需求拆解与方案选型:为什么是单总线传感器加F107
1.1 HVAC场景下温度监测的真实需求
先说现场需求。暖通空调的温度监测点和普通消费电子不太一样,测点不但多,而且位置极其分散。一个典型的空调机组,回风温度传感器在机组回风口,送风温度在盘管后风道内,防冻开关旁边还要有一个低温保护测点,室外新风温度探头得拉到屋外,室内侧可能还分布在好几个房间。这些点离控制器的距离短则两三米,长则三五十米。
本地监测和远程监测是同时存在的需求。本地指设备机柜就地能看温度,远程指把温度送到楼宇中控或者值班室电脑上。以前的做法很传统:每个测点用一只NTC热敏电阻加一路模拟量输入,测温范围窄、非线性还要查表校准,最麻烦的是长距离传输时导线电阻会直接影响精度。后来改PT100加变送器,精度上去了,但调理电路和变送器成本也跟着上去了,一个点就是大几十块,十几个点成本就很可观。
对比之后,PJ85718DM这类单总线数字传感器优势很明显。温度值直接在传感器内部完成ADC转换,通过单总线协议以数字方式输出,全程不受线缆电阻和模拟干扰影响。一条总线上可以挂多个器件,因为每个器件出厂有唯一的ROM序列号。这样HVAC现场常见的多点分散测温和长线传输问题,一根双绞线就解决了,不用每个点都单独拉一堆模拟线。
1.2 器件选型逻辑:为什么不是普通单片机加模拟量处理
PJ85718DM属于单总线数字温度传感器这一类,工作电压范围覆盖3.0V到5.5V,测温范围覆盖常见HVAC场景的-55℃到+125℃,分辨率可以通过配置寄存器选择,默认精度在±0.5℃左右。这些参数对于机房、风管、室内环境监测完全是够用的。这里要提醒一句,不同厂商的单总线温度芯片在时序细节上多多少少有差异,拿到实物先对一下器件手册,我下面讲的代码逻辑是基于最常见的单总线协议来写的,只要你手头的PJ85718DM遵循同类协议,基本可以通用。
主控选STM32F107VC,最核心的原因就是它自带以太网MAC控制器。当时也纠结过用F103和F407,F103便宜但内置MAC都没有,要联网就必须外挂SPI转以太网芯片,多一颗芯片意味着多一份BOM成本,还要把SPI口和中断引脚占掉。F407性能过剩,用在温度采集这种低频场景有点浪费。F107VC这颗芯片是Cortex-M3内核,主频72MHz,内部有256KB Flash和64KB SRAM,带以太网MAC、CAN、USB OTG和多路串口。对于HVAC这类既要跑本地逻辑又要走网络协议的场景,它的定位就是刚好合适。
做HVAC设备还有一个现实问题:工业现场电源环境并不干净,变频压缩机启停、电磁阀动作都会带来干扰。F107的工作温度范围和抗干扰能力在同类MCU里属于比较稳的,这也是工业类产品选型时的一个重要考量。
1.3 整体架构:本地温度和远程温度是如何组织在一起的
项目里我把“本地”和“远程”拆成了两个维度,这样容易说清楚。
空间维度的远程,指传感器不在主控板上,而是通过长线分布在远端。我用两条独立的总线:第一条总线放本地传感器,挂在控制器的PCB端子座上,测机柜环境温度和箱体进风温度;第二条总线通过一个接插件引出,用一根屏蔽双绞线拉到几十米外的风道和房间里,挂上六到八个传感器。两条总线由MCU的两个不同GPIO引脚驱动,互不干扰,某一条总线被干扰时另一条还能正常工作。
信息维度的远程,指数据从设备端通过网络上传。F107内置以太网MAC,外接一颗PHY芯片就能跑LwIP协议栈,我在板子上实现了TCP Server,上位机通过网络调试工具或者自写的Python脚本发起连接,就能读取所有传感器的实时温度。相当于每个节点本身是一台小服务器,上位机随时可以拉数据。
整体数据流向是这样的:MCU轮询两条总线上的所有传感器,把温度值存到一个结构体数组里,OLED本地循环显示,同时TCP服务线程监听以太网端口,收到上位机的读取指令后把整个温度数组打包发送出去。本地显示和远程读取用的是同一份数据源,不需要额外同步。
2. 硬件搭建要点:总线、接口与外围电路
2.1 单总线传感器接入电路:上拉电阻不是随便选的
单总线传感器的电气接口非常简单,核心就是一根数据线DQ加上电源和地。数据线必须接一个上拉电阻到VCC,这是因为器件工作在开漏模式,没有总线主机拉高的时候,数据线要靠上拉电阻维持高电平。
上拉电阻的取值是第一个跳跃点。短距离、传感器少的时候,4.7kΩ是标准值,稳定可靠。但是HVAC现场动不动就是三五十米的长线,线缆分布电容和电阻都会影响信号上升沿。线越长,分布电容越大,上升沿就越缓,时序上就容易超时。我在远程那条总线上实测,30米双绞线配4.7k上拉,偶尔出现读不到应答的情况,换成2.2k上拉后问题就消失了,因为电阻变小,充电电流变大,上升沿变快,时序裕量被找回来了。
这里有个反直觉的坑:不要在总线上并太大的电容。很多人习惯在电源引脚旁边加个100nF退耦电容,这没错,但如果把这颗电容直接并到DQ线上,它会和上拉电阻形成RC延迟,让上升沿变得更慢,反而把时序搞坏。DQ线上的ESD保护或者滤波,建议只保留寄生电容级别,我一般不加额外的电容,靠屏蔽线的分布电容就够。
供电方式上,PJ85718DM支持外部供电和寄生供电两种模式。实际项目里我毫不犹豫选了外部供电,每个传感器单独接VDD和GND,DQ走数据。寄生供电看着省一根线,但在长线场景下问题非常多:传感器做温度转换时需要从DQ线上窃取电流,总线电压会被拉低,主机侧如果上拉能力不够强,轻则转换超时,重则直接导致复位失败。HVAC现场变频器一启动,电源波动本来就不小,别在这种地方省一根线。
线缆选择上,我用的屏蔽双绞线,屏蔽层单端接地。普通平行线我在调试中发现,附近有继电器吸合时偶尔会出现CRC校验错误,换屏蔽线后这类问题明显减少。
2.2 STM32F107VC最小系统与外置PHY芯片
STM32F107VC的最小系统部分属于常规操作,但还是有几个点值得说。MCU使用8MHz无源晶振,配合两个20pF到22pF的负载电容,通过PLL倍频到72MHz运行。注意晶振布局要尽量靠近MCU的OSC_IN和OSC_OUT引脚,负载电容的地要短而粗地回到MCU的GND引脚,这个细节直接影响主时钟的稳定性,以太网通信的时钟源又对稳定性敏感,不能马虎。
以太网部分,F107内置的MAC控制器不能直接接RJ45,外面必须配一颗以太网PHY芯片。接口模式我选了RMII,因为RMII相比MII少了一半引脚,而且信号速率固定,100Mbps模式下只需要50MHz的参考时钟。RMII的引线要求是等长处理,特别是TX_EN、TXD0、TXD1这些信号,长度差控制得越小越好,我用的是两层板,布线时尽量让这些信号走同一层且路径接近,实测通信稳定。
RMII参考时钟有几种来源:用外部50MHz有源晶振直供PHY、由PHY内部倍频后输出、或者由MCU的MCO引脚输出50MHz。我用的是外部50MHz无源晶振配合PHY内部的振荡电路,这样PHY和MAC能在同一时钟源下工作,省去时钟不同步的麻烦。上电后调试的第一步,一定要能正确读到PHY芯片的寄存器ID,不然后面所有网络调试都是白费力气。
RJ45我用了带网络变压器的集成座,内部有隔离变压器,这是以太网物理层的标配,不要省。隔离变压器的中心抽头要按PHY芯片手册要求接偏置电压,接法错了会出现能连接但无法通信的诡异问题。
2.3 本地显示与交互接口设计
本地显示选用了一块0.96英寸的I2C接口OLED,4个引脚就搞定,对于显示多个温度点完全够用。I2C地址一般是0x3C或0x3D,买到的模块不确定时可以在初始化时扫描一下地址,这个习惯能省不少排查时间。
按键交互设计了三键方案:一个模式切换键,用于在“本地点选”和“远端点选”画面之间切换;两个阈值调整键,用于调整本地报警上限和下限阈值。报警执行部分是一个有源蜂鸣器加一个双色LED,超上限报警时亮红灯,低于下限报警时亮黄灯,蜂鸣器通过三极管驱动,GPIO直接驱动蜂鸣器的电流不够,容易把引脚烧坏。蜂鸣器驱动引脚还要并联一个续流二极管,不然断电瞬间的反向电动势会打坏三极管。
GPIO分配可以给个参考,我直接列在下面的表里:
| 功能 | MCU引脚 | 说明 |
|---|---|---|
| 本地总线DQ | PA0 | 开漏输出,外接4.7k上拉 |
| 远程总线DQ | PA1 | 开漏输出,外接2.2k上拉 |
| OLED SDA | PB7 | I2C数据线 |
| OLED SCL | PB6 | I2C时钟线 |
| 蜂鸣器 | PB0 | 经三极管驱动 |
| 报警LED | PB1 | 双色灯 |
| 模式键 | PA4 | 内部上拉 |
| 阈值加/减键 | PA5、PA6 | 内部上拉 |
| ETH RMII | PD0-PD5 | F107专用引脚 |
| ETH MDIO/MDC | PA2、PC1 | PHY管理接口 |
按键全部使用内部上拉,外部不用再放上拉电阻,省一点BOM。OLED和蜂鸣器都是3.3V供电,与MCU电平一致,不需要电平转换。
3. 软件驱动与通信协议实现
3.1 用GPIO模拟单总线时序:关中断是必须的
单总线协议的基石就是精确的时序。整个通信过程是一系列特定时间长度的电平跳变,对HAL库这种经过多层封装的函数来说,延时精度太差,必须用寄存器级别操作加空循环延时来实现。
先定义总线结构体,这样两条总线可以复用同一套驱动代码:
typedef struct { GPIO_TypeDef *port; uint16_t pin; } ow_bus_t; #define OW_BUS_LOCAL {GPIOA, GPIO_PIN_0} #define OW_BUS_REMOTE {GPIOA, GPIO_PIN_1}GPIO配置成开漏输出模式。开漏模式下写0就是把引脚拉低,写1就是把引脚释放,由外部上拉电阻拉高,完美契合单总线协议的电平要求。
复位时序是整个通信的起点:主机拉低总线至少480us,然后释放,等待传感器回应。传感器上电后会主动拉低总线60us到240us作为存在脉冲。代码实现:
uint8_t ow_reset(ow_bus_t *bus) { uint8_t presence = 0; __disable_irq(); /* 拉低总线 520us */ GPIO_ResetBits(bus->port, bus->pin); delay_us(520); /* 释放总线 */ GPIO_SetBits(bus->port, bus->pin); delay_us(40); /* 读取存在脉冲 */ presence = !GPIO_ReadInputDataBit(bus->port, bus->pin); __enable_irq(); delay_us(200); return presence; }读写一位时序有严格区分。写0时隙和写1时隙都是60us到120us,区别在于低电平持续时间。写0要求整个时隙都保持低电平;写1则是先拉低1到15us,然后释放总线让上拉电阻拉高。读时隙稍微复杂一点,主机先拉低1到6us,然后释放,在释放后的15us窗口内读取引脚电平,因为传感器如果返回0,会在这段时间内把总线拉低。
uint8_t ow_read_bit(ow_bus_t *bus) { uint8_t bit = 0; __disable_irq(); GPIO_ResetBits(bus->port, bus->pin); delay_us(5); GPIO_SetBits(bus->port, bus->pin); delay_us(9); bit = GPIO_ReadInputDataBit(bus->port, bus->pin); __enable_irq(); delay_us(60); return bit; }我刻意在时序操作前后调用__disable_irq()关闭全局中断,这是非常重要的一步。如果不关中断,延时过程中一旦有定时器中断或者串口中断插入,时序就会超时,单总线通信当场失败。这也是为什么我不用HAL_Delay,HAL延时依赖系统滴答定时器,但滴答定时器中断本身就会打乱时序。
3.2 多传感器ROM枚举与温度数据读取
单总线传感器每个器件在出厂时都有一个64位的唯一ROM序列号,这个设计让多个传感器可以在同一条总线上共存。读取流程分两条路线:
如果一条总线上只挂一个传感器,主机可以发跳过ROM指令(0xCC),直接进入温度转换或者读取暂存器操作,简单快速。但我要的是多点采集,所以必须先把总线上所有传感器的ROM码读出来,存到数组里,后续通信用匹配ROM指令(0x55)逐个指定访问。
ROM搜索算法是单总线协议最经典的一段逻辑,思路是逐位读取并记录分支:先读第一位两次,如果两次读到的值不同,说明当前总线上只有一个器件在该位是0或1,可以确定方向;如果两次都读到0,说明有分叉,需要记录选择路径。这个算法说起来简单,但在位级别上做回溯还是容易绕晕,我实现时把搜索过程放在了温度采集初始化阶段,设备上电后执行一次,之后传感器位置固定,ROM码不会变,直接缓存使用。
读取温度的核心流程是这样的:
- 复位总线
- 发送匹配ROM指令(0x55),然后发送该传感器的64位ROM码
- 发送温度转换指令(0x44)
- 等待转换完成,12位分辨率下典型转换时间在数百毫秒量级,我配置成12位模式后等待了约800ms
- 再次复位总线,发送匹配ROM指令
- 发送读暂存器指令(0xBE),连续读取9个字节
完整读取一个传感器的关键代码:
int16_t ow_read_temperature(ow_bus_t *bus, uint64_t rom) { uint8_t data[9]; uint8_t crc = 0; int16_t raw = 0; if (!ow_reset(bus)) return TEMP_READ_ERR; ow_write_byte(bus, 0x55); /* 匹配ROM */ for (int i = 0; i < 8; i++) ow_write_byte(bus, (uint8_t)(rom >> (i * 8))); ow_write_byte(bus, 0x44); /* 启动温度转换 */ delay_ms(800); if (!ow_reset(bus)) return TEMP_READ_ERR; ow_write_byte(bus, 0x55); for (int i = 0; i < 8; i++) ow_write_byte(bus, (uint8_t)(rom >> (i * 8))); ow_write_byte(bus, 0xBE); /* 读暂存器 */ for (int i = 0; i < 9; i++) data[i] = ow_read_byte(bus); crc = ow_crc8(data, 8); if (crc != data[8]) return TEMP_READ_ERR; raw = (data[1] << 8) | data[0]; return (int16_t)raw; }CRC校验必须做。单总线传感器返回的9个字节中,最后一个字节是前8字节的CRC8校验值,多项式是X^8+X^5+X^4+1。在长线传输和高频干扰环境下,CRC偶尔会报错,程序里对返回错误的情况做一次重试,重试三次仍失败就丢弃该测点本次数据并做故障标记,上层逻辑就不会把错误温度显示出来了。
3.3 本地数据处理:滤波、报警与OLED显示
直接从传感器读到的温度虽然经过了CRC校验,但还是偶尔会有毛刺,尤其是电机启停的瞬间。我在显示和上报之前加了一道滤波,方案是中值滤波加滑动平均。
中值滤波的做法是连续读取5次温度值,取中间的第三个值作为有效值。这个方法对付单点尖峰特别有效,因为哪怕五次里有一两次读到异常值,排序后异常值都会被排在两端,不会进入最终结果。滑动平均则是在中值滤波的基础上再做一次加权平均,让曲线更平滑,避免画面上的温度跳变太快。
滤波的代码如下:
float temp_filtered(float new_value) { static float buffer[5]; static uint8_t idx = 0; float temp[5], sum = 0; buffer[idx] = new_value; idx = (idx + 1) % 5; for (int i = 0; i < 5; i++) temp[i] = buffer[i]; /* 简单冒泡排序,取中值 */ for (int i = 0; i < 5; i++) for (int j = i + 1; j < 5; j++) if (temp[j] < temp[i]) { float t = temp[i]; temp[i] = temp[j]; temp[j] = t; } for (int i = 1; i < 4; i++) sum += temp[i]; return sum / 3.0f; }浮点运算是有点浪费MCU资源,但这个场景下72MHz的Cortex-M3跑起来毫无压力,而且温度采集频率不高,不用过度优化。
本地报警逻辑放在滤波之后。系统每个采样周期把当前温度与预设阈值比较,超上限时置位报警标志,OLED画面温度数字变红,蜂鸣器以2Hz频率鸣响,直到温度回落并进入回差区间才解除报警。回差很重要,没有回差的话,温度在阈值附近波动会导致报警频繁通断,现场听起来非常烦人。
OLED驱动部分要注意,0.96寸OLED一屏最多能显示多少个字符要提前规划好。我的界面是这样布局的:第一行显示“LOCAL”加上本地温度,第二行显示远端总线上前三个测点温度,如果远端超过三个点,通过按键切换到下一页。显示格式固定为xx.x℃,避免浮点转字符串时占用过多时间。
3.4 基于LwIP的以太网远程温度上报
STM32F107VC移植LwIP协议栈,用无操作系统的RAW API就可以跑起来。F107有64KB SRAM,跑一个TCP Server绰绰有余,但LwIP的配置还是要裁剪一下:TCP窗口不要开太大,PBUF池数量和大小按实际负载设置,我用的是4个TCP连接上限,每个连接的接收窗口控制在1460字节,内存开销非常可控。
移植LwIP时需要把底层接口对接好:F107的以太网MAC驱动提供发送和接收函数,PHY的状态查询通过MDIO/MDC管理接口完成。我在初始化时做了PHY地址匹配,从F107的以太网寄存器里读到PHY ID后,与硬件实际配置比对,确认链路协商到的速率。
TCP Server的初始化流程如下:
static struct tcp_pcb *server_pcb; static err_t server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { char *cmd; if (err == ERR_OK && p != NULL) { cmd = (char *)p->payload; if (strncmp(cmd, "READ_ALL", 8) == 0) { char reply[256]; int len = snprintf(reply, sizeof(reply), "TEMP: %.2f, %.2f, %.2f, %.2f, %.2f\r\n", temp[0], temp[1], temp[2], temp[3], temp[4]); tcp_write(pcb, reply, len, TCP_WRITE_FLAG_COPY); } pbuf_free(p); tcp_recved(pcb, p->len); } return ERR_OK; } static err_t server_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { tcp_recv(newpcb, server_recv); return ERR_OK; } void ethernet_tcp_server_init(void) { server_pcb = tcp_new(); tcp_bind(server_pcb, IP_ADDR_ANY, 9090); server_pcb = tcp_listen(server_pcb); tcp_accept(server_pcb, server_accept); }主循环里每隔10ms调用一次tcp_tmr(),这个函数驱动LwIP的TCP定时器,不调用的话TCP连接状态机不会自动超时重传,连接会挂在半开状态。我在实际调试中还发现一个问题:上位机断电或者程序退出时,TCP连接不会干净地关闭,板子这边如果不处理,就会出现旧连接占用PCB资源的情况。解决办法是在accept回调里对同一个客户端的重复连接做清理,或者依靠LwIP的TCP_KEEPALIVE选项自动探测死连接。
3.5 通信帧格式与上位机脚本
TCP上报的协议我设计得比较简单,因为自用为主,不需要复杂方案。客户端发送一个ASCII命令,服务器返回一帧数据,数据帧格式如下:
| 字段 | 长度 | 说明 |
|---|---|---|
| 帧头 | 1字节 | 0xAA |
| 传感器数 | 1字节 | 0x01-0x10 |
| 温度区 | N×2字节 | 每个温度用int16,放大100倍,单位0.01℃ |
| CRC8 | 1字节 | 对前面所有字节计算 |
温度用int16放大100倍传输,而不是直接用float,这是网络传输的常规操作。float在跨平台通信中有字节序和表示格式差异的问题,int16没有这个问题,固定按小端序传输即可。上位机只需要做一次除法就能还原成摄氏温度。
为了远程监控调试方便,我写了一个简单的Python上位机脚本:
import socket import struct s = socket.create_connection(("192.168.1.233", 9090), timeout=5) s.sendall(b"READ_ALL") header = s.recv(1) if header == b"\xaa": count = s.recv(1)[0] payload = s.recv(count * 2 + 1) temps = [struct.unpack("<h", payload[i:i+2])[0] / 100.0 for i in range(0, count * 2, 2)] print("本地温度: %.2f℃" % temps[0]) print("远程温度: " + ", ".join("%.2f℃" % t for t in temps[1:]))命令设计为READ_ALL和READ_ONLY两个指令,实际操作中还可以扩展SET_THRESHOLD指令,让上位机直接改写报警阈值,省去现场按键操作。通信连接方式用TCP,主要考虑HVAC监控室上位机和设备之间可能跨交换机,TCP有重传机制,比UDP可靠得多。
4. 完整联调过程与关键参数记录
4.1 分级联调:从点灯到全系统打通
整个系统不是一次性上电就通的,我习惯严格遵守从小到大的联调路径。第一级是电源和最小系统,上电后先用万用表确认3.3V输出正常,然后用一个LED点灯程序验证MCU核心和晶振工作正常。
第二级调试单总线部分。先用一根很短的杜邦线接一个传感器,用串口打印复位应答和ROM码。这个阶段最容易暴露的问题是GPIO配置错误,比如忘记配置成开漏模式,或者上拉电阻没有焊接,表现就是复位永远不成功。把第一个传感器读通之后再往总线上挂第二个、第三个,逐步增加节点数,观察搜索到的ROM码数量是否与实际一致。
第三级调试OLED显示。等到总线上的温度数据能稳定读到了,再驱动OLED显示,检查是否有花屏、地址不对、字库缺字等问题。OLED地址错误时屏幕完全不亮或者全亮,这个很好判断。
第四级调试以太网。先用网线连接路由器和开发板,通过串口打印DHCP分配到的IP地址,然后在PC上ping一下,通了再跑TCP Server,用Python脚本连接测试。这个阶段我踩过最大的坑是RMII时钟问题导致PHY无法协商,后面单独讲。
第五级接入真实的HVAC试点环境。把远程总线的传感器分别放到回风口、送风管、室外新风处,本地传感器留在控制机柜,开启空调运行,观察温度变化趋势是否合理。送风温度在制冷模式下应该明显低于回风温度,室外温度如果比室内高或者低都取决于季节,这些规律能帮我快速判断传感器是否安装正确。
4.2 关键参数与配置记录
整理一份项目参数记录,方便复现时对照:
| 参数项 | 配置值 | 备注 |
|---|---|---|
| MCU主频 | 72MHz | 8MHz晶振×9倍频 |
| 本地总线上拉 | 4.7kΩ | 短线短距离 |
| 远程总线上拉 | 2.2kΩ | 30米双绞线实测 |
| 传感器数量 | 本地2个,远程6个 | 最多可扩到8个每路 |
| 温度分辨率 | 12位 | 转换时间约800ms |
| 采样周期 | 1秒 | 轮询一遍所有测点 |
| 滤波策略 | 中值+滑动平均 | 5次取中值再平均 |
| 以太网接口 | RMII | PHY外置 |
| TCP端口 | 9090 | 服务端监听 |
| 静态IP | 192.168.1.233 | 避免DHCP抖动 |
| 报警回差 | 1℃ | 防止阈值反复触发 |
远程总线长度超过30米时,2.2k上拉还能不能稳,取决于线缆质量和现场干扰强度,建议先用示波器看一下上升沿和下降沿,上升沿时间控制在1us以内比较保险。
4.3 实测数据:本地和远程到底准不准
联调完成后,我把所有测点跑了一天,对比水银温度计的数据如下:
| 测点 | PJ85718DM读数 | 水银参照 | 偏差 |
|---|---|---|---|
| 本地机柜 | 26.4℃ | 26.1℃ | +0.3 |
| 本地进风 | 24.9℃ | 25.0℃ | -0.1 |
| 远程回风 | 27.2℃ | 27.0℃ | +0.2 |
| 远程送风 | 15.8℃ | 15.9℃ | -0.1 |
| 远程室外 | 18.3℃ | 18.1℃ | +0.2 |
| 远程房间 | 25.6℃ | 25.5℃ | +0.1 |
从数据看,最大偏差在0.3℃以内,对于HVAC环境监测完全够用。上位机通过TCP读取到的数据与OLED本地显示一致,两者来自同一份内存数据,不存在二次采集差异。网络读取延时在局域网内基本可以忽略,每秒刷新一次完全没压力。
连续24小时运行时,远程总线出现过几次CRC错误,但重试机制都自动恢复了,没有丢点数。调试中还发现一个现象:送风温度测点在制冷刚启动时,读数会先掉到很低再回升,这是传感器贴在风管壁上的热惯性导致的,不是硬件问题,属于正常现场现象。
5. 常见故障与排查技巧实录
5.1 总线上读不到任何设备
这是初次上电最常遇到的问题。排查顺序坚决固定,不要跳步。
第一步用万用表量总线上三个引脚的电平,DQ应该是高电平,如果DQ被拉低到地,说明总线上有传感器短路或者接线错误,这时候复位永远读不到应答。第二步检查上拉电阻是否焊接,没焊上拉的话DQ悬空,电平不确定,也会有诡异表现。第三步用示波器观察复位波形,看是否存在480us以上的低电平脉冲,以及低电平释放后是否有应答脉冲。如果示波器看到主机复位脉冲但没看到应答脉冲,把传感器在断电状态下重新插拔一次,排除接触不良。
远程总线上的设备排查还有个技巧:把总线上的传感器从远端逐段断开,从靠近主控的一端开始试,每断一段就扫一次总线,很快就能定位到故障节点。千万不要在总线上挂着一堆设备的时候做替换测试,那样很难区分是哪个节点把总线拖死的。
常见问题整理:
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 复位无应答 | DQ接反或短路 | 量电平,查接线 |
| 复位无应答 | 上拉电阻缺失 | 补焊4.7k或2.2k |
| 应答不稳定 | 线缆过长干扰大 | 换屏蔽线或降上拉电阻 |
| 只识别到部分传感器 | ROM搜索中止 | 关中断时间过长,查延时函数 |
| 读温度全为85℃ | 上电后未等待转换完成 | 转换指令后等足时间 |
85℃这个现象特别说一下,这是单总线传感器的上电复位默认值。如果你读到85℃且所有点都一样,说明程序在温度转换还没完成时就去读暂存器了,读到了初始化后的原始数据。解决办法就是等足转换时间再读,别省这几百毫秒。
5.2 温度读数乱跳或明显不合理
读数和实际温度差好几度,或者数值一直在跳,这个问题的根源多半不在传感器本身,而在电源和安装。
供电不稳是最常见的元凶。传感器是数字器件,对电源质量仍然敏感,供电电压纹波太大会导致内部ADC参考电压抖动,读数自然不准。我的对策是所有传感器的VDD和GND在远端统一并联一个100nF电容,这个电容不能放在DQ线上,只能放在电源引脚上。其次传感器不要和继电器、接触器共用一个电源回路,MCU用一路隔离电源,总线传感器用另一路,中间用DC-DC隔离或者至少用地线分开走。
安装位置也是一个大坑。传感器直接贴在金属风管壁上,温度反映的是管壁温度而不是空气温度,和真实空气温度差好几度。正确的做法是让传感器探头伸进风管内约10到20cm,悬在气流中,不要碰管壁。室外测点要加防辐射罩,避免阳光直射导致读数偏高。空调传感器、水管温度计其实都有类似问题,安装方式对测量精度的影响常常比传感器本身的精度误差还大。
软件上如果确认硬件没问题,滤波参数也要重新审视。中值滤波的窗口取5次的话,如果总线上有传感器连续两次读取失败,重试期间插入了旧值,会导致转折处的毛刺,这种情况下改成时间序列判断更合理:如果新值比上一次值高出超过8℃,且持续不到两个采样周期,直接判定为无效数据丢弃。
5.3 以太网连接不上或频繁断开
TCP连接不上的排查,我的顺序是先物理层,再网络层,最后应用层。
物理层的第一步是读PHY的寄存器。F107初始化后如果能正确读出PHY ID和链接状态寄存器,说明RMII接口和时钟没问题。如果PHY寄存器读出来全是0xFF,物理层就没通,重点检查RMII时钟和PHY地址配置。第二步看RJ45指示灯,通常Link灯亮代表物理链路已经协商成功。
网络层就是ping。如果PC能ping通板子的IP,但TCP连不上,多半是TCP Server没有正常启动或者监听端口不对。应用层问题则集中在LwIP的tcp_tmr没调用、accept回调里没调用tcp_recv、接收缓存区太小导致数据丢失这几个地方。
还有个细节:上位机的TCP连接在数据读取后要主动调用recv清空数据,否则服务端pcb的接收窗口会慢慢填满,连接看起来是通的,但数据发不出去。这个问题在长连接、高频读取的场景下特别容易出现。
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| PHY寄存器读不到 | RMII时钟未配置 | 检查50MHz时钟源 |
| 能ping通但连不上TCP | 端口被防火墙拦截 | 检查PC防火墙或换端口 |
| TCP连上后收不到数据 | recv回调未注册 | 注册tcp_recv并处理pbuf |
| 连接频繁断开 | LwIP定时器未驱动 | 主循环调用tcp_tmr |
| 上报数据字节错位 | 帧格式解析错误 | 用调试助手逐字节核对 |
5.4 远程总线长线部署的专项问题
远程总线用于HVAC现场时,最大的敌人是两个:线缆压降和电磁干扰。
线缆压降影响的是传感器供电。远程传感器到主控板的距离超过30米时,如果VDD线径太细,传感器转换期间的大电流会在导线上产生明显压降,导致传感器供电电压低于最低工作电压,表现就是远程节点间歇性丢失。解决方法是加大供电线径,或者直接采用24V供电再在每个传感器附近用LDO降到3.3V。第二个方案在工业现场更常见,因为24V是标配,而且压降远小于3.3V供电场景。
电磁干扰对单总线的影响主要是上升沿被拉缓或者被毛刺打断。应对方法是总线使用屏蔽双绞线,屏蔽层单端接地;如果干扰非常大,可以在总线末端加一个约100Ω的阻尼电阻到地,这能在不破坏时序的前提下抑制振铃。我在一个变频器功率很大的试验间里试过,加阻尼电阻后CRC错误率显著下降。
最后是节点数限制。单总线的理论挂载数量上限有几十个,但实际长线部署时,每增加一个节点,总线上寄生电容就大一点,时序裕量就少一点,可靠性会逐步下降。一般建议每条总线控制在8到10个以内,确实有更多测点就分两路总线。不要贪多,出问题时排查成本比多挂几个传感器高得多。
结尾
这套本地加远程温度监测方案,我在实际项目里用了很长时间,过程中最大的感悟是:温度采集本身不复杂,复杂的反而是总线的物理层处理和现场的干扰防护。如果你只是把一堆传感器接上然后读数据,嵌入式本身就能跑通;但要把这些数据稳定地用在HVAC控制上,就得把上拉电阻、屏蔽布线、供电隔离这些基础细节做扎实,这些功夫不显眼,却是整套系统可靠性的真正保障。
最后再分享一个小技巧:如果你也遇到温度读数在某个时刻统一跳变的问题,别急着怀疑传感器,先看看是不是主控板上的ADC那条高精度参考电压被电磁干扰拉偏了,或者看看传感器电源上是不是有间歇性的高电平毛刺。我后来在总线传感器供电线上加了一个共模电感,然后再也没出现过这个现象。希望这篇笔记能帮你少踩几个坑,省下的调试时间拿去喝茶都是好的。