2026/10/22 6:41:49

嵌入式HVAC温度监测:本地I2C与远程CAN的工程实践

嵌入式HVAC温度监测:本地I2C与远程CAN的工程实践 做商用空调控制器方案的时候我对着两个测温点纠结了很久一个是机组内部的盘管温度传感器贴在铜管上离主板不到半米另一个是装在几十米外风管末端的回风温度线要从电缆桥架穿过去旁边还躺着变频器和接触器。那台控制器的主控用了 PIC18F96J65测温芯片用 PJ85718DM。这个组合听上去不算高精尖但把整套东西理顺之后你会发现本地温度和远程温度根本是两个量级的问题而一颗 8 位 MCU 完全能优雅地把它们同时解决好。这篇文章不是讲某个厂商的 Demo而是把这类嵌入式HVAC 温度监测方案从选型、电路、固件到现场调试的关键链路拆开讲。适合正在做空调控制、冷链监测、工业测控、楼宇自控的朋友参考尤其是那些被传感器读数跳字、远程节点掉线、总线死锁折腾过的人。1. 先想明白本地和远程在硬件架构上是两件完全不同的事很多人拿到需求说监测本地与远程温度第一反应是不都是温度吗拉两根线接同一个传感器型号不就行了这种思路恰恰是后面所有坑的源头。1.1 本地温度传感器贴 MCU 很近用 I2C 数字器件是体力活本地测温比如测主板附近的环境温度、冷媒管路表面温度传感器离主控位置通常在几厘米到几十厘米之间。这种场景最适合用 PJ85718DM 这类数字温度传感器挂 I2C 总线直接输出数字量不需要 MCU 内部 ADC 参与省掉了参考电压校准和热敏电阻的查表换算。工程上最容易忽略的一个点数字传感器不是为了替代 NTC 而替代的它解决的是那个温度值到底准不准的信任问题。ADC 分压法测 NTC 的时候供电电压波动会直接变成温度误差1% 的基准偏差在 0 到 100 摄氏度范围内可能带来好几度的漂移。而数字传感器内部自己完成测量和转换主机读到的就是接近手册标称精度的温度值这对 HVAC 控制很关键——压缩机的启停判据往往就差在 0.5 到 1 摄氏度上。当然I2C 不是把两个器件焊在一起就完事。总线上拉电阻、器件地址冲突、总线死锁哪一个处理不好本地温度通道都能变成最难排查的故障。这些我放在后面电路和固件部分细说。1.2 远程温度距离一拉长I2C 就是地雷场远程测温就完全是另一个故事。几十米的线缆、经过桥架、靠近动力电缆这类环境里如果用 I2C 拉长线光是总线电容就能让通讯波形变成一团糟。I2C 协议是给板内通信设计的上拉电阻和线缆电容一叠加上升沿直接变斜坡速率上去之后连 ACK 都收不到。我当时的判断远程节点必须走差分总线要么 RS-485要么 CAN。最后选了 CAN原因很实际。首先是抗干扰能力CAN 的差分电平对共模干扰的抑制比单端强得多HVAC 现场有大功率设备启停干扰脉冲是常态而不是意外其次PIC18F96J65 本身带 CAN 控制器不需要外扩总线芯片就能组网。如果工程里已经有一套 RS-485 的 Modbus 链路用 RS-485 也完全合理技术选择没有唯一答案关键是远程链路的物理层要够稳。远程节点怎么做每个远端采集点放一块小板板上有 PJ85718DM 和一个负责采样上报的从机节点节点把温度数据打成 CAN 报文发回主控。主控 PIC18F96J65 作为总线上的调度者按周期轮询所有远程节点统一仲裁、统一诊断。1.3 一套清晰的架构比好听的芯片型号更值钱把整个系统拆成这样主控板部分PIC18F96J65 负责全部逻辑本地 PJ85718DM 挂在 I2C 总线上供主控直接读取。远程部分若干个远端采集节点每个节点有自己的小 MCU 和一颗 PJ85718DM小 MCU 负责采样、滤波并把结果封装成 CAN 报文。总线上所有节点共享一条双绞线主机定时广播轮询帧或者用数据帧逐个点名。这样的划分让职责非常清楚本地温度是板级信号完整性问题远程温度是总线通信和组网问题两类问题几乎不会互相污染排查故障时也不会一个坑带崩另一个坑。2. 硬件电路设计里的四个关键决策都是拿实测换来的经验硬件设计看起来就那么几颗阻容件但在这种工业应用场景里每一个决策背后都站着一个曾经出现过的故障现象。2.1 传感器供电不要直接从主控的数字电源上取温度传感器虽然叫数字传感器但芯片内部一定有模拟测量前端。模拟前端对电源噪声非常敏感手册里通常会写推荐的供电纹波上限。如果你直接把 PJ85718DM 的 VDD 接到和 MCU 同一个 3.3V 网络而这块板子上又有继电器吸合、数码管扫描那读数跳动会让你怀疑传感器坏了。我的做法给传感器供电串一颗 10Ω 左右的电阻或者用小电感隔离再加一组 100nF 陶瓷电容配合 4.7μF 钽电容形成低通滤波。数字部分和模拟电源域用单点接地汇合。成本增加几毛钱换来的却是温度数据的稳定。实测下来不隔离的时候继电器吸合的瞬间温度读数能跳 0.3 摄氏度加上隔离之后基本稳定在 0.02 摄氏度以内的波动。这个差距对控制逻辑来说是质的差别。2.2 I2C 上拉电阻不是随便贴一个 4.7k 完事I2C 上拉电阻的选择要和总线电容一起算。简单说上升时间由 RC 决定tR 0.8473 × R × R × C这里指上拉电阻 × 总线上所有电容。标准 Fast Mode 要求上升沿不超过 1μs。假设总线电容 150pF那么 R 最大约等于 1μs / (0.8473 × 150pF)算出来大约 7.8kΩ所以选 4.7kΩ 是合理的。但如果你用了延长线总线电容涨到 300pF那同样的 4.7kΩ 就不够了上升沿会超时。这种情况下要么换 2.2kΩ要么干脆加一个 I2C 总线缓冲器。电阻也不能无限小太小会让低电平灌电流超限。3.3V 供电下 2.2kΩ 的灌电流大约在 1.3mA 左右还在多数芯片的承受范围内。保守做法板内布线不超过 20cm 用 4.7kΩ走线到 1 米左右换 2.2kΩ超过 3 米我就不会再用 I2C 了老老实实走远程总线方案。2.3 CAN 总线的终端电阻和隔离谁在什么时候加CAN 总线要求两端各接一个 120Ω 终端电阻匹配线缆特性阻抗抑制反射。最容易犯的错是每个节点都加结果总线上多了一堆并联电阻信号幅值被拉低远端节点直接进不了显性位判定阈值。我习惯用一个简单规则只有物理线缆的两个最远端节点各加一个 120Ω 电阻中间节点一律不加。如果现场实测波形振铃明显再用示波器确认是哪一段的阻抗不连续。另一个常见问题是施工时把两股线分叉接成星形CAN 标准是总线型拓扑不该有长分支短的分支线控制在几十厘米内否则反射非常难看。远程节点供电地和主控地之间如果有很大的电位差最好选带隔离的收发器或者加共模电感把射频干扰挡在外面。HVAC 现场经常出现地环路烧接口的例子一个隔离收发器能把这类问题的 80% 消灭在源头。2.4 探头的物理防护灰尘、冷凝水、震动远程温度节点做出来后不比实验室里放桌面跑的评估板。风管里有冷凝水机房里有灰尘铜管表面温度可能到零下或者 60 到 80 摄氏度。我吃过一次亏传感器 PCB 没有做三防处理一个采暖季之后实测读数整体漂了 1 摄氏度拆开看发现焊盘边上已经有一圈水渍腐蚀的痕迹。所以凡是会装到设备内部的传感器小板一律过三防漆至少把焊盘和过孔盖住。传感器如果要粘到管壁上测表面温度一定用导热硅脂加机械压紧不能用胶布一缠了事。机械松动的探头读数会在一个区间内来回晃那种波形看起来特别像传感器坏了其实只是热接触不好。3. 固件实现传感器驱动、数据滤波与 CAN 轮询协议电路板回来之后真正花时间的部分是固件。温度监测这种小功能写起来不难难在把这个小功能放进整个控制系统中还不出幺蛾子。3.1 传感器初始化与寄存器读写先把基础动作做到位大多数数字温度传感器的寄存器布局都类似温度数据寄存器、配置寄存器、ID 寄存器。PJ85718DM 这类芯片的读写流程是主机发 I2C 起始信号接着是器件地址加写位然后是寄存器地址最后是数据读的时候要先把指针指到目标寄存器再用重启动的方式读取。写驱动的时候有两个习惯一定要养成。第一写配置寄存器之后必须读回来确认不能写完了假装成功。I2C 总线上的写入过程经常因为电气问题静默失败如果 MCU 不检查后面读到的可能是上电默认值而你以为自己已经设成了想要的精度。第二芯片上电后第一次转换结果要丢弃等一个完整转换周期再开始拿数据。很多读数初始跳了一大格的怪象就是因为首帧数据是半成品。一个示意性的读取流程是这样伪代码具体寄存器地址以你拿到的手册为准uint8_t buf[2]; // 写指针到温度寄存器 i2c_write_reg(PJ85718DM_ADDR, REG_POINTER, REG_TEMP); // 连续读取两个字节 i2c_read_regs(PJ85718DM_ADDR, REG_TEMP, buf, 2); int16_t raw (int16_t)((buf[0] 8) | buf[1]); // 通常 12 位分辨率下LSB 表示 0.0625 摄氏度 int16_t temp_x100 raw * 625 / 100;注意不要一上来就用浮点数。8 位 MCU 做浮点运算太奢侈了用整数表示 0.01 摄氏度的精度完全够 HVAC 控制使用。3.2 滤波不是越重越好延迟和失真的权衡温度信号本身变化很慢现场又有各种干扰所以滤波几乎是必须的。最常见的做法是均值滤波加中值保护。均值滤波就是开一个环形缓冲区存最近 N 次采样每次算一个平均值。代码逻辑不复杂#define FILTER_LEN 8 uint16_t temp_buf[FILTER_LEN]; uint8_t temp_idx 0; uint32_t temp_sum 0; void update_temp_filter(uint16_t new_val) { temp_sum - temp_buf[temp_idx]; temp_buf[temp_idx] new_val; temp_sum temp_buf[temp_idx]; temp_idx (temp_idx 1) % FILTER_LEN; if (temp_idx 0) { filtered_temp temp_sum / FILTER_LEN; } }有一个很容易踩的坑累加和溢出。如果原始温度寄存器是 16 位N 取 64中间累加值就有 20 位超过普通 uint16_t 的范围了所以累加变量一定用 uint32_t或者在每次累加后除以 N 做成滑动平均。但滤波窗口不是越大越好。温度控制回路有滞后如果主机每 100ms 采一次样滤波深度 N8 意味着温度响应被拖慢了接近一秒。做回风温度控制时这个延迟还能接受但如果测的是压缩机排气管温度这种需要快速响应的保护点N 取 4 或者干脆不加滤波更安全。滤波深度是参数不是越大越光荣一定要配上设备自身的惯性来看。3.3 CAN 应用层协议轮询制比自发上报省心得多远程测温节点组网以后通信协议必须自己定。我强烈建议主机统一调度用轮询的方式点名。每个远程节点不主动发数据只有被主机问到了才应答。这样有几个好处总线上不会出现多节点同时上电导致的一窝蜂广播也不会出现某个节点故障后疯狂发错误帧扰乱总线。报文 ID 的分配可以这样设计高字节表示帧类型轮询请求、应答、诊断应答中间字节表示节点地址低字节用不上就填 0。数据段按固定格式排放温度占两个字节序列号占一个字节剩下两个字节放 CRC。表格大概是这样报文 ID 范围方向数据段内容0x100主机 → 节点轮询请求节点号放在 ID 中0x200节点 → 主机温度高字节、温度低字节、序列号、CRC0x300主机 → 节点配置命令设置滤波深度/上报周期0x400节点 → 主机故障状态字、节点自检结果主机每轮巡检所有节点之后再统一刷新本地温度和控制算法输入。轮询周期 1 秒对 HVAC 足够CAN 500kbps 波特率下哪怕挂了 30 个节点负载都低得可怜留给控制算法的资源和总线余量都很充足。故障识别也不能少。连续三到五个轮询周期没有应答的节点直接标记为离线同时保留它最后一次的有效温度用于显示但不再参与控制决策。等到节点恢复了自动重新纳入调度整个过程不要让整个控制器重启。4. 实测中踩过的几个坑每一个都值得单独复盘代码写完只是开始真正让人长记性的是实测阶段。以下四个问题我是真实花了不少工时才逐个揪出根因的。4.1 I2C 总线死锁最大概率事故必须先学会自救I2C 死锁的现象很典型总线上所有器件都不再响应拉 SCL 也没用因为可能是从机把 SDA 拉死在低电平等待主机给它一个时钟脉冲才能释放。触发原因通常是主机在通信过程中遇到错误后没有等当前字节结束就中途退出导致从机卡在状态机中间。解决办法分两层。第一层写驱动的时候保证每次通信都必须完整走完即使出错也要发停止信号。第二层如果系统已经进入死锁靠普通 I2C 初始化无法恢复这时需要主动用 GPIO 模拟时序给 SCL 打 9 个周期来从机复位void i2c_bus_recover(void) { uint8_t i; // 先把 SCL/SDA 设为 GPIO 输出手动拉高并打时钟 set_scl_output(); set_sda_output(); set_scl_high(); set_sda_high(); for (i 0; i 9; i) { set_scl_low(); delay_us(5); set_scl_high(); delay_us(5); } // 再发一个 STOP set_sda_high(); set_scl_high(); delay_us(5); i2c_init(); }这段代码看起来暴力但在工业现场非常救命。我把这个恢复函数挂在定时器巡检里一旦发现 I2C 连续两次通信无应答先执行总线恢复再重新初始化传感器而不是直接让整个控制器看门狗复位。4.2 跳字问题先查电源和走线再调软件滤波有一次整机测试PD 温度板的读数在运行一段时间后开始每隔几秒跳 0.3 到 0.5 摄氏度。当时第一反应是滤波不够把均值窗口改成 16 以后还是跳。后来示波器一测传感器供电引脚上有一串高频毛刺每当变频器调整输出时就会出现一波。这个案例说明一个判断原则温度信号有惯性真正的温度变化不可能在一帧采样内突然跳好几度如果看到瞬跳先怀疑电气干扰不要急着改软件。硬件上把供电隔离做好、信号线远离动力线比加大滤波窗口有效得多。软件上也保留一道防线连续采样三次如果某一次的值和前一次相差超过设定阈值比如 2 摄氏度就丢弃这一帧继续用上一帧滤波结果。这个尖峰保护逻辑在工业场合格外有用。4.3 远程节点偶发-40 摄氏度根因可能在线上调试时发现一个问题2 号节点的读数时不时变成 -40 摄氏度然后又自己恢复正常。一开始我以为是传感器坏了换了一个还是这样。后来把 CAN 收发器的信号用示波器抓到才发现总线波形在某些时刻有非常严重的振铃导致接收方偶发误码而误码后解析出来正好是 0xFFFF 之类的极值。根因是总线上终端电阻加重复了而且那一小段连接用的分支线太长。把冗余的终端电阻去掉、缩短分支线后再也没出现过 -40 摄氏度的假数据。这个坑让我养成了一个习惯任何远程数据异常先看物理层再看协议层CRC 只是最后一道拦截网不能指望它抗物理故障。4.4 滤波和轮询参数的调优表直接照抄也行对于没有明确设计依据的同行我给出我实测中最稳妥的一组起始参数参数推荐值说明本地采样周期100ms跟随电源周期的干扰100ms 采样足够本地滤波深度8 点均值约 0.8 秒延迟回风温度控制可接受远程轮询周期1s30 节点以内500kbps 下负载很低远程超时判断连续 5 个周期无应答避免单帧丢失导致误判尖峰丢弃阈值2 摄氏度超过阈值直接丢弃防止干扰进入滤波这些参数不是定死的不同设备的热惯性差异很大。唯一的原则是滤波带来的延迟必须小于被控对象的可容忍延迟否则控制回路就会振荡。5. 从能测温到用好温度HVAC 联动逻辑与现场校准温度数值出来以后真正的项目价值才开始。温度监测最终是要参与控制的而参与控制的方式决定了这套系统好不好用。5.1 温度不是用来显示的是要拿来参与启停控制的在 HVAC 里温度最典型的用处是滞回控制。制冷模式下回风温度高于启动阈值就开启压缩机低于停止阈值就关闭。如果温度滤波过度启动和停止的判断会被人为推迟机组运行周期变长舒适性变差甚至出现频繁启停。传感器安装位置对控制的影响也很大。如果把回风温度传感器装得离出风口太近测到的是已经处理过的冷风而不是房间内混合后的实际温度控制系统就会错误地提前停机。这不是传感器芯片的锅是系统设计的问题。所以做 HVAC 控制项目时一定要把安装位置写进施工要求文档里不能让现场工人随手一放。5.2 故障安全传感器失效时宁可保守也不能乱猜温度监测出现故障时控制器最忌讳的行为是随便拿一个值继续跑。我曾经在一个方案里看到控制代码在传感器出错时回退到默认温度 25 摄氏度这简直是把设备往事故边缘推。正确的做法是明确标记通道故障并触发对应的安全策略。伪代码逻辑大致是if (local_temp_sensor_status FAULT) { alarm_set(ALARM_TEMP_SENSOR); compressor_disable(); fan_set_speed(FAN_SAFE_SPEED); } else if (remote_node_status[node_id] OFFLINE) { alarm_set(ALARM_REMOTE_NODE); // 该节点对应的风机/水阀进入安全开度 valve_set_safe_open(node_id); } else { control_run(); }故障状态下的策略要和具体设备联动压缩机保护类的温度传感器故障时严禁压缩机继续启动除非有冗余传感器确认安全回风温度传感器故障时风机可以继续运行但不要盲目开启冷源。这个逻辑在验收文档里写清楚现场调试才不会争执不清。5.3 现场校准把系统误差从不确定变成已知PJ85718DM 这类数字传感器出厂精度不错但装到设备里之后探头的安装热阻、外壳风道、延长线的压降都会引入系统误差。有的误差方向一致比如导热硅脂没涂均匀、外壳隔热没做好可能导致读数长期偏低或偏高。批量生产时我建议至少做一次两点校准冰水浴0 摄氏度附近和温水浴50 摄氏度附近把读到的偏差写入 PIC18F96J65 的片内 EEPROM运行时在读取值上加上修正偏移。要提醒的是这个校准是校准传感器加安装方式的综合偏差不是校准传感器本体如果发现偏差超过 1 摄氏度先怀疑安装方式不要急着调软件偏移掩盖问题。另外留一个小设计传感器端子附近留几个可焊跳线用来切换器件地址。这样万一现场出现两路传感器撞地址不需要重新打板用跳线就能错开实际使用中帮了大忙。温度监测项目做多了之后我的体会是读温度那一下永远不是工作量最大的部分电源噪声、总线恢复、远程链路物理层才是真正烧时间的区域。这也是为什么我在这篇文章里花大篇幅讲供电和通信因为这些经验的换手率比某个具体的寄存器配置高得多。最后再分享一个小技巧给所有传感器板留出测试点焊一个双排排针现场排查时示波器探头一夹就能看到波形排查速度会快非常多。