1. 项目概述与需求拆解
1.1 为什么做这个温度监测方案
先说结论:嵌入式系统里做温度监测,本质上不是在选一颗传感器,而是在设计一条从物理世界到业务逻辑的可靠链路。这条路走通畅了,HVAC(暖通空调)控制、机房告警、冷链运输、工业设备热保护,都是一样的套路。
我最初的切入点很简单:一套设备同时盯着本地环境温度和远程关键部件的温度,数据要稳定、实时性要够、成本可控。考虑到 HVAC 场景的特殊性——室内外温差大、现场环境嘈杂、安装位置五花八门,直接选了一颗数字温度传感器配合 STM32F303RC 来做整体方案。这个组合的核心逻辑是:传感器负责把物理量变成数字信号,MCU 负责把数字信号变成可用信息,两层各司其职,排查问题的时候也清楚该查哪边。
选 PJ85718DM 这颗传感器的原因,排在第一位的并不是精度,而是它支持本地和远程两个测温通道。HVAC 应用里,我既需要知道控制板附近的环境温度(本地),也需要知道远端压缩机外壳或风道出口的温度(远程),用一颗芯片同时搞定两路测温,整个 BOM 就简洁很多。第二点才是精度和分辨率,这颗器件自带较高分辨率的 ADC,本地和远程通道的数据都能独立读取,不需要额外设计模拟调理电路。第三点,接口是标准的 I2C/SMBus 协议,STM32F303RC 的 I2C 外设直接对接,固件层面不需要搞花活。
STM32F303RC 这边,选它的原因更直接:Cortex-M4F 内核带浮点单元,处理温度值的线性化换算、数字滤波、PID 控制量计算都很顺手。而且它的 I2C 模块支持多主模式、超时检测、模拟噪声滤波,这些对温度监测这种常年运行的场景很重要。再加上多路 ADC、多个定时器、丰富的中断控制器,后续想扩展其他传感器或者增加 PWM 输出控制风机,都不用换 MCU。
1.2 本地与远程监测的工程含义
很多人第一次看到“本地温度”和“远程温度”这两个词,会理解为“一个测板子旁边的温度,一个测远处设备的温度”。这个理解方向没错,但工程上要更细一层:本地通道,传感器本体直接感知它所处位置的温度,比如控制盒内空气温度;远程通道,传感器则通过引脚外接一个测温二极管(通常是 PNP 晶体管的 BC 结),对外部设备的关键节点做非接触式或贴装式测温。
这个方案的深层原因在于,现代温度传感器芯片内部集成了精密恒流源和 ADC,通过切换电流激励方向,可以测量外接二极管在不同激励电流下的正向压降变化。二极管压降随温度近似线性变化,斜率大约在 -2mV/°C 左右,芯片内部根据这个“电压—温度”关系反推出远程被测点的温度。这个过程全部由传感器芯片完成,MCU 那边只需要读结果。
HVAC 场景里典型的远程测温点有这么几处:
- 压缩机排气管表面温度,用于过热保护,一般贴装热敏二极管到管壁
- 冷凝器进出风温差,用来判断换热效率是否下降
- 风道内空气温度,配合风阀执行器做闭环调节
- 冷却液或水路的温度,通过导热胶把二极管固定在管路外壁
每路测温点的物理位置不同,线缆长度不同,受到的电磁干扰也不同,所以设计时要把传感器输入引脚的保护电路、走线方式、滤波电容都考虑到位,这块我会在后面的硬件章节详细展开。
1.3 这套方案解决的实际痛点
没做过 HVAC 嵌入式开发的人,可能觉得“测温度”没什么难的。可真做起来,痛点全在细节上。
第一个痛点是温度漂移和精度损失。很多廉价方案用 NTC 热敏电阻,虽然成本低,但一致性差、需要查表校准、老化后漂移严重。数字温度传感器的 ADC、基准都在芯片内部完成,精度和生产校准已经在出厂时做好了,开发阶段少操一份心。
第二个痛点是系统架构混乱。见过一些项目,一路温度用一个独立传感器,三路温度就挂了三个传感器、三份驱动代码、三条 I2C 地址冲突的处理逻辑。PJ85718DM 这类多通道器件,一颗芯片一个 I2C 地址就能读两路温度,地址冲突问题直接少一半。
第三个痛点是故障排查困难。温度监测系统最怕的是传感器坏了你不知道,数据还在走,但值已经不对了。这颗器件支持断线检测和故障报警,远程通道断线时能返回明确的错误标志,配合固件做一致性校验,就能避免“坏数据”被当成“正常温度”参与控制逻辑。
2. 硬件电路与系统设计
2.1 MCU 选型与最小系统
STM32F303RC 属于 STM32F3 系列,主频最高 72MHz,Cortex-M4F 内核带单精度 FPU。之所以没用更常见的 F103 或 G030,是因为这个方案除了读温度,后面还要跑 PID 控制、风机 PWM、人机交互界面,对运算能力和外设丰富度都有要求。F303RC 的 256KB Flash、40KB SRAM 足够跑一套带 RTOS 的应用,而且它内部有两个 5MSPS 的 ADC 模块、三个 PGA(可编程增益放大器)、多个比较器,做混合信号采集非常顺手。虽说我们这里主要用 I2C 读温度,但同一个芯片上还能挂电流检测、电压监测、开关量输入,系统扩展性远超普通 M0 方案。
最小系统这边,有几个关键的工程决策:
时钟源:使用外部 8MHz 晶振,再经 PLL 倍频到 72MHz。温度监测对时间精度不算敏感,但 I2C 通信时序依赖系统时钟,外部晶振比内部 HSI 的稳定性好,规避高温环境下的频率漂移。晶振两端各接一个 20pF 负载电容,走线尽量短且对称。
复位电路:STM32F303RC 上电后由内部 POR 电路完成复位,外部仅需在 NRST 引脚接一个 100nF 电容到地,增强抗干扰能力。这个电容选 X7R 介质,温度稳定性好,别用高容值电解电容,否则复位时间会被拉长。
电源方案:HVAC 控制器普遍是 24V AC 或 24V DC 供电(部分商用机组甚至是 380V 变频驱动),所以板级电源必须经过隔离 DC-DC 降压到 5V,再经 LDO 降到 3.3V 给 MCU 和传感器供电。实测下来,这个方案的功耗集中在 MCU 和通信接口,传感器本身功耗很低,3.3V 通道用 RT8059 这类小封装 LDO 就够,但要注意 LDO 的压差和热阻,别在高温场合烧掉。
调试接口:SWD 四线(SWDIO、SWCLK、GND、VCC)预留标准 2.54mm 排针,方便固件调试和量产烧录。F303RC 的 SWD 口和某些 GPIO 复用,原理图阶段就要确认没有冲突,否则后边飞线非常痛苦。
2.2 传感器引脚功能与连接要点
PJ85718DM 的封装形式一般是小尺寸贴片,引脚数量不多,功能上包括电源、地、I2C 数据/时钟、远程二极管正负极(有些型号叫 D+/D-)、报警输出等。具体引脚号和命令字以官方数据手册为准,这里不一一列举,但连接设计上有几个通用原则。
电源去耦:传感器电源引脚到地之间放一个 0.1µF 陶瓷电容,紧贴引脚摆放。这颗电容的作用是滤除 I2C 总线上的高频噪声串扰进传感器的电源引脚,以及传感器内部 ADC 切换采样时产生的电源毛刺。如果供电线比较长,建议在远端再并一个 10µF 钽电容,效果明显。
I2C 上拉电阻:I2C 的 SDA、SCL 都必须接上拉电阻到 VCC,阻值选 4.7kΩ 比较常规。但是——如果总线上挂的设备多、走线长,等效电容变大,4.7kΩ 的上升沿可能太慢,需要降到 2.2kΩ。反过来,如果总线很短、速率只有 100kHz,10kΩ 也能用。我习惯的做法是先按 4.7kΩ 设计,量产前用示波器量一下 SCL 上升沿时间,如果大于 1µs 就换 2.2kΩ。这个细节直接影响通信稳定性,尤其是高温环境下阈值电压变化之后,本来能工作的上拉阻值可能会变得临界。
远程二极管连接:这是整颗传感器里最需要注意的接口。远程测温引脚对走线长度、寄生电容、ESD 防护都很敏感。工程上建议:
- 双绞线或等长平行走线,两根线尽量靠近,保持相同的寄生电容
- 走线长度控制在 1 米以内,超过这个长度信号完整性明显下降
- 在传感器引脚输入端并联一个 1nF 到 10nF 的差分电容,滤除共模噪声
- 如果远程端是工业环境,在二极管端并联一个 0.1µF 电容,同时串联几百欧姆的限流电阻
报警输出:PJ85718DM 通常有一个可编程的报警输出引脚(有些型号是开漏输出),可以工作在比较器模式,当温度超过阈值时拉低中断 MCU。HVAC 场景建议把这个引脚接到 MCU 的外部中断输入,而不是靠轮询 I2C 读状态寄存器——这样 MCU 在休眠模式下也能被温度告警唤醒,降低系统平均功耗。
2.3 本地通道与远程通道的标定设计
现在聊聊温度标定,这是温度监测系统误差的主要来源,也是很多项目的坑。
本地通道的精度主要由芯片内部的基准和 ADC 决定,出厂时已经校准过,用户一般不需要额外修正。但是,PCB 布局中的某些因素会造成“自热误差”——传感器芯片自身功耗导致结温高于环境温度。规避方法是让传感器远离大功率器件,比如 LDO、功率 MOS 管、变压器,同时在 PCB 上开散热过孔阵列,把热传导到背面的铜皮。实测中,如果传感器旁边跑着 1A 的稳压器,本地通道读数可能偏高 2~3°C,这个误差在 HVAC 控制里是致命的——明明环境温度只有 25°C,系统却以为到了 28°C,从而错误地启动制冷。
远程通道的标定相对复杂。远程测温二极管本身可以是某个型号的 2N3906、MMBT3906 等 PNP 晶体管,也可以是 CPU/GPU 内部集成的衬底 PNP。不同的二极管,其理想因子 n 和串联电阻 R 不一样,PJ85718DM 这类器件一般通过芯片配置寄存器来匹配。具体做法是:标定时把传感器和参考温度计放在同一个恒温环境,比如恒温槽设定 25°C,在配置寄存器里微调偏置电流和串联电阻补偿值,直到远程读数等于参考值。这个校准过程最好在产线上做一遍,把所有产品的补偿系数写进 Flash 里存起来。
注意:不要指望远程二极管测温能替代接触式 PT100 的精度等级。它更擅长的是做趋势监测、温度差异判断、告警阈值判断,而不是高精度计量。如果项目明确要求 ±0.1°C 级别的测量,还是老老实实上 3 线制 PT100 + 24 位 ADC。
3. 固件架构与核心算法实现
3.1 底层驱动设计思路
固件的整体架构我倾向于分三层:硬件抽象层(HAL)、传感器驱动层、应用层。别看这只是一个测温项目,分层带来的收益在后边扩展功能时非常明显。
硬件抽象层:直接操作 STM32 HAL 库或者 LL 库,完成 I2C 外设初始化、引脚复用配置、GPIO 中断配置。注意 STM32CubeIDE 生成的初始化代码只是骨架,I2C 时序参数需要根据实际总线负载调整,特别是 I2C 时钟速率。PJ85718DM 支持标准模式(100kHz)和快速模式(400kHz),我一般先用 100kHz 把基础通信跑通,再尝试升速,快速模式必须用示波器确认波形没问题再说。
传感器驱动层:这一层把 PJ85718DM 的寄存器操作封装成语义化接口,对外只暴露几个函数:pjt_init()、pjt_read_local_temp()、pjt_read_remote_temp()、pjt_set_alarm_threshold()、pjt_get_fault_status()。驱动内部负责寄存器地址映射、数据格式转换、错误状态解析。还有一个重要函数——pjt_check_connection(),内部实现是读芯片的 ID 寄存器,如果返回值匹配,说明总线上确实挂着一颗工作正常的传感器。这个函数建议在系统上电初始化时调用,如果返回错误,直接上报故障,避免后续所有温度读数都是无效的。
应用层:负责温度数据的后处理,包括滑动平均滤波、坏点剔除、告警逻辑、数据上报等。后面我会详细讲滤波和告警的实现细节。
3.2 STM32F303RC 的 I2C 初始化配置
我用 STM32CubeMX 做初始化配置,关键的几个参数这样设置:
- I2C 速率模式:Fast Mode,100kHz(保守起见,先别跑 400k)
- 时钟源:F303 的 I2C1 挂载在 APB1 总线上,APB1 时钟 36MHz
- 上升沿时间:根据 I2C 规范,100kHz 模式下最大 1000ns
- 数字滤波:开启,宽度设为 1 个时钟周期,能滤掉总线上的短毛刺
初始化代码骨架如下:
void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } }需要特别提醒的是NoStretch 模式。I2C 协议里,从机忙的时候可以通过拉低 SCL 来让主机等待,这就是时钟拉伸。默认配置I2C_NOSTRETCH_DISABLE,即允许时钟拉伸。但如果传感器在非易失性寄存器写入时被拉伸了时钟,而主机的 I2C 外设没有超时保护,就可能无限等下去。STM32F303RC 的 I2C 外设带超时检测,代码里配置一下hI2C1.Init.AnalogFilter和超时寄存器就行,但更省心的做法是:初始化完成后不要频繁写寄存器,温度读取操作只读不写,从硬件上规避半数以上的 I2C 卡死问题。
3.3 温度读取与数据转换
PJ85718DM 的数据寄存器通常是 16 位,格式为二进制补码,分辨率能达到 0.0625°C 或者更细。读取流程分四步:
- 写命令:指定要读取的寄存器地址(本地温度或远程温度)
- 等待转换完成:传感器内部 ADC 转换需要一定时间,通常是几百毫秒级别,也可以通过读状态寄存器判断
- 连续读两个字节:高字节在前
- 将 16 位原始数据转换为有符号整数,再乘以分辨率
代码实现:
int16_t pjt_read_raw_temp(PJT_Device *dev, uint8_t reg_addr) { uint8_t buf[2] = {0}; HAL_StatusTypeDef status; status = HAL_I2C_Mem_Read(dev->hi2c, dev->i2c_addr, reg_addr, I2C_MEMADD_SIZE_8BIT, buf, 2, 100); if (status != HAL_OK) { dev->last_error = status; return 0x8000; // 返回显式的错误值 } int16_t raw = (int16_t)((buf[0] << 8) | buf[1]); return raw; } float pjt_convert_to_celsius(int16_t raw, float resolution) { return (float)raw * resolution; }这里面有一个所有做嵌入式温度的人都得面对的问题:错误值不能伪装成正常温度。如果 I2C 读取失败,函数返回一个普通的 int16 数值,上层拿它除以分辨率得到一个“温度”——这时系统就会把 -64°C 或者 128°C 当成真实环境温度,然后做出错误的控制决策。
我的做法是二分法:正常温度范围之外的值全部视为异常。比如 HVAC 场景,温度范围设定为 -40°C 到 150°C,原始值如果超出这个范围,直接走故障分支,标记数据无效,触发告警,而不是参与后续滤波和控制。这个“数据有效性判断”放在驱动层,不要放到应用层,保证所有调用方都拿不到假数据。
3.4 滑动平均滤波与坏点剔除
温度信号的特点是变化缓慢、惯性大,但会被偶发的电磁干扰或 I2C 数据错误影响。如果直接把原始读数给 PID 或告警逻辑,可能出现风机误动作。
我采用的是“中值滤波 + 滑动平均”两级滤波。第一级中值滤波:每采 5 个点,取中间值,剔除突发噪声。第二级滑动平均:对经过中值滤波的数据做 4 次平均,窗口 4~8 合适,太长会让系统响应变得迟钝。
#define MEDIAN_WINDOW 5 #define AVERAGE_WINDOW 4 float temp_filter(float new_sample) { static float median_buf[MEDIAN_WINDOW]; static float avg_buf[AVERAGE_WINDOW]; static uint8_t median_idx = 0; static uint8_t avg_idx = 0; // 坏点剔除 if (new_sample < -40.0f || new_sample > 150.0f) return get_last_avg(); // 丢弃坏点,返回上一次的平均值 // 步骤1:中值滤波 median_buf[median_idx] = new_sample; median_idx = (median_idx + 1) % MEDIAN_WINDOW; float temp_buf[MEDIAN_WINDOW]; memcpy(temp_buf, median_buf, sizeof(temp_buf)); // 这里对 temp_buf 排序,取中值 float median_val = sort_and_get_median(temp_buf, MEDIAN_WINDOW); // 步骤2:滑动平均 avg_buf[avg_idx] = median_val; avg_idx = (avg_idx + 1) % AVERAGE_WINDOW; float sum = 0.0f; for (int i = 0; i < AVERAGE_WINDOW; i++) sum += avg_buf[i]; return sum / AVERAGE_WINDOW; }坏点剔除的逻辑在这里起到了双重作用:第一,防止 I2C 错误导致的离谱数据进入滤波器,污染中值窗口;第二,给上一级调用方提供连续的“上次有效值”,保证在短暂异常期间系统还能以一个合理值继续运行。
需要注意的是滤波器的初始化问题:系统刚上电时,buffer 里的初始值是 0,前几个窗口平均出来的值会严重偏低,导致开机瞬间产生低温报警。我的做法是在初始化时把 buffer 填成环境温度初始值(比如 25°C),或者直接跳过滤波阶段,把第一个有效读数作为初始值。
固件里的滤波参数要根据现场调试微调。如果现场存在大功率变频器之类的强干扰源,中值窗口从 5 扩大到 9 比较稳妥,代价只是响应延迟增加几百毫秒,对 HVAC 工况完全可接受。
3.5 告警阈值与状态机
温度告警不能只设一个阈值然后比较大小,因为单一阈值会造成在临界点反复触发/停止触发的问题(俗称“抖动”)。正确做法是设置迟滞区间:温度超过上限阈值报警,但回落到“上限阈值 - 迟滞值”之后才解除告警。
以 HVAC 冷凝器排气管温度保护为例:
- 报警启动阈值:80°C,温度超过 80°C 时输出告警并关闭压缩机
- 迟滞值:5°C,温度降到 75°C 以下时才允许重新开启压缩机
- 这样做的好处是防止温度在 80°C 附近波动时压缩机频繁启停,损坏设备
用状态机实现:
typedef enum { TEMP_NORMAL = 0, TEMP_HIGH_ALARM = 1, TEMP_SENSOR_FAULT = 2 } TempState; TempState temp_alarm_update(float temp_celsius, float hi_thresh, float hysteresis) { static TempState state = TEMP_NORMAL; switch (state) { case TEMP_NORMAL: if (temp_celsius >= hi_thresh) state = TEMP_HIGH_ALARM; break; case TEMP_HIGH_ALARM: if (temp_celsius <= (hi_thresh - hysteresis)) state = TEMP_NORMAL; break; case TEMP_SENSOR_FAULT: // 传感器恢复后需要重新初始化 break; } return state; }除了高温告警,还要考虑低温告警,HVAC 冬季工况下管路冻结风险也很重要。同样的状态机思路再写一个低温版本,两个状态机的输出作为系统的保护信号。
传感器断线故障的处理也放进状态机。PJ85718DM 的远程通道如果检测到二极管开断,会在状态寄存器里标记出来。固件在读取温度时同时读状态寄存器,如果发现断线标志,直接把状态机切到TEMP_SENSOR_FAULT,并上报故障码,避免应用层把错误数据当作正常温度处理。
4. HVAC 场景的系统集成与扩展
4.1 HVAC 控制器的整体交互逻辑
单独一个温度监测模块不能算完事,它必须嵌入整个 HVAC 控制系统。这套方案最终要接三个模块:屏幕显示、风机/压缩机控制、通信接口。
实际做下来,系统的工作流程是这样的:
- 上电初始化:MCU 初始化时钟和外设,PJ85718DM 初始化并检查连接状态
- 轮询任务:每 500ms 读取一次本地温度和远程温度,经滤波后存入全局结构体
- 控制任务:500ms 的任务根据温度值驱动风机 PWM 输出,1s 的任务做告警状态机更新
- 上报任务:通过 RS485 Modbus 把温度和控制状态上报到楼宇自控系统
- 人机交互:LCD 屏幕每 1 秒刷新一次温度显示,同时响应按键设置阈值
这里有个值得强调的点:I2C 操作和耗时任务要分开。不要在 I2C 读写操作期间关中断,也不要在一个循环里连续做多次 I2C 读,特别是传感器 ADC 转换未完成时,连续读只会读到旧数据。用 RTOS 的话,把温度采集放在一个独立任务里,信号量或者消息队列通知应用任务。
4.2 RS485 通信与数据上报
HVAC 项目里最常见的上位机协议是 Modbus RTU。STM32F303RC 没有内置 RS485 PHY,需要外接一个收发器芯片,比如便宜好用的 SP3485。方向控制引脚通过 MCU 的 GPIO 来控制,发送数据时拉高 DE,发送完毕拉低,这个切换要注意时机,否则第一个字节或者最后一个字节会丢失。
温度数据在 Modbus 寄存器里以 0.1°C 为单位用 int16 上传,比如 25.3°C 就传 253。这样既保留了精度,又方便上位机直接显示。每条温度通道占用两个寄存器(一个传值,一个传状态码),通信分包 8 个寄存器组成一帧,上位机一次性读完。
实测下来,Modbus 通信的波特率按 9600 或 19200 配置比较稳,38400 以上在工业现场容易被干扰。帧间隔要根据波特率算好,标准 Modbus 要求 3.5 个字符长度的空闲时间作为帧分隔,代码里用定时器精确计时,别用HAL_Delay,那个不准。
4.3 低功耗关联设计
虽然 HVAC 控制器通常长时间通电,不一定要求极低功耗,但如果这个监测模块要用在电池供电的无线传感节点上,有几个设计点值得留意。
STM32F303RC 有多个低功耗模式:Sleep、Stop、Standby。温度监测场景下,最常用的是 Stop 模式,唤醒时间相对适中,RAM 数据能保留。思路是:每 10 秒唤醒一次,读取 PJ85718DM 温度,然后继续睡,只有温度越过阈值才立即唤醒 MCU 做告警处理。
这里用到了 PJ85718DM 的报警输出引脚——温度超限时,传感器可以直接把 MCU 从 Stop 模式唤醒,而不需要 MCU 反复醒来查询。这个设计能显著降低平均电流。实际测量中,10 秒周期的平均电流可以做到 10µA 以下(取决于传感器本身的功耗和 I2C 上拉电阻漏电流)。
如果确实需要进一步压低功耗,I2C 上拉电阻可以在 MCU 控制下动态通断,空闲时关掉上拉电源,节省漏电流。这个小技巧我在低功耗数据记录仪项目里用过,效果不错,代价是多占用一个 GPIO 控制一个 P-MOS 管,这里作为可选优化提一下。
5. 常见问题与排查经验
5.1 I2C 总线卡死与恢复策略
这是嵌入式 I2C 项目里最高的故障,没有之一。表现为:MCU 读不到传感器数据、SCL 线一直低电平、HAL_I2C 调用永远返回超时。
原因通常是三类:总线竞争、从机异常拉低 SCL、时钟拉伸未正确释放。排查步骤按顺序来:
- 先用示波器测 SCL、SDA 静态电平:正常时都是高电平(被上拉电阻拉高),如果有一根线为低,说明有设备拉住了总线,或上拉电阻虚焊
- 如果 SCL 是正常的,SDA 在通信时乱飞,检查 I2C 地址是否正确,PJ85718DM 地址可能受引脚电平影响
- 如果通信一段时间后才卡死,重点怀疑从机在 NACK 之后进入异常状态,MCU 侧必须处理 NACK——收到 NACK 后发送 STOP,再对 I2C 外设做清除错误标志操作
- 软件复位 I2C 外设,或者把引脚重配成 GPIO 模式,手动产生 9 个时钟脉冲,让从机跳出错误状态
STM32 的 HAL 库里HAL_I2C_Init之后,遇到总线错误时,很多工程师直接重新调用 Init,但实际我发现光 Init 不够,最好先把外设 DeInit,再置位I2C_CR1_SWRST复位外设,等待几个时钟周期后重新初始化,效果稳定得多。
如果现场出现偶发卡死,先别急着改代码,去看 I2C 总线上拉电阻阻值。总线上每多挂一个设备,寄生电容就多一分,阻值偏大时,上升沿不够陡峭,抗干扰能力显著下降。这是硬件问题,软件只能兜底,不能根治。
5.2 远程温度读数跳变
远程通道测温读数不稳定,一会儿正常一会儿偏低,几种常见原因和处理方式:
- 电容失配:两根走线寄生电容不一致,会导致压降采样不准确。用双绞线或者等长走线。如果产品已经定型改不了 PCB,可以在输入端并联一个大电容给两根线做耦合,减小差值。
- 噪声耦合:远程二极管线缆靠近大电流走线时,50Hz 或开关噪声会耦合到信号里。把线缆换成屏蔽双绞线,屏蔽层单端接地,能解决大部分问题。
- 接线接触不良:远程端是端子连接的话,振动环境中偶发接触不良,读数瞬间跳变。选择带锁扣的端子,或者在二极管端加一滴导热硅胶固定。
- 二极管类型不匹配:换了一颗不同厂商的三极管或二极管后温度读数系统性偏离,这是理想因子不匹配。需要重新标定,或者选择允许设置“diode ideality factor”的传感器型号。
5.3 上电初始读数异常
上电后第一次读温度时,有的工程师发现读回来一个很大的负数,比如 -128°C。这个现象的原因通常是传感器内部 ADC 还没完成第一次转换,读取时拿到的寄存器内容是上电默认值或乱码。处理方法是:上电后先延时 200ms 以上(具体参考数据手册的转换启动时间),然后连续读两次,丢弃第一次的无效结果,再进入正常轮询。
还要检查一个容易忽略的寄存器——配置寄存器里的转换分辨率。如果之前有人把分辨率改成了低比特模式,读回来的数值跳动范围会变大。初始化函数里建议显式地把分辨率设为最高档,把平均值采样次数设为最大,虽然转换时间变长,但精度和稳定性收益明显。
5.4 现场多台设备数据互相干扰
这个坑发生在一台 HVAC 主机控制器带多个从设备,或者多个传感器共用一个 I2C 总线时。如果你用的是 SMBus 协议兼容模式,要注意它和 I2C 的一个重大差异:SMBus 有超时机制,设备在低电平超过 35ms 后会主动释放总线;I2C 没有这个机制,从机拉死总线就永远死锁。
另外,多从机共用一条 I2C 总线时,PJ85718DM 可能有地址引脚(或者有多个地址版本),要确保每台设备的地址不冲突。如果传感器地址不可配置且只有一种地址,总线上挂多颗这种芯片就行不通,需要用 TCA9548A 这类 I2C 多路开关切分总线。我在一个项目里就用了一颗 TCA9548A,把四路温度传感器分到四个通道,每个通道单独上拉电阻,隔离效果也很好,还顺手解决了地址冲突和总线电容过大两个问题。
5.5 温升超过预期的问题排查
如果发现读出来的温度明显偏高于环境实测值,先做减法找自热源。把传感器供电断开,单独测传感器周围的 PCB 温度,再上电对比。如果差异大于 1°C,说明电路板上的热传导显著影响传感器。处理优先级如下:
- 把传感器移动到远离功率器件的位置(最有效)
- PCB 上把传感器下方和周围的铜皮挖掉一部分,阻断热传导路径
- 降低传感器的工作频率,减少自热(比如从连续转换改成单次触发转换)
6. 实操心得与后续扩展方向
6.1 原型验证阶段的踩坑记录
第一次搭这套系统时,我犯了两个低级错误,在这里写出来给大家当反面教材。
第一个错误:远程二极管接线用的普通杜邦线,线长 30cm,没有双绞,也没有屏蔽。当时系统整体在实验室环境调试,温度读数偶尔跳变,当时没在意,以为是滤波参数没调好。直到后来我拿一个加热电阻放在远程二极管旁边做温度响应测试,发现读数能偏差 5°C 以上,才意识到是线缆间的耦合噪声问题。换了双绞屏蔽线之后,读数稳定度立刻改善。
第二个错误:固件里刚开始没有坏点剔除,只做了滑动平均。有一次调试时我在 I2C 总线上插入了一台逻辑分析仪,也许是因为接入方式不当,瞬间干扰让传感器返回了一个错误数据——0x8000 对应的温度大约是 -128°C。滑动平均窗口里混入这个值后,连续 4 个周期显示异常低温,系统竟然触发了“管路冻结保护”,整个控制器停止输出。这个教训是深刻的:任何通信接口都可能产生错误数据,而错误数据永远不能进入控制逻辑。从那以后我的所有传感器驱动,第一件事就是数据范围校验。
6.2 如何利用 F303 的 DSP 指令优化算法
STM32F303RC 的 Cortex-M4F 内核支持 DSP 扩展指令,包括饱和运算、SIMD 指令、单周期乘加等。温度滤波算法里的乘法和累加操作可以使用这些指令加速。如果使用 GCC 编译,需要在编译选项里开启-mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mthumb,同时开启-O2优化。实测下来,纯软件滤波和 DSP 优化之间的差异在温度监测场景并不明显,因为数据量本来就小,但是如果你把同样的处理思路用在振动信号或电流采样上,DSP 指令的效率提升就非常有感了。
更重要的一点是,F303 的 FPU 是硬件的,做浮点运算不需要软件模拟,这让我们可以在嵌入式端直接用 PID 控制算法而不会占用太多 CPU。我在这个项目里做了一个简易的温度 PID 控制器:目标温度、当前温度、偏差积分、微分项,双精度浮点运算直接跑,CPU 占用率不到 5%。
6.3 方案往边缘计算方向扩展的思路
温度监测只是数据入口。把这套系统再往上扩展,可以做不少有意思的事情:
- 趋势预测:把历史温度数据存到 MCU 的 Flash 或者外置 Flash,做简单的线性回归预测温度走向。HVAC 场景中,提前预测到冷凝器温度持续上升趋势,可以提前启动保护,而不是等温度到了阈值才反应。
- 传感器自诊断:通过比对本地通道和远程通道的温度差异,判断传感器是否失效或者失准。比如当远程通道显示 -40°C 而本地通道显示 28°C 时,大概率二极管已经脱落或断线。
- 自适应阈值:根据室外温度或者季节自动调整告警阈值。比如夏季冷凝器排气管温度阈值可以设高一些,冬季设低一些。这个逻辑在 MCU 端就能实现,不需要上位机参与。
这些扩展方向用 F303RC 的资源都能跑得动,而且整个系统的框架不需要推翻重来。这就是最初分层设计的好处——驱动层接口不变,应用层随便加功能。
我个人在实际开发中的体会是,温度监测在嵌入式系统里看着是最不起眼的功能,但它的工程质量却最能体现一个开发者的基本功。传感器选型、电路布局、I2C 抗干扰、数据可靠性、告警逻辑、系统集成,每一个环节都有足够的细节让新手栽跟头。如果你正在计划做 HVAC 控制或者任何需要多点测温的嵌入式项目,建议从“一颗多通道传感器 + 一个带 FPU 的 MCU”这个组合起步,先跑通采集链路,再考虑功能扩展。这套方案的容错空间和扩展潜力,会随着项目深入越来越有感觉。