ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于PJ85718DM和dsPIC33F的HVAC温度监测系统设计与实现

基于PJ85718DM和dsPIC33F的HVAC温度监测系统设计与实现

做暖通空调的嵌入式项目,温度监测永远是第一个要稳定下来的模块:回风温度、送风温度、盘管温度、室外温度,每一路都直接影响压缩机和风机策略。前阵子我设计一款 HVAC 控制器时,测量链路选用了 PJ85718DM 单总线数字温度传感器,主控用 dsPIC33FJ256GP710A,实现了“本地直连 + 远程总线”的双层测温:控制器附近温度由板载传感器直接采集,分布在风管、百叶箱、室外机组里的测温点则挂在同一条 1-Wire 总线上,由主控统一轮询,再通过 RS485 送到触摸屏或上位机。这套结构不大,但它把嵌入式端和 HVAC 端最常见的需求都覆盖了。下面把选型、硬件、代码和踩坑过程完整写一遍,给正在做类似温度采集系统的朋友一个可复现的参考。

1. 问题拆解:暖通系统里“本地温度”和“远程温度”到底指什么

1.1 两种测温点的业务定义

很多人一看到“本地和远程温度”就觉得是“本机显示”和“云端查看”,其实在 HVAC 控制器里,这两个词首先是物理位置的划分。本地温度通常指控制器所在区域的温度,比如回风温度、控制柜内部温度;远程温度指那些离主机有几米到几十米的测点,比如送风管道末端、室外冷凝器进风侧、水系统换热器的进出口。这些测点不能直接在 PCB 上走线,必须通过外部线缆引到控制器。

PJ85718DM 这类带唯一序列号的单总线传感器,天然适合干这个活。本地测温可以把它放在板子边缘,通过短排线引出;远程测温则把它装在金属套管或百叶罩里,用一段双绞线挂到总线上。同一颗传感器、同样的读时序,只是物理位置不同,软件上没差别。真正的差别在电气布线和现场环境:远程线要穿过强电区域,要耐受冷凝水和较大温差,还要支持维护时插拔更换。

1.2 嵌入式端与 HVAC 现场的约束差异

嵌入式设备内部,温度传感器离主控近,电源也干净,干扰主要来自开关电源和功率器件。这时候用 NTC、数字传感器都没有太大区别,PCB 布局注意避开热源就行。但 HVAC 现场完全是另一回事:线长、环境脏、变频器多,而且测点经常在检修口、风机盘管这类不好动手的位置。

单总线方案在这类场景的优势非常明显:从控制器出去只需要两根信号线(数据线加地线,供电再另加一根 VDD),不需要每个传感器单独接模拟调理电路。节点通过 64 位唯一 ROM 编码区分,换传感器不影响软件配置,总线短了或传感器坏了能被软件识别出来。我曾经在项目里对比过模拟量方案和数字单总线方案,前者在长线上受变频器共模干扰非常明显,后者只要把上拉电阻、线缆屏蔽和接地点处理到位,稳定性会好很多。

1.3 功能需求清单

在写代码之前,需求表要先定死。我当时按 HVAC 应用列了这样一份清单:

需求项目标值说明
测温范围-20℃ 至 +85℃覆盖回风、送风、室外测点常规范围
显示分辨率0.1℃内部计算用 0.0625℃,显示只保留一位小数
测量精度±0.5℃ 以内整条链路实测误差,含传感器和采集电路
远程节点数1-8 个一条总线最多挂 8 路,避免总线负载过大
刷新周期1 秒从启动转换到全部节点数据更新完成
断线识别支持连续 3 个周期无有效数据则标记离线
上报方式RS485 总线接入触摸屏或 IoT 网关
控制联动预留 PID 接口温度数据滤波后供压缩机/阀位调节使用

dsPIC33FJ256GP710A 的资源完全撑得起这套需求。256KB 级 Flash、几十 KB 的 RAM,可以同时跑温度采集状态机、数字滤波、Modbus 协议解析和后续的控制算法,不用像 8 位单片机那样到处省内存。后面我会详细说软件怎么分层,这里先把硬件方案定下来。

2. 选型逻辑:为什么用 PJ85718DM 单总线传感器搭配 dsPIC33FJ256GP710A

2.1 传感器选型对比

传感器选型是整块电路板的第一个关键决定。HVAC 领域常见的选择有热敏电阻 NTC、铂电阻 PT100、4-20mA 变送器和数字单总线传感器,各有各的适用场景。我做过一个对比表格,直接把当时的想法摆出来:

方案精度水平布线成本MCU 资源开销抗干扰能力适用场景
NTC + ADC一般,需要查表/校准每点至少 2 根线加滤波占用 ADC 通道长线容易受干扰板内测温、短距离
PT100 + 调理电路高,需恒流源和差分放大每点 3-4 根线,成本高占用 ADC、运放、基准源好,但电路复杂高精度工业测点
4-20mA 变送器高,取决于变送器每点 2 根线,需配 24V 供电串电阻采样或 ADC很好数十米以上远传
PJ85718DM 单总线中等偏高,±0.5℃3 根线(VDD/GND/DQ)占 1-2 个 GPIO数字信号,较好几米到几十米多节点

结论很直接:测点数量在 2-8 个、距离在 50 米以内时,单总线数字传感器是效率和成本最均衡的方案。如果距离超过 50 米,或者现场对实时性要求到毫秒级,我会建议把单总线换成 RS485 分布式采集节点,但这个项目里明显不需要。

2.2 PJ85718DM 的关键电气特性

PJ85718DM 是典型的 1-Wire 数字温度传感器,我挑几个对硬件设计和软件编写最重要的参数列在这:

  • 测温范围 -55℃ 至 +125℃,满足 HVAC 的宽温要求;
  • 12 位分辨率时最小温度步进 0.0625℃;
  • 典型精度 ±0.5℃(在 -10℃ 至 +70℃ 范围内表现更好);
  • 内置 64 位唯一序列号,支持同一总线上多点寻址;
  • 温度转换时间在 12 位模式下典型值 750ms;
  • 供电范围 3.0V 至 5.5V;
  • 每个器件带片上 CRC 校验,可检测暂存器读取错误。

这里特别提醒一点:PJ85718DM 的分辨率是可以通过配置寄存器调整的,从 9 位到 12 位。9 位时转换时间很快,但分辨率只有 0.5℃;12 位最慢但最准。HVAC 温度控制对速度要求不高,我直接锁定 12 位模式,把转换时间吃满,换取最好的数据质量。如果后续做低功耗电池供电节点,可以把分辨率降到 10 位换取更短的唤醒时间。

2.3 dsPIC33FJ256GP710A 的资源盘点与外设分配

dsPIC33FJ256GP710A 是一颗 16 位数字信号控制器,它在这个系统里的角色不只是读温度,还要承担整个控制器的主控任务。我当时把它的外设分配成下面这样:

外设用途说明
GPIO RB81-Wire 数据线通过开漏/推挽切换实现单总线时序
UART1 + RS485 收发器远程上报接触摸屏或协议转换网关
UART2调试串口输出日志、温度曲线,调试完可关闭
Timer11ms 系统节拍驱动采集状态机和看门狗喂狗
Timer2转换等待计时记录 750ms 转换窗口
片上温度监控(如果有)板级过热保护用于控制柜内部高温告警

为什么不用一颗普通的 8 位单片机?因为后续要在温度基础上叠加风机 PID、压缩机启停优化、故障诊断,这些算法如果放在 8 位机上,写起来处处受限。dsPIC33F 有 DSP 引擎和单周期 MAC,做滑动平均、PID 运算时指令开销小得多。温度采集本身不重要,重要的是它给后续控制留出了余量。

2.4 采样周期设计中的关键计算

12 位模式下转换一次要 750ms,如果逐点转换 8 个传感器,总耗时就是 8×750ms,等于 6 秒才能刷新一遍,这在实际 HVAC 控制里不可接受。正确的做法是利用单总线的广播特性:先对所有传感器发一次 Skip ROM + Convert T,让总线上所有 PJ85718DM 同时开始转换,然后等 750ms,再逐个用 Match ROM 读回每个节点的暂存器。

这样做的总刷新周期是:

总周期 ≈ 转换时间 750ms + 逐个读取 8 个节点的时间 ≈ 750ms + 8 × 约 5ms(读到 9 字节) ≈ 790ms

刚好可以塞进 1 秒的刷新任务里。这个计算虽然简单,但它决定了整个软件状态机怎么设计:必须先广播转换,不能逐个转换。很多第一次接触 1-Wire 多节点的人在这里栽过跟头,后面代码部分我会再展开。

3. 硬件链路:本地直连通道与远程总线网络的搭法

3.1 本地温度通道的连接方式

本地传感器可以直接放在主板上,也可以放在控制器外壳内侧。我当时把一颗 PJ85718DM 放在板边,用短排线拉到回风口附近,避免直接贴在变压器和功率器件上,否则测出来的不是空气温度而是元件温度。

电气连接上,VDD 接 3.3V,GND 和主板地相连,DQ 接 dsPIC33FJ256GP710A 的 RB8。DQ 到 3.3V 之间放一颗 4.7kΩ 上拉电阻,这是短距离单总线最常规的取值。如果板内还有其他数字噪声源,可以在 DQ 上再并联一个 100pF 到 1nF 的小电容滤波,不能太大,否则上升沿会被拖慢导致时序失败。

本地通道还有一个容易被忽略的点:传感器封装本身会自热。PJ85718DM 在转换时会从 DQ 和 VDD 取几毫安电流,如果空气不流通,自热效应会让读数偏高 0.1-0.3℃。所以我建议板载传感器尽量放在空气流通的位置,或者把转换频率降到 1Hz,不要连续高速轮询,减少自热积累。

3.2 远程总线的拓扑设计与上拉电阻选择

远程部分是我花时间最多的地方。多个 PJ85718DM 挂在同一条总线上,拓扑最好做成菊花链式,也就是主控出来后,一个节点接一个节点地串过去,尽量避免在总线中间长出很长的分支线。分支超过 30cm 的“stub”会造成信号反射,长距离时特别明显。

上拉电阻不是随便选的。总线上每个节点都有一点输入电容,加上双绞线本身的线间电容,总线越长,负载电容越大。典型双绞线电容大约 100pF/m,20 米线就是 2nF。RC 时间常数直接影响 DQ 上升沿:

R = 4.7kΩ,C = 2nF → τ = 9.4μs R = 2.2kΩ,C = 2nF → τ = 4.4μs

1-Wire 读时序要求主机释放总线后大约 15μs 内完成采样,4.7kΩ 在 20 米时勉强够用,但余量不大。实测下来,超过 15 米我直接把上拉改成 2.2kΩ,效果更稳。反过来,如果距离只有 1-2 米,上拉用小阻值会导致传感器低电平时拉不下去,所以短距离开 10kΩ 反而更省电。

远程线缆我建议用带屏蔽的双绞线,屏蔽层单端接地。如果走线区域靠近变频器或者接触器,信号线上还要加双向 TVS 管,比如 5V 规格的,防止感应过压打坏传感器或主控 GPIO。

3.3 供电方案与地线处理

单总线传感器有两种供电思路:外部供电和寄生供电。PJ85718DM 支持寄生供电,数据线 DQ 在转换期间会被拉高,由总线给传感器内部电容充电。听起来省一根线,但多节点时非常不靠谱:几个传感器同时进入转换,转换电流可以达到十几毫安,总线驱动能力不足,VDD 内部跌落,转换结果直接错乱。

我强烈建议远程节点全部外部供电,也就是拉 3 根线到每个传感器:VDD、GND、DQ。虽然多一根线,但时序简单,供电稳定,调试成本低得多。如果现场只允许两根线,宁可改成总线供电方式或者用 RS485 远端采集模块,也别硬上寄生供电。

地线处理上,远程节点距离远的时候最容易出现地电位差。HVAC 系统里各个设备通常从同一配电柜取电,中性点电位基本一致,问题不大。但如果测点跨到了另一个配电回路,或者线缆很长,就要考虑两端用电平转换芯片或者光耦隔离。我处理过一次 30 米外测点通信不稳定,最后发现是地电位差导致 DQ 高低电平窗口偏移,把两端地线加粗并做了单点接地后问题消失。

4. 软件架构:一套能支撑多节点轮询的温度采集状态机

4.1 分层设计思路

软件不能一上来就写读温度的裸循环,尤其是主控还要处理 RS485 上报和控制任务。我把温度采集部分拆成了三层:

  • 接口层:负责 1-Wire 的复位、读写位、读写字节、CRC8 校验;
  • 节点管理层:维护 ROM 表、节点在线状态、故障重试;
  • 业务层:温度换算、数字滤波、量程判断、报警和协议上报。

这样拆的好处是:接口层如果换了传感器型号,只需要改底层时序;节点管理层如果改成 RS485 模块,只需要替换发现设备的方式。温度数据到业务层永远是“节点 ID + 温度值 + 状态”的统一结构,不会牵连上层逻辑。

整个采集过程用状态机驱动,而不是多个阻塞延时函数。原因很简单:dsPIC33FJ256GP710A 在等待 750ms 转换期间还要持续响应 UART 中断、处理按键和控制任务,一个阻塞的固定延时会让整个系统卡死。状态机让 CPU 在等待期间干别的活,等计时到了再切回温度读取。

4.2 上电枚举与 ROM 表生成

每个 PJ85718DM 都有一个不可修改的 64 位 ROM 编码,其中包含 8 位家族代码、48 位序列号和 8 位 CRC。上电后第一件事不是直接读温度,而是做一次总线搜索,把所有在线节点的 ROM 读出来存到数组里。

搜索算法就是先发 Reset,再发 Search ROM 命令 0xF0,然后逐位读出两个数据位:00 表示总线上有冲突(多个节点该位不一致),01 或 10 表示该位不同。按规则逐位寻址,最后可以枚举出所有节点的完整 ROM。这个过程看起来繁琐,但功耗和耗时都可控,上电跑一次就够了。如果现场传感器会插拔更换,每次开机都重新枚举,省去手工配置地址的麻烦。

设备被找到后,软件要给每个节点分配一个逻辑编号,比如“送风温度”“回风温度”“室外温度”,再给一个统一的数据结构:

typedef struct { uint8_t rom[8]; // 64位 ROM uint8_t enable; // 节点是否启用 float temp; // 当前温度 uint16_t error_cnt; // 连续错误计数 uint8_t state; // 在线/离线/未就绪 } temp_node_t;

4.3 广播转换与逐个读取的状态机

采集状态机我设计成这个样子:

TEMP_IDLE → TEMP_CONVERT(发 Skip ROM + Convert T) → TEMP_WAIT(等 750ms) → TEMP_READ(逐个 Match ROM + Read Scratchpad) → TEMP_PROCESS(换算、滤波、状态判断) → TEMP_REPORT(更新上报缓冲区) → TEMP_IDLE(下个周期)

这里最关键的细节是:Convert 阶段用 Skip ROM 让所有节点同时转换,Read 阶段必须用 Match ROM 逐节点读。如果读取阶段也发 Skip ROM,所有传感器会同时把数据放到总线上,互相拉低,读回来的全是 0xFF 或者乱码。

每个节点的读取错误处理也要有策略。单次 CRC 校验失败先不急着标记离线,可能是偶然干扰;连续 3 次失败才把节点状态置为离线。恢复机制是每个采集周期都继续尝试读取,只要有一次成功就把错误计数清零。这样现场临时插拔传感器后,系统能自动恢复,不需要重启。

4.4 非阻塞时间基准

状态机需要两个时间基准:一个是 1ms 的系统节拍,用于状态切换和超时判断;另一个是温度转换等待计时,精确到 750ms。我用 Timer1 产生 1ms 中断,在中断里递增一个全局 tick 变量,状态机只在判断“是否到 750ms”时读取这个变量。千万不能在 1-Wire 精确读写时序的中断里做耗时操作,否则时序会被打乱。

5. 代码落地:1-Wire 时序、CRC、温度换算与 RS485 上报实现

5.1 系统初始化与配置位设置

芯片配置位决定运行模式,这个地方出问题往往最隐蔽。我的配置位里特别注意关掉了看门狗,因为温度转换等待期间如果用软件喂狗,稍微一点延时偏差都会影响 1-Wire 采样点;如果不用软件喂狗,看门狗又会复位。调试阶段直接关掉 WDT,量产版本如果一定要开,也要放在主循环里喂,而不是放在 1-Wire 操作的临界区里。

#include <p33FJ256GP710A.h> #pragma config FOSC = HS // 外部高速晶振 #pragma config FWDTEN = OFF // 关闭看门狗,调试阶段务必关闭 #pragma config ICS = PLL // 使用 PLL 倍频 #pragma config FCMDIS = OFF #pragma config IESO = OFF #define FCY 40000000UL #define OW_PORT PORTBbits.RB8 #define OW_LAT LATBbits.LATB8 #define OW_TRIS TRISBbits.TRISB8 void system_init(void) { // 配置系统时钟为 40 MIPS CLKDIVbits.PLLPRE = 0; CLKDIVbits.PLLDIV = 41; // 具体倍频需按实际晶振和 PLL 寄存器设定 CLKDIVbits.PLLPOST = 0; PLLFBD = 0xFFFF; // 演示配置,实际按器件手册计算 // 1-Wire 数据线初始化为高阻输入,外部上拉 OW_TRIS = 1; // UART1 初始化,用于 RS485 U1MODEbits.STSEL = 0; U1MODEbits.PDSEL = 0; // 8N1 U1MODEbits.BRGH = 1; U1BRG = 129; // 波特率 9600,按 FCY 计算 U1STAbits.UTXEN = 1; U1STAbits.URXEN = 1; // RS485 方向控制引脚,例如 RC0,低电平接收,高电平发送 TRISCbits.TRISC0 = 0; LATCbits.LATC0 = 0; }

配置位的具体倍频值在不同晶振下不同,实际做项目时要以器件手册中的公式为准,我这段代码只是把结构演示出来。重点是 FWDTEN 必须关,否则整机会在 750ms 等待窗口期间被内部看门狗打断。

5.2 1-Wire 底层时序函数

1-Wire 的时序窗口全部在微秒级别,dsPIC 工作在 40 MIPS 时,单个指令周期是 25ns,所以延时函数要用指令数来精准控制。下面是我常用的底层函数,逻辑很直白:

static void delay_us(volatile unsigned int us) { while (us--) { __asm__ volatile("nop"); // 实际要按主频校准循环次数 } } static uint8_t ow_reset(void) { uint8_t present; OW_TRIS = 0; // 拉低 OW_LAT = 0; delay_us(480); OW_TRIS = 1; // 释放总线 delay_us(60); present = OW_PORT; // 读存在脉冲 delay_us(420); return (present == 0) ? 1 : 0; // 读到低电平表示有设备应答 } static void ow_write_bit(uint8_t b) { OW_TRIS = 0; OW_LAT = 0; if (b) { delay_us(8); // 写 1:短暂拉低后释放 OW_TRIS = 1; delay_us(70); } else { delay_us(65); // 写 0:保持低电平 OW_TRIS = 1; delay_us(8); } } static uint8_t ow_read_bit(void) { uint8_t b; OW_TRIS = 0; OW_LAT = 0; delay_us(2); OW_TRIS = 1; // 释放总线 delay_us(8); b = OW_PORT; // 采样 delay_us(60); return b; }

底层时序最怕的就是中断。如果在 ow_read_bit 采样前后来了一个长中断,采样点就错过了。因此调用 1-Wire 底层函数时,要么把中断全部关掉,要么保证中断服务程序足够短。我的做法是在采集状态机的单个节点读操作期间关闭其他无关中断,只保留 Timer1 的 tick 累加,但把 UART 中断挂起,读完之后再打开。

在 3.3V 供电下,PJ85718DM 的时序要求与 5V 供电基本兼容,但拉低后的恢复时间略敏感。如果你从 5V 平台移植到 3.3V 平台,不要直接沿用原来的延时参数,要用逻辑分析仪重新看波形调整。

5.3 多节点并行转换与逐个读取

完整的多节点温度刷新函数是这个系统的主干,我把它写成状态机的样子:

void temperature_task(void) { static uint8_t state = TEMP_IDLE; static uint8_t idx = 0; static uint16_t wait_ms = 0; switch (state) { case TEMP_IDLE: if (g_tick_1ms >= next_period) { state = TEMP_CONVERT; } break; case TEMP_CONVERT: if (ow_reset()) { ow_write_byte(0xCC); // Skip ROM ow_write_byte(0x44); // Convert T wait_ms = 0; state = TEMP_WAIT; } else { state = TEMP_IDLE; // 总线无设备,下一周期再试 } break; case TEMP_WAIT: wait_ms++; if (wait_ms >= 750) { idx = 0; state = TEMP_READ; } break; case TEMP_READ: if (idx < node_count) { read_node_temp(idx); // 内部 Match ROM + 读暂存器 idx++; } else { state = TEMP_PROCESS; } break; case TEMP_PROCESS: process_temperatures(); // 换算、滤波、离线判断 state = TEMP_IDLE; break; } }

read_node_temp 内部的流程是:Reset,发 Match ROM 命令 0x55,再发这个节点的 8 字节 ROM,然后发 0xBE 读暂存器,连续读 9 个字节,最后一个字节是 CRC8。这 9 个字节包含了温度 LSB、温度 MSB、报警阈值、配置寄存器和 CRC。

void read_node_temp(uint8_t idx) { uint8_t sp[9]; uint8_t i; if (!ow_reset()) return; ow_write_byte(0x55); // Match ROM for (i = 0; i < 8; i++) { ow_write_byte(node[idx].rom[i]); } ow_write_byte(0xBE); // Read Scratchpad for (i = 0; i < 9; i++) { sp[i] = ow_read_byte(); } if (crc8(sp, 8) == sp[8]) { node[idx].temp = scratchpad_to_temp(sp); node[idx].error_cnt = 0; node[idx].state = NODE_ONLINE; } else { node[idx].error_cnt++; if (node[idx].error_cnt >= 3) { node[idx].state = NODE_OFFLINE; } } }

CRC 校验相当重要。HVAC 现场变频器多,总线上的偶发干扰很常见,如果没有 CRC,一个位翻转可能让温度从 25℃ 直接跳到 85℃。PJ85718DM 的 CRC 多项式是 X^8+X^5+X^4+1,软件实现如下:

uint8_t crc8(uint8_t *data, uint8_t len) { uint8_t crc = 0; uint8_t i, j; for (i = 0; i < len; i++) { crc ^= data[i]; for (j = 0; j < 8; j++) { if (crc & 0x01) { crc = (crc >> 1) ^ 0x8C; } else { crc >>= 1; } } } return crc; }

5.4 温度换算与负温处理

暂存器里的温度数据是 16 位有符号数,但实际只用了高 12 位,低 4 位全零。换算很简单,把两个字节拼成 int16_t 右移 4 位,再乘 0.0625 就是摄氏度。负数温度时这个右移要小心,我用一种比较稳妥的写法:

float scratchpad_to_temp(uint8_t *sp) { int16_t raw; int16_t value; raw = (int16_t)((uint16_t)(sp[1] << 8) | sp[0]); value = (int16_t)(raw >> 4); // 12 位有符号数 return (float)value * 0.0625F; }

负温度场景在 HVAC 里不常见,但如果在北方冬季做室外测点,这个细节就很重要。如果你在某个编译器上发现负温换算不对,多半是右移被当成无符号处理了,换成先判断正负再手动补符号即可。

5.5 RS485 上报与 CRC16

温度数据最终要离开控制器。我用 RS485 接到触摸屏,协议上选用了带 CRC16 校验的帧结构。发送帧很简单:帧头、地址、数据长度、温度值、CRC16。CRC16 用 Modbus 标准多项式 0xA001 实现。

uint16_t crc16_modbus(uint8_t *buf, uint16_t len) { uint16_t crc = 0xFFFF; uint16_t i; uint8_t j; for (i = 0; i < len; i++) { crc ^= buf[i]; for (j = 0; j < 8; j++) { if (crc & 0x01) { crc = (crc >> 1) ^ 0xA001; } else { crc >>= 1; } } } return crc; } void rs485_send_frame(uint8_t addr, uint8_t *data, uint8_t len) { uint8_t buf[64]; uint8_t i; uint16_t crc; buf[0] = 0xA5; // 帧头 buf[1] = addr; // 地址 buf[2] = len; // 数据长度 for (i = 0; i < len; i++) { buf[3 + i] = data[i]; } crc = crc16_modbus(buf, 3 + len); buf[3 + len] = (uint8_t)(crc & 0xFF); buf[4 + len] = (uint8_t)(crc >> 8); LATCbits.LATC0 = 1; // RS485 切换到发送模式 for (i = 0; i < 5 + len; i++) { while (U1STAbits.UTXBF); U1TXREG = buf[i]; } while (U1STAbits.TRMT == 0); // 等待发送移位寄存器空 LATCbits.LATC0 = 0; // 切回接收模式 }

RS485 方向切换特别容易踩坑:最后一字节发完后,如果立刻把 DE 拉低,最后一个字节可能还在移位寄存器里,还没发完就被截断。我每次都在TRMT标志置位后再切换方向,这个细节看着小,实际丢包率能从 5% 降到几乎为零。

6. 实测与校准:精度、刷新率和常见偏差的处理

6.1 静态精度实测

硬件和软件跑通后,第一件事是静态精度测试。我把 8 个 PJ85718DM 全部放到同一个恒温环境里,对比基准温度计的读数,测试点在 0℃、20℃、60℃ 三档。实测结果是最大偏差 0.35℃,最小偏差 0.05℃,整体落在 ±0.5℃ 指标内。多节点一致性方面,同一批次传感器在相同环境下互相之间差距基本在 0.2℃ 以内,做普通 HVAC 温度监测完全够用。

如果你是做高精度计量用的设备,±0.5℃ 可能不够,但 HVAC 控制本身的温度滞后和气流波动都远大于这个误差。更值得花时间的反而是传感器安装位置能不能代表真实环境温度,这个误差往往有 1-2℃。

6.2 动态响应测试

传感器加了金属套管和防潮胶之后,热时间常数会明显变大。我从 20℃ 室温突然移到 60℃ 水浴中,裸传感器大概 30 秒内能稳定在误差范围内,加了套管后接近 2 分钟。HVAC 应用里,风管温度本身变化就不快,1 秒刷新周期完全够用,但如果用来做变频压缩机快速过热保护,响应延迟就要考虑进去。

我建议在软件里再加一个 5 点滑动平均。单次转换本身很稳,但现场气流扰动、压缩机启停带来的温度波动会让读数有小幅抖动,滑动平均后控制逻辑更平顺。dsPIC33FJ256GP710A 做这个运算几乎不占资源,代码也很简单:

float filter(float new_value) { static float queue[5]; static uint8_t idx = 0; static uint8_t cnt = 0; float sum = 0; uint8_t i; queue[idx] = new_value; idx = (idx + 1) % 5; if (cnt < 5) cnt++; for (i = 0; i < cnt; i++) sum += queue[i]; return sum / cnt; }

6.3 误差来源分析

系统误差不完全是传感器本身带来的。排查顺序我建议这样:先看传感器供电,远程节点如果 VDD 跌落,转换结果会在低位不稳定;再看总线干扰,CRC 错误率是否偏高;最后看安装方式,传感器是否太靠近金属外壳、是否被太阳直射、是否贴住水管表面。

还有一种容易犯的错:自热效应。PJ85718DM 转换时需要几个毫安电流,如果节点放在完全密闭不通风的盒子里面,热量攒住,读到的温度会缓慢漂高。我在一个项目里发现远程节点读数比其他节点高 0.3℃,把传感器从金属盒移出到通风位置后偏差就消失了。

6.4 快速校准流程

如果多个传感器之间有固定偏差,可以通过软件校准。最省事的是单点偏移校准:在 20℃ 环境下测每个传感器的偏差,比如某个节点偏高 0.2℃,就在软件里对这个节点的温度减 0.2℃。要求更高时做两点校准,在 0℃ 和 60℃ 各测一次,拟合出斜率修正系数。

校准系数我放到 Flash 参数区,不跟着固件走,现场更换传感器后可以单独修改。这样批量生产时不用对每一台设备重新编译固件。对于普通 HVAC 监测需求,我通常只做单点偏移校准,因为数字温度传感器本身的非线性很小,两点校准带来的提升有限。

7. 调试排坑与后续扩展

7.1 三个最隐蔽的坑

第一个坑是看门狗。配置位默认把 WDT 打开后,温度转换等待的 750ms 时间段内,如果喂狗时机不对,芯片会在读取瞬间复位,总线上的读时序被打断,表现为个别节点偶发读到 85℃。排查这个坑花了半天,最后用编程器读出配置位才发现 FWDTEN 是 ON。调试阶段直接把 WDT 关掉,必须在量产前再仔细规划喂狗策略。

第二个坑是对 1-Wire 读取阶段发 Skip ROM。多节点系统里 Convert 命令可以用 Skip ROM 让所有节点并行转换,但读取暂存器阶段不能 Skip,必须 Match ROM。我就见过有人复制粘贴转换阶段的代码,结果读取时所有节点同时响应,总线寸步难行,读回来的数据全是乱的。

第三个坑是 RS485 方向切换过早。这个问题前面写过,很多新手在循环发送完最后一个字节后立刻把 DE 拉低,但实际上最后一个字节还在移位寄存器里没出去,导致收方收到少一字节的帧。解决方法是等TRMT标志置位后再切换。

7.2 排查实测:一个远程节点读不到温度

测试时遇到“单总线某个节点完全读不到”的情况,我一般按照下面这个链路定位,而不是直接换传感器:

  1. 先量这个节点处的 VDD 和 GND,确认供电正常;
  2. 用示波器看 DQ 静态电平,应该是接近 3.3V 的高电平;
  3. 断开这个节点,用该节点的线缆直连一个已知正常的传感器,判断是节点问题还是总线线缆问题;
  4. 如果直连正常,重新检查这个节点传感器焊接,特别是 DQ 脚是不是有虚焊;
  5. 如果短距离直连正常、接入长线后异常,优先怀疑线缆太长、分支太长或者地电位差;
  6. 用逻辑分析仪抓总线上的 Reset 和存在脉冲,看传感器有没有正确应答。

这个排查思路可以套用到几乎所有的单总线故障。大多数现场问题出在供电和线缆,而不是传感器本身。因为数字传感器要是坏了,通常刷机或换新就能解决,反而是布线问题会反复出现。

7.3 后续扩展方向

这套平台可扩展性很强。要做低功耗节点,可以让 dsPIC 休眠,每隔 5 分钟唤醒一次,给传感器供电的 MOS 管只在采集前打开,采集完立即断电,整套系统平均功耗能压到几十微安级别。

如果测点距离超过 50 米,或者需要无线部署,可以把 1-Wire 总线改成 RS485 分布式采集,每个节点放一颗小单片机本地读取 PJ85718DM,再用 Modbus 协议上传。这样既保留了数字传感器的优点,又解决了长线干扰问题。更远距离则可以直接在 dsPIC 上挂以太网芯片或无线模块,把温度数据通过 MQTT 推到云端。

控制联动方面,因为 dsPIC33FJ256GP710A 本身带 PWM 和 DSP 引擎,滤波后的温度值可以直接输入 PID 计算,输出控制压缩机或调节阀的开度。温度监测只是系统的起点,真正有价值的还是基于温度数据的闭环控制。

最后说一个整体感受:温度采集本身并不难,难的是让它在现场长期稳定工作。比如 1-Wire 时序、总线上拉和看门狗配置,这些看起来都是小细节,但任何一个处理不好,数据都会在关键时候出毛病。如果你准备在下一个项目里用类似方案,建议先把这三件事当成一等公民对待:给足硬件余量、用状态机管理采集流程、开机会话预算彻底确认一遍配置位。把这几点做扎实,后面的温度数据想不准都难。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进