
前不久在处理一套设备散热监控的活儿时我把PIC18F4550和PJ85718DM组合到了一块电路板上顺手搭出了“本地温度直接读、远程温度走总线回传”的完整链路。这个方案同时覆盖了嵌入式板卡自身的温升保护以及 HVAC 场景里分布式测点的温度采集。项目本身不算复杂但里头的选型逻辑、I2C 时序处理、RS485 组网帧设计还有最后调试时踩到的一堆坑我觉得很值得单独写一篇。如果你正在做 8 位 MCU 为主控的温度监测、环境监控或者暖通设备控制器这篇文章应该能帮你少走不少弯路。先说清楚这个项目到底做了什么。核心器件就两颗PIC18F4550承担主控负责读取传感器、处理数据、对外上报PJ85718DM是 I2C 接口的数字温度传感器挂在本地总线上。所谓“本地温度”是传感器和主控在同一个机箱或者同一块 PCB 附近直接走 I2C 读取“远程温度”则是把多个PJ85718DM节点通过 RS485 总线分布在 HVAC 的不同风管、房间或设备舱由主控统一轮询采集。采集上来的温度再做两件事HVAC 侧用来联动风机、阀门和压缩机嵌入式侧用来做温升告警和风扇调速保护。适合谁看正准备入门嵌入式传感器采集的开发者、做暖通控制器或设备监控面板的硬件工程师以及想把“温度采集组网上报联动控制”一次性跑通的朋友。本文会按方案架构、硬件接线、固件实现、远程组网、联动逻辑、调试实录六个部分展开尽量把能直接抄作业的代码、参数和排查方法都放进来。1. 方案架构先拆清“本地/远程”两种温度采集的分工1.1 为什么偏偏选 PIC18F4550 加一颗 I2C 温度传感器市面上温度采集方案很多NTC 热敏电阻加 ADC 是最便宜的DS18B20 单总线也很流行。但这套项目最终选了PIC18F4550加PJ85718DM不是我拍脑袋是几个约束条件凑到了一起。PIC18F4550这颗 8 位单片机最大优势是自带 USB 全速模块和 MSSP 硬件 I2C/SPI 模块。项目里有个需求设备本机要能通过 USB 接上位机把采集到的温度曲线实时拉出来看。如果换成不带 USB 的普通 8 位 MCU就得外挂 USB 转串口芯片增加成本和布板面积。这颗 MCU 直接解决了这个问题。另一方面它的内部多个定时器、10 位 ADC 和 PWM 输出也为后面做风扇调速预留了资源一套片子能包揽采集、上报和控制三件事。传感器侧选PJ85718DM核心原因是它把“温度测量”这件事变成了很干净的 I2C 寄存器操作。芯片内部集成了温度传感单元、ADC、寄存器接口和可配置地址引脚主控只需要按 I2C 协议读写寄存器就能拿到 11 位或 12 位的数字温度值不需要像 NTC 那样做查表标定也不存在 ADC 参考电压漂移的问题。而且它支持多片挂同一条 I2C 总线用 A0/A1/A2 引脚设置不同地址正好满足 HVAC 场景里“一个主控带多个测温点”的需求。这里要坦白一点PJ85718DM的具体寄存器布局我下面会以常见的 LM75 兼容模型为基准展开。如果你手里的型号数据手册和这个模型有细微差异只要对着手册调整寄存器偏移量和分辨率位数就行整体架构不需要动。这也是这类 I2C 数字温度传感器最友好的地方——寄存器模型基本都是同一套思路。1.2 本地和远程在架构上到底怎么分层“本地温度”和“远程温度”听起来像是空间概念但在具体实现里其实是两条完全不同的数据通道。本地温度通道传感器和主控距离很近比如都贴在同一个控制板上或者传感器探头固定在机箱内壁。主控用 I2C 总线直接读取寄存器然后立刻把温度值用于板卡温升判断、风扇调速或者本地 OLED/数码管显示。这条链路的延迟可以做到毫秒级适合做实时保护逻辑。远程温度通道HVAC 设备往往要把传感器布置到送风口、回风管、房间温控面板等分散位置和主控之间的距离可能从几米到几十米。I2C 总线不适合长距离传输所以远程节点需要先把PJ85718DM的温度数据读到本地一颗小 MCU 或者从设备侧再通过 RS485 总线转成差分信号回传给中央主控。主控这边用 UART 加 RS485 收发器芯片按自定义帧或者 Modbus 轮询各节点。这样一条总线上可以挂几十个测温点地址由拨码或者从设备程序配置。说直白一点本地温度是“同一个板子上的事”远程温度是“分散节点怎么汇总的事”。很多新手把两者混在一起直接在 I2C 总线上拉十几米线结果波形乱七八糟温度数据根本读不回来。先把这个分工记牢后面所有硬件和固件设计才有依据。提示I2C 总线设计目标是板内通信通常不建议超过 1~2 米更不要跨设备长距离走线。跨机柜、跨风管的温度采集老老实实转 RS485 或者无线。2. 硬件接线与 PCB 细节把两颗器件正确连起来2.1 PJ85718DM 的引脚功能和最小接法PJ85718DM这类温度传感器常规引脚包括电源 VDD、地 GND、I2C 数据 SDA、I2C 时钟 SCL、地址配置脚 A0/A1/A2以及可选的中断输出引脚有的型号叫 ALERT 或 OS。具体丝印顺序以实际手册为准但逻辑结构大差不差。最小系统接法很简单VDD 接电源GND 接地SDA 和 SCL 各接一个上拉电阻到电源A0/A1/A2 接高或接地来设定 I2C 地址。举例来说如果 7 位基地址是0b0011000兼容 LM75 时很多型号基地址是这个A0、A1、A2 全部接地那么地址就是0x18加(A22 | A11 | A0)的偏移。我在项目里把三颗传感器分别设在0x18、0x19、0x1A就是为了验证多地址挂在同一条 I2C 总线上的可行性。接线还有两个细节容易忽略。第一SDA 和 SCL 的上拉电阻不能漏漏了以后 I2C 通信会时好时坏尤其是温度偶尔能读出来、偶尔超时十有八九是上拉问题。第二传感器的去耦电容要尽量靠近 VDD 和 GND 引脚我习惯用 0.1uF 陶瓷电容如果供电纹波大再加一个 10uF 电解电容兜底。HVAC 现场常有风机启停造成的电源跌落去耦做不好温度读数会跟着电源抖动一起跳。2.2 PIC18F4550 侧的资源分配与接线PIC18F4550的 MSSP 模块默认把 I2C 引脚映射到 RC3SCL和 RC4SDA。我会建议把这两个引脚独立引到传感器的 I2C 总线中间不要串电阻或加二极管保持信号完整。如果要用 USB 上报PIC18F4550的 D/D- 引脚需要接一个 1.5kΩ 上拉电阻到 3.3V具体值以手册为准外部晶振和 PLL 配置要让系统时钟以及 USB 模块达到约定的 48MHz 工作条件。这些引脚布局、走线长度和差分阻抗要求比普通 I2C 严格建议直接参考芯片手册里推荐的 USB 参考电路来画。RS485 部分主控的 UART TX/RX 引脚接到一颗 RS485 收发器芯片比如常见的 MAX48x 系列。收发器的 A/B 差分端子接总线另外还需要一个方向控制引脚由主控 GPIO 控制发送和接收切换。注意 A/B 两根线在总线两端各接一个 120Ω 终端电阻不是每个节点都接接多了会把信号幅度拖垮。2.3 上拉电阻取值不是随手用 4.7k 就完事I2C 上拉电阻的取值要和总线电容、通信速率一起算。标准模式下 100kHz 总线上升沿时间要求不超过 1us快速模式下 400kHz上升沿要求更严。电阻太大RC 充电太慢波形上升沿满足不了时序电阻太小灌电流过大功耗高还可能把低电平拉不到规定的 0.4V 以下。我当时的计算过程是这样的假设总线电容约 200pF目标 400kHz上升时间取最大 300ns。Rmax 大概可以按 300ns / 0.8473 / 200pF 估算结果是 1.77kΩ 左右。为了留余量我取了 2.2kΩ如果默认降到 100kHz 模式4.7kΩ 也完全能用。实际项目里直接把上拉定为 2.2kΩ再把PJ85718DM通信速率配置到 400kHz实测波形干净、读数稳定。2.4 硬件上我先踩过的坑这个项目第一版 PCB 打样回来有四个问题是我后来才在调试中发现的。第一个就是传感器 ALERT/OS 引脚悬空导致传感器内部比较器输出状态不稳定间接干扰了 I2C 读操作。解决办法是把这个引脚通过 10kΩ 上拉到电源或者干脆在固件里不使能比较器功能。第二个是 RS485 收发器芯片的 DE/RE 控制脚一开始直接接地导致芯片永远处于接收状态发不出数据。第三个是终端电阻位置放错把 120Ω 焊接在了中间节点的板子上总线信号反射明显远程节点温度偶尔乱码。第四个是 USB 走线太长且没做阻抗控制插上电脑有时识别不到设备。这些坑单独看都很低级但组合在一起会让人误以为是固件问题调试很久才发现是硬件。3. 温度读取固件从 I2C 寄存器到可靠的摄氏度3.1 MSSP 的 I2C 主模式初始化PIC18F4550的 MSSP 模块配置成 I2C 主模式后硬件会自动产生 START、STOP、ACK 这些时序主控只需要往 SSPBUF 写数据、读数据即可。初始化代码可以写成这样// 以 48MHz 系统时钟、400kHz I2C 速率为例 #define FOSC 48000000UL #define FSCL 400000UL void i2c_init(void) { // 配置 RC3/SCL、RC4/SDA 为输入MSSP 模块接管引脚 TRISCbits.TRISC3 1; TRISCbits.TRISC4 1; // 使能 MSSP 为 I2C 主模式具体位域组合参考 plib 或手册 SSPCON1 0x28; // SSPEN1I2C Master mode SSPCON2 0x00; SSPSTAT 0x00; // 波特率 FOSC / (4 * (SSPADD 1)) SSPADD (FOSC / (4 * FSCL)) - 1; // 48M/(4*400k)-1 29 }如果你习惯用厂商外设库OpenI2C1(MASTER, SLEW_OFF)加一句SSPADD 29的效果是一样的。重点是理解SSPADD的计算公式这样换晶振、换速率时心里有数不用死记寄存器值。3.2 读温度寄存器并把原始值换算成摄氏度以经典的 11 位分辨率模型为例温度寄存器占两个字节最高位是符号位有效数据左对齐。读取流程分两步先写指针寄存器指向0x00再重新发起读操作连续读两个字节。核心函数如下int16_t read_pj_temp(uint8_t dev_addr) { uint8_t reg_msb, reg_lsb; // 1. 写指针寄存器为 0x00 StartI2C1(); WriteI2C1(dev_addr 1); // 写地址 WriteI2C1(0x00); // 温度寄存器偏移 RestartI2C1(); // 重复起始信号 WriteI2C1((dev_addr 1) | 0x01); // 读地址 // 2. 读两个字节第一个字节回 ACK第二个字节回 NACK reg_msb ReadI2C1(); AckI2C1(); reg_lsb ReadI2C1(); NotAckI2C1(); StopI2C1(); // 3. 合成 16 位有符号数11 位有效数据右移 3 位 int16_t raw (int16_t)((reg_msb 8) | reg_lsb); return raw 3; // 此时每个 LSB 代表 0.125°C } float get_temp_c(uint8_t dev_addr) { int16_t code read_pj_temp(dev_addr); return code * 0.125f; }这里有个关键点合成int16_t后右移 3 位算术右移会保留符号位所以负温度也没问题。比如 -5°C 在 11 位模型里对应原始值0xFFD8右移 3 位得到-40乘0.125就是 -5.0。如果你用的是 12 位分辨率型号有效数据位数不一样直接右移 4 位每个 LSB 代表0.0625°C即可。3.3 采样周期选择、温度滤波与传感器在位检测嵌入式保护和 HVAC 控温对采样周期的要求完全不同。我实测下来HVAC 房间温度变化很慢1 到 2 秒读一次已经足够太快反而会让后续的 PID 或者滞环控制频繁抖动。嵌入式板卡温升保护则建议 100 到 250ms 读一次这样能及时响应瞬时过温。滤波我用了滑动平均加剔除离群值的方法思路很简单连续采 5 次去掉最大值和最小值剩下 3 个求平均。这个组合比单纯滑动平均响应更快又能滤掉传感器 ADC 的偶发跳变。传感器在位检测也很重要尤其是现场有几十个远程节点时。每次读取温度前可以先读 ID 寄存器或者配置寄存器如果返回全 0xFF 或者全 0x00说明总线上这个地址没有器件直接上报“传感器断线”而不是上报一个虚假温度。我在项目里定义了一个状态字节正常是0x00I2C 超时是0x01温度越限是0x02这样上位机拿到温度的同时也能知道传感器健康状态。注意不要对传感器连续性读取不加延时。转换需要时间转换未完成就读寄存器拿到的可能是上一次的旧数据。安全做法是每次转换完成后去读或者固定加一个大于芯片转换时间的延时。4. 远程数据链RS485 组网与上位机解析4.1 三种远程方案怎么选USB、RS485、还是无线“远程温度”在具体落地时有三种常见选择我根据自己的项目约束做了一个对比方案传输距离节点数实时性适用场景PIC18F4550 自带 USB 上报几米内1 个主设备好设备本机接电脑调试、近距离监控面板RS485 总线组网几十到几百米几十个中轮询式HVAC 分布式测温点、楼宇监控无线模块WiFi/Sub-GHz可变可变中已有无线环境、不方便布线的改造场景PIC18F4550自带 USB 是做本地上位机的利器但它本质上不解决“远程”问题因为 USB 线长度限制在那里。所以远程部分我选了 RS485。HVAC 现场布线本来就多RS485 用双绞线就能跑抗干扰能力强而且 Modbus 之类的协议栈非常成熟后续接组态软件也方便。4.2 RS485 帧格式与主从轮询设计远程节点侧我用的是简化 Modbus-RTU 风格帧。主控作为主机定期广播查询各节点的温度每个远程节点作为从机收到和自己地址匹配的查询帧后回传温度数据。帧格式如下主机查询地址(1B) | 功能码(0x03) | 寄存器地址(2B) | 寄存器数量(2B) | CRC16(2B)从机应答地址(1B) | 功能码(0x03) | 数据长度(1B) | 温度值(2B, 有符号int16, 单位0.1°C) | CRC16(2B)举个例子从机返回01 03 02 01 2C C4 37其中0x012C是 300除以 10 就是 30.0°C。CRC16 用 Modbus 多项式0xA001的低位在前算法。CRC 校验代码不复杂直接贴在主控和上位机两侧用uint16_t crc16_modbus(const uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; for (uint16_t i 0; i len; i) { crc ^ data[i]; for (uint8_t j 0; j 8; j) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; }轮询周期怎么算假设总线上有 8 个节点波特率 19200一个标准查询帧加应答帧大概 10 字节左右传输时间约 10ms。主控给每个节点留 50ms 超时窗口8 个节点一轮下来不到 1 秒。这个刷新率对 HVAC 温度控制绰绰有余。实际项目里我把波特率控制在 9600 到 19200不追求高速因为长线高速传输容易出误码温度数据本来就不需要毫秒级刷新。4.3 上位机解析、日志和两级越限告警数据到了上位机之后我用 Python 脚本监听串口解析 Modbus 帧按节点地址和时间戳存成 CSV 日志。核心代码思想是这样import serial ser serial.Serial(/dev/ttyUSB0, 19200, timeout1) def parse_frame(data: bytes): if len(data) 7: return None addr data[0] length data[2] temp_raw int.from_bytes(data[3:5], big, signedTrue) temp_c temp_raw / 10.0 return addr, temp_c while True: frame ser.read(7) if frame: result parse_frame(frame) if result: addr, temp_c result print(f节点 {addr:02X}: {temp_c:.1f} °C) # 写日志、越限判断告警策略我分了两个等级。第一级是提醒阈值比如温度超过 50°C 时只在上位机标注黄色告警记录事件日志第二级是保护阈值超过 70°C 时主控直接联动风扇全速并上报错误码。两级阈值的好处是避免一次小波动就触发继电器动作或者停机给运维留出处理窗口。5. 嵌入式温控与 HVAC 联动数据到手后怎么用5.1 为什么 HVAC 必须多点测温很多简单的暖通控制器只放一个室内温感靠它一个点来决定压缩机开停其实问题很大。房间不同位置的温差可能有 2 到 3°C阳光直射区域、设备散热口、风口下方温度响应差别明显。靠单点测温要么房间局部冷热不均要么压缩机频繁起停。用PJ85718DM做多点测温后HVAC 主控拿到的是“回风温度”“送风温度”“室内面板温度”等多个维度。控制策略就可以更合理室内面板温度进入舒适区后才允许减载送风温度和回风温度差值用来判断换热效率送风温度过低则触发防冷风保护。这套逻辑在项目里实测下来房间温度波动从原来单点控制时的约 2°C 收窄到 0.8°C 以内舒适度改善明显。5.2 温度滞环控制防压缩机频繁启停的关键HVAC 控制压缩机、阀门和风机时不能刚过阈值就动作、刚回阈值就复位必须加滞环。滞环的代码实现其实很朴素#define TARGET_COOL 24.0f #define HYSTERESIS 0.5f static bool cooling_active false; void hvac_loop(float current_temp) { if (!cooling_active current_temp TARGET_COOL HYSTERESIS) { cooling_active true; // 启动制冷 } else if (cooling_active current_temp TARGET_COOL - HYSTERESIS) { cooling_active false; // 停止制冷 } // cooling_active 决定继电器或阀门输出 }这个逻辑的含义是目标 24°C温度升到 24.5°C 才启动降到 23.5°C 才停止中间 1°C 的死区就是滞环。没有这个死区温度在 24°C 附近微小幅波动会让继电器反复开断几个小时就能把触点烧坏。滞环宽度要根据系统惯量来调HVAC 全屋这种大惯量系统我建议至少 0.5°C小的风机盘管可以缩到 0.3°C。5.3 嵌入式设备的温升保护与风扇调速策略和 HVAC 的舒适性控制不同嵌入式设备温升保护的目标是可靠性。我在这个项目里给主控板设计了这样的策略板载温度低于 60°C 时风扇低速运行高于 60°C 开始线性加速到 75°C 达到全速超过 85°C 触发降频保护超过 95°C 直接进入安全停机状态。温度读数来自本地PJ85718DM直接贴在功率器件附近。风扇调速我用的是 PIC 的 PWM 模块加 MOSFET 驱动PWM 占空比根据温度做分段线性映射uint16_t fan_duty_from_temp(float temp) { if (temp 50.0f) return 0; // 低于 50°C 停转 if (temp 75.0f) return 100; // 高于 75°C 全速 return (uint16_t)((temp - 50.0f) / (75.0f - 50.0f) * 100.0f); }传感器断线时的处理更考验设计。嵌入式设备上如果温度读不回来我会让风扇进入全速默认状态并上报错误码而不是按“0°C”去处理更不是直接停机。这一点对 HVAC 尤其重要一台中央空调如果因为温感断线直接停机夏天会让整个片区失去制冷这不是设备自我保护这是事故。Fail-safe 的设计原则是宁可让系统在保守状态下运行也不能因为失控造成更大损害。6. 现场调试实录常见问题定位与修复6.1 I2C 读不到数据先查地址和上拉我遇到最多的问题是 I2C 通信一直超时温度读不出来。排查顺序我建议固定化先确认器件供电再量 SDA/SCL 有没有上拉到电源然后用逻辑分析仪或示波器看有没有正常的 START/STOP 波形最后确认地址是不是匹配。很多时候是 A0/A1/A2 拨码设置和固件里的地址没有对上差一位就是完全不同的设备总线 ACK 都收不到。如果波形有毛刺或者上升沿很缓多半是上拉电阻太大或者总线电容太重把上拉从 4.7kΩ 换成 2.2kΩ 往往立竿见影。如果波形干净但读回来的数据0xFF那可能是传感器地址错误也可能是读时序里缺少最后一个字节的 NACK。数据手册里通常要求读最后一个字节后回 NACK漏掉的话传感器会一直占用总线后续通信全乱。6.2 温度读数跳变、漂移问题可能在采样时机和布局温度读数偶尔跳一下往往不是传感器坏了而是读取时机不对。PJ85718DM这类传感器模拟前端采样需要转换时间转换没完成就去读寄存器里的值是上一次的残留数据看起来就像跳变。解决办法是每次读之前留足转换时间或者连续读两次丢弃第一次、取第二次。漂移问题则多半来自 PCB 布局。传感器如果离大功率发热器件太近读到的就不是空气温度而是板温。我当时把一颗传感器放在电感旁边读数始终比实际环境高 3°C 左右移走 5cm 之后立刻恢复正常。HVAC 现场还要注意不要让传感器探头贴在风管金属壁上金属导热会造成读数偏向风管温度需要留空气间隙或者加隔热垫。6.3 RS485 丢帧乱码先接地线再查匹配电阻远程节点温度偶尔乱码、某些节点永远无响应这类问题 90% 出在 RS485 的电气细节。第一个要查的是地线。RS485 的 A/B 虽然是差分信号但节点之间如果地电位不一致共模电压超出收发器承受范围就会乱码。项目现场当时有个节点离主控接近 50 米单独拉了一根地线做等电位连接丢帧率立刻下降了一个数量级。第二个是终端电阻位置总线两端各接一个 120Ω绝对不能每个节点都接。第三是总线屏蔽层要单端接地不能两端都接否则会形成地环路。6.4 故障速查表按现象直接定位现象可能原因处理办法I2C 超时无 ACK地址错误、上拉缺失、器件供电异常检查拨码地址补 2.2kΩ 上拉确认 VDD 电压温度偶尔跳变转换时间不足、电源干扰延时后再读加强去耦连续读两次取后一次温度整体偏高传感器靠近热源移开传感器增加隔热检查贴装间隙远程节点无响应地址冲突、RS485 A/B 接反、终端电阻错误逐个排除地址对调 A/B检查终端电阻乱码率高地电位差、波特率不匹配、屏蔽层接地错误拉等电位地线统一波特率单端接屏蔽层上位机识别不到 USB晶振配置错误、D/D- 走线问题核对 PLL 配置检查 USB 参考电路这张表我打印了一份贴在工作台上后来调试第二版硬件时大部分问题都能在表里直接找到答案省了很多时间。最后说一个我个人的习惯。凡是带多个 I2C 从设备的项目我都会在固件里加一个上电自检函数冷启动后扫描所有配置过的器件地址读取 ID 寄存器或配置寄存器把在位情况和错误码通过串口打印出来。这个自检逻辑看似简单但在现场维护时价值极高——设备报修过来接上电脑一看启动日志是传感器掉了、地址漂了还是总线断了一眼就知道不用拆机一个一个量。这次的本地和远程温度监测项目最后也是靠这个自检日志快速定位到了远端两个节点因拨码松动导致地址冲突的问题。做嵌入式多留一条诊断通道永远是值得的。