1. 从一颗温度传感器说起:为什么HVAC场景需要本地+远程双路测温
做嵌入式暖通空调(HVAC)项目的人都有一个共识:温度采集看起来简单,实际上是最容易翻车的环节之一。一颗传感器读数漂移两度,可能就让整个楼层的温控策略跑偏;远程测温链路多一个环节,就可能引入几十毫秒的延迟和额外的噪声。我最近在做一个中小型商用空调控制器的方案验证,核心需求很明确——既要监测设备本体的进风口温度(本地),又要通过有线链路读取远端房间的室温(远程),两路数据都要在同一个MCU上完成采集、处理和上报。
选型的起点是PJ85718DM这颗温度传感芯片配合STM32F042K6这颗Cortex-M0内核的MCU。为什么是这两颗?先说PJ85718DM,它是一颗支持本地测温加远程二极管测温的传感器IC,本地通道测的是芯片自身所处的PCB环境温度,远程通道则通过外接一个二极管接法的三极管(通常是低成本NPN如MMBT3904)来感知远端位置的温度。这种"一颗芯片管两路"的架构,在HVAC控制器里非常实用——主板上的本地温度用来做冷热源侧的补偿,远程温度用来做房间侧的闭环控制。
STM32F042K6这边,48MHz主频、32KB Flash、6KB RAM,带I2C和CAN外设,封装小巧,成本控制得住。对于只需要处理两路温度、几路继电器输出和一路通信的控制器来说,资源刚好够用,不会浪费。我试过用更高端的F1系列来做同样的事,结果发现大部分外设都闲着,纯属浪费BOM成本。
这篇文章面向的是正在做HVAC控制器、嵌入式温度采集模块,或者任何需要"本地+远程"双路测温方案的工程师。我会把从硬件连接到寄存器配置、从温度换算到误差校准的完整链路拆开讲,包括我在实测中踩过的几个坑。不管你是刚接触温度传感器的新手,还是做过几轮项目想优化精度的老手,应该都能找到有用的东西。
2. PJ85718DM的测温机制:本地通道和远程通道到底怎么工作
2.1 本地测温的物理原理与数据格式
PJ85718DM的本地温度通道本质上是一个带隙基准温度传感器。芯片内部有一个与绝对温度成正比(PTAT)的电流源,这个电流经过一个高精度ADC转换成数字量。芯片出厂时会在室温下做一次校准,把校准系数存在内部寄存器里,所以上电后直接读寄存器就能拿到温度值,不需要用户自己做两点校准。
数据格式上,本地温度寄存器是11位有效数据加符号位,分辨率是0.125°C。什么意思呢?就是最低位代表0.125度,读出来的原始值乘以0.125就是摄氏度。比如读到0x0640,换算成十进制是1600,乘以0.125等于200,但要注意符号位和补码的处理——如果是负温度,高字节的最高位是1,需要按补码规则转换。我见过有工程师直接把原始值当无符号数算,结果零下环境测出来是两百多度,排查了半天才发现是符号位没处理。
本地通道的测温范围是-40°C到+125°C,精度在0°C到+85°C区间内是±1°C,全温区±2°C。这个精度对于HVAC应用足够了,因为房间温度控制的死区通常都设在±0.5°C以上,传感器本身±1°C的误差在系统层面可以通过软件补偿来收敛。
2.2 远程通道的二极管测温法与串联电阻补偿
远程通道是这颗芯片真正有意思的地方。它通过两个引脚(D+和D-)外接一个二极管接法的三极管,利用三极管基极-发射极电压VBE与温度的关系来测温。具体来说,芯片会交替注入两个不同大小的电流(比如10μA和100μA),测量两次VBE的差值ΔVBE。根据半导体物理,ΔVBE = (kT/q) × ln(N),其中N是电流比,k是玻尔兹曼常数,q是电子电荷,T是绝对温度。这个ΔVBE与温度成严格的正比关系,而且因为是两个电流下的差值,三极管本身的饱和电流Is被消掉了,所以对三极管的个体差异不敏感。
但这里有个坑:D+和D-引脚到三极管之间如果走线太长,线路电阻会引入额外的压降,导致测温偏高。PJ85718DM支持串联电阻补偿,内部有一个补偿寄存器,可以抵消掉一定范围的线路电阻影响。我实测过,用0.1mm线径的走线拉两米,线路电阻大约0.7欧姆,如果不补偿,读数会偏高约1.5°C。补偿的方法是在寄存器里写入估计的电阻值,芯片会在计算时自动扣除。
远程测温的范围是-40°C到+150°C,精度在+25°C到+100°C区间是±1°C,比本地通道略宽但精度相当。HVAC场景里远程探头通常放在回风口或者房间墙壁上,温度范围不会太极端,这个指标完全够用。
2.3 两路通道的采样时序与更新速率
PJ85718DM内部有一个状态机,按照配置的速率轮流采样本地和远程通道。转换速率可以通过配置寄存器设置,从每秒1次到每秒8次不等。对于HVAC应用,温度变化本身很慢,每秒1次足够了,设太快反而增加功耗和自热。
这里要注意自热效应。芯片工作时自身会发热,如果采样速率太高,芯片温度会略高于环境温度,导致本地通道读数偏高。我实测在每秒8次的速率下,芯片自热大约0.3°C;降到每秒1次,自热降到0.1°C以内。所以如果不是需要快速响应的场景,建议把速率设低一些。
两路通道的数据分别存在不同的寄存器里,MCU读取时要注意先读高字节再读低字节,避免在读取过程中数据更新导致高低字节不匹配。PJ85718DM支持一种"读锁存"机制,读取高字节时会锁存低字节,读完低字节后自动解锁,这样就能保证数据一致性。这个细节在数据手册里写得不太显眼,但实际用起来很关键。
3. STM32F042K6侧的I2C驱动与寄存器操作实战
3.1 硬件连接与上拉电阻的取值计算
PJ85718DM通过I2C接口和STM32F042K6通信,标准模式100kHz或快速模式400kHz都支持。硬件连接上,SDA和SCL各需要一颗上拉电阻接到3.3V。上拉电阻的取值不是随便选的,要根据总线电容和通信速率来算。
I2C总线的上升时间要求是:标准模式不超过1000ns,快速模式不超过300ns。上升时间t = 0.847 × R × C,其中R是上拉电阻,C是总线总电容。假设总线电容估计为100pF(包括PCB走线、引脚电容和器件电容),快速模式下要求t ≤ 300ns,则R ≤ 300ns / (0.847 × 100pF) ≈ 3.5kΩ。所以快速模式下上拉电阻不能超过3.5kΩ,常用2.2kΩ或3.3kΩ。标准模式下可以放宽到10kΩ。
我一般用4.7kΩ做标准模式,2.2kΩ做快速模式。但要注意,上拉电阻越小,总线空闲时流过上拉的电流越大,功耗越高。对于电池供电的场景,需要在速率和功耗之间权衡。HVAC控制器通常是市电供电,这点功耗无所谓,所以直接用2.2kΩ跑400kHz。
STM32F042K6的I2C引脚是PB6(SCL)和PB7(SDA),需要配置为复用开漏模式,并且使能内部上拉(虽然外部已经有上拉了,内部上拉可以增加一点裕量,但不要依赖内部上拉,它的阻值太大,约40kΩ,驱动能力不够)。
3.2 初始化代码:从时钟使能到I2C参数配置
STM32F042K6的I2C初始化有几个关键参数:时钟频率、占空比、自身地址、应答控制等。下面是我实际用的初始化代码,基于标准外设库风格,用寄存器操作的方式写,方便理解每一步在做什么。
// 使能GPIOB和I2C1时钟 RCC->AHBENR |= RCC_AHBENR_GPIOBEN; RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // 配置PB6(SCL)和PB7(SDA)为复用开漏模式 GPIOB->MODER &= ~(GPIO_MODER_MODER6 | GPIO_MODER_MODER7); GPIOB->MODER |= (GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1); // 复用模式 GPIOB->OTYPER |= (GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7); // 开漏输出 GPIOB->OSPEEDR |= (GPIO_OSPEEDR_OSPEEDR6 | GPIO_OSPEEDR_OSPEEDR7); // 高速 GPIOB->AFR[0] |= (1 << 4) | (1 << 8); // PB6->AF1(I2C1_SCL), PB7->AF1(I2C1_SDA) // I2C1复位 I2C1->CR1 |= I2C_CR1_SWRST; I2C1->CR1 &= ~I2C_CR1_SWRST; // 配置时钟:48MHz PCLK,目标400kHz // CCR = PCLK / (2 * 目标频率) = 48MHz / (2 * 400kHz) = 60 I2C1->CCR = 60; // 上升时间:最大300ns,TRISE = (300ns / (1/48MHz)) + 1 = 15.4 -> 16 I2C1->TRISE = 16; // 使能I2C I2C1->CR1 |= I2C_CR1_PE;这段代码里最容易出错的是CCR的计算。STM32F042的I2C是旧版外设,CCR的计算公式是PCLK / (2 × 目标频率),而不是新版I2C的PCLK / 目标频率。我一开始按新版的公式算,结果通信速率只有200kHz,虽然也能通,但没跑到预期速率。后来查了参考手册才发现这个差异。
TRISE的计算也要注意,它是以PCLK周期为单位的上升时间加1。48MHz下每个周期约20.8ns,300ns对应约14.4个周期,加1取整为16。如果TRISE设小了,高速通信时波形会畸变,导致通信失败。
3.3 读写PJ85718DM寄存器的完整流程
PJ85718DM的I2C从机地址是0x48(7位地址),写操作时地址字节是0x90,读操作是0x91。寄存器地址是8位的,每个寄存器16位数据,高字节在前。
写寄存器的流程是:发送起始条件 → 发送从机地址+写 → 发送寄存器地址 → 发送数据高字节 → 发送数据低字节 → 发送停止条件。读寄存器的流程是:发送起始条件 → 发送从机地址+写 → 发送寄存器地址 → 发送重复起始条件 → 发送从机地址+读 → 读取高字节 → 读取低字节 → 发送停止条件。
下面是我封装的两个函数,用轮询方式实现,没有用中断或DMA,因为温度采集对实时性要求不高,轮询足够且代码简单。
// 写16位寄存器 uint8_t PJ85718_WriteReg(uint8_t reg, uint16_t data) { uint32_t timeout; // 发送起始条件 I2C1->CR1 |= I2C_CR1_START; timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_SB)) { if (--timeout == 0) return 1; } // 发送从机地址+写 I2C1->DR = 0x90; timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_ADDR)) { if (--timeout == 0) return 2; } (void)I2C1->SR2; // 清除ADDR标志 // 发送寄存器地址 I2C1->DR = reg; timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_TXE)) { if (--timeout == 0) return 3; } // 发送数据高字节 I2C1->DR = (data >> 8) & 0xFF; timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_TXE)) { if (--timeout == 0) return 4; } // 发送数据低字节 I2C1->DR = data & 0xFF; timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_BTF)) { if (--timeout == 0) return 5; } // 发送停止条件 I2C1->CR1 |= I2C_CR1_STOP; return 0; } // 读16位寄存器 uint8_t PJ85718_ReadReg(uint8_t reg, uint16_t *data) { uint32_t timeout; // 发送起始条件 I2C1->CR1 |= I2C_CR1_START; timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_SB)) { if (--timeout == 0) return 1; } // 发送从机地址+写 I2C1->DR = 0x90; timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_ADDR)) { if (--timeout == 0) return 2; } (void)I2C1->SR2; // 发送寄存器地址 I2C1->DR = reg; timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_TXE)) { if (--timeout == 0) return 3; } // 发送重复起始条件 I2C1->CR1 |= I2C_CR1_START; timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_SB)) { if (--timeout == 0) return 4; } // 发送从机地址+读 I2C1->DR = 0x91; timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_ADDR)) { if (--timeout == 0) return 5; } (void)I2C1->SR2; // 准备接收两个字节 I2C1->CR1 |= I2C_CR1_ACK; // 等待第一个字节 timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_RXNE)) { if (--timeout == 0) return 6; } *data = (I2C1->DR << 8) & 0xFF00; // 清除ACK,准备接收最后一个字节 I2C1->CR1 &= ~I2C_CR1_ACK; // 等待第二个字节 timeout = 10000; while (!(I2C1->SR1 & I2C_SR1_RXNE)) { if (--timeout == 0) return 7; } *data |= I2C1->DR & 0xFF; // 发送停止条件 I2C1->CR1 |= I2C_CR1_STOP; return 0; }这两个函数里,超时机制是必须的。I2C总线如果因为干扰或者从机异常导致时钟被拉低,轮询会死循环。加超时后,函数会返回错误码,上层可以决定重试还是报警。我实际项目中遇到过传感器上电时序不对导致I2C无应答的情况,有了超时就能检测到并重新初始化。
读函数里有一个细节:接收最后一个字节前要清除ACK,然后发停止条件。如果忘了清ACK,从机会继续发下一个字节,导致总线卡住。这个坑我在早期调试时踩过,现象是读一次之后总线就死了,必须断电重启。
4. 温度换算、校准与误差处理:从原始码到可信读数
4.1 本地温度与远程温度的换算公式
本地温度的换算前面提过,原始值乘以0.125就是摄氏度。但要注意数据是13位还是11位的问题。PJ85718DM的本地温度寄存器是16位,其中高13位有效,低3位保留。实际有效数据是13位,包括符号位。换算时先取高13位,然后判断符号位,如果是负数,按补码转成有符号整数,再乘以0.125。
远程温度的换算稍微复杂一点。远程通道的原始数据是14位有效,分辨率也是0.125°C,但它的编码方式不同。远程温度寄存器的高14位是数据,低2位保留。数据是以0.125°C为单位的无符号数,但有一个偏移量。具体来说,远程温度 = 原始值 × 0.125 - 64。这个偏移量是因为远程通道的测量范围从-64°C开始,原始值0对应-64°C。
我一开始没注意这个偏移,直接把原始值乘以0.125,结果室温25度测出来是89度,差了64度。后来翻数据手册才发现这个偏移。所以远程温度的换算一定要记得减64。
4.2 串联电阻补偿寄存器的实际配置方法
远程通道的串联电阻补偿是通过一个叫"远程电阻补偿"的寄存器来配置的。这个寄存器写入的值代表估计的线路电阻,单位是欧姆,范围0到100欧姆,步进约0.5欧姆。芯片在计算时会根据这个值扣除线路电阻引入的误差。
怎么估计线路电阻?如果你知道走线的长度、线径和材质,可以算出来。铜的电阻率是0.0175 Ω·mm²/m,假设走线长2米,线径0.2mm(截面积约0.0314mm²),则单线电阻 = 0.0175 × 2 / 0.0314 ≈ 1.1欧姆。D+和D-各一根,总共约2.2欧姆。但实际影响测温的是D+和D-之间的电阻差,如果两根线等长同径,差值为零,理论上不需要补偿。但实际走线很难完全对称,所以还是会有残余误差。
更实用的方法是用已知温度校准。把远程三极管放在一个已知温度的恒温槽里(比如25°C),读出差值,然后反推需要的补偿值。我一般先不补偿,读一次,然后根据偏差调整补偿寄存器,迭代两三次就能收敛到±0.2°C以内。
补偿寄存器的地址是0x0B,写入的值是估计电阻的整数部分。比如估计2欧姆,就写2。注意这个寄存器是8位的,不是16位,写的时候只写一个字节。
4.3 实测中的噪声抑制与滑动平均滤波
温度读数难免有噪声,尤其是远程通道,因为三极管的引线可能拾取环境噪声。我在实测中观察到,不滤波的情况下,远程温度读数会在±0.5°C范围内跳动。对于HVAC控制来说,这个跳动会导致继电器频繁动作,缩短寿命。
最简单的滤波是滑动平均。我一般取8个采样点做平均,这样噪声能降到±0.1°C以内,而且响应延迟只有8秒(按每秒1次采样算),对温度控制来说完全可以接受。滑动平均的实现很简单,用一个环形缓冲区存最近8次读数,每次新数据进来就替换最旧的数据,然后求平均。
#define FILTER_SIZE 8 static int32_t temp_buf[FILTER_SIZE]; static uint8_t buf_idx = 0; static uint8_t buf_full = 0; int32_t Filter_Temperature(int32_t new_temp) { int32_t sum = 0; uint8_t i; temp_buf[buf_idx] = new_temp; buf_idx = (buf_idx + 1) % FILTER_SIZE; if (buf_idx == 0) buf_full = 1; uint8_t count = buf_full ? FILTER_SIZE : buf_idx; for (i = 0; i < count; i++) { sum += temp_buf[i]; } return sum / count; }如果噪声特别大,可以考虑中值滤波加滑动平均的组合。先取3个点取中值,再做8点滑动平均。这样能同时抑制脉冲噪声和高频噪声。但中值滤波需要排序,代码量稍大,对于F042K6这种小资源MCU,如果RAM紧张,可以只用滑动平均。
还有一个硬件层面的降噪技巧:在远程三极管的D+和D-引脚附近各加一颗100nF的电容到地,能有效滤掉高频干扰。但电容不能太大,否则会影响芯片注入电流的建立时间,导致测温不准。100nF是我实测下来比较合适的值,再大就开始影响精度了。
5. 本地与远程数据的协同处理:HVAC控制策略中的温度融合
5.1 两路温度的角色分工与优先级
在HVAC控制器里,本地温度和远程温度不是简单的二选一,而是各有分工。本地温度反映的是控制器安装位置的环境温度,通常靠近回风口或者设备间,用来做设备保护(比如防止蒸发器结冰)和冷热源侧的负荷估算。远程温度反映的是被控房间的实际温度,是闭环控制的反馈量。
我通常的策略是:远程温度作为主控温度,用于PID调节;本地温度作为辅助,用于限幅和保护。比如当远程温度传感器故障(读数超范围或变化率异常)时,自动切换到本地温度作为后备,同时上报故障。这样即使远程探头坏了,系统还能降级运行,不会完全失控。
优先级上,远程温度正常时权重100%,本地温度只做监测;远程温度异常时,本地温度接管,但控制精度会下降,因为本地温度不等于房间温度。这时候可以给用户发一个维护提醒,但不影响基本运行。
5.2 远程传感器故障检测的三种判据
远程传感器故障怎么判断?我总结了三种判据,实际项目中组合使用。
第一种是范围判据。远程温度的合理范围是-40°C到+125°C,如果读数超出这个范围,直接判故障。但要注意,传感器开路时读数会跑到极端值(比如+150°C以上),短路时可能读到-64°C(偏移后的零点),这两种情况都能被范围判据捕获。
第二种是变化率判据。温度变化是连续的,如果两次采样之间变化超过5°C,很可能是干扰或故障。正常HVAC场景下,温度变化率不会超过1°C每分钟。所以如果1秒内变化超过5°C,可以判为异常,连续3次异常则确认故障。
第三种是合理性判据。本地温度和远程温度虽然不等,但应该在同一气候区域内,差值不会太离谱。如果本地25°C,远程读到80°C,那远程肯定有问题。我一般设差值阈值为30°C,超过就报警。
这三种判据组合起来,能覆盖绝大多数故障场景。误报率也低,我实测跑了三个月,没有出现过误报。
5.3 温度数据的上报格式与通信协议设计
温度数据最终要上报给上位机或者云端。上报格式我一般用简单的二进制协议,两个字节表示一个温度值,高字节在前,单位是0.1°C,有符号数。比如25.3°C表示为0x00FD(253),-10.5°C表示为0xFF97(-105的补码)。
为什么用0.1°C而不是0.125°C?因为0.1°C更符合人的阅读习惯,而且上位机解析时不用做额外的换算。虽然传感器分辨率是0.125°C,但上报时四舍五入到0.1°C,精度损失可以忽略。
通信协议上,如果是有线RS485,可以用Modbus RTU,温度寄存器直接映射到保持寄存器里。如果是CAN总线,可以自定义报文,每帧8字节,前两字节是本地温度,接着两字节是远程温度,再两字节是状态字,最后两字节保留。STM32F042K6自带CAN外设,用起来很方便。
我实际项目中用的是CAN,因为HVAC控制器通常挂在同一个CAN网络上,和风机、阀门控制器共享总线。CAN的差分信号抗干扰能力强,适合楼宇环境的长距离通信。波特率设125kbps,总线长度可以到500米,足够覆盖一般楼层。
6. 调试与验证:几个让我印象深刻的踩坑记录
6.1 上电后I2C无应答:电源时序与复位引脚的处理
第一批样板回来,上电后MCU读PJ85718DM一直超时,示波器看SDA和SCL都有波形,但从机就是不拉低SDA应答。排查了半天,最后发现是PJ85718DM的复位引脚(RESET)悬空了。这颗芯片的复位引脚是低电平复位,内部有上拉,但悬空时容易受干扰,导致芯片一直处于复位状态。
解决办法很简单,在复位引脚上加一颗10kΩ上拉到3.3V,再并一颗100nF到地。这样上电时复位引脚被可靠拉高,芯片正常启动。这个坑让我养成了一个习惯:任何芯片的复位引脚,不管手册说内部有没有上拉,都外部加一颗上拉电阻,成本几分钱,省去几小时的调试时间。
还有一个相关的问题是电源时序。PJ85718DM的供电范围是2.7V到5.5V,STM32F042K6是2.0V到3.6V。如果两者用同一个3.3V电源,上电时序基本一致,没问题。但如果传感器用5V供电,MCU用3.3V,就要注意I2C电平匹配。PJ85718DM的I2C引脚是开漏的,可以拉到5V,但STM32F042K6的引脚不耐5V,需要加电平转换或者用分压电阻。我一般统一用3.3V供电,省去电平转换的麻烦。
6.2 远程温度读数偏高:从走线电阻到补偿寄存器的排查链路
前面提到远程温度偏高的问题,我实际遇到过一次偏差3°C的情况。排查过程是这样的:先确认三极管本身没问题,换了一颗新的,偏差依旧;然后检查走线,发现D+和D-的走线长度差了将近一倍,D+绕了一个大弯。这就导致两根线的电阻不对称,引入了额外的误差。
重新布线,让D+和D-等长且靠近,偏差降到1°C左右。然后启用串联电阻补偿,写入估计的电阻值,偏差进一步降到0.3°C以内。最后用滑动平均滤波,读数稳定在±0.1°C。
这个排查链路的关键是先排除硬件不对称,再考虑补偿。如果走线本身差太多,补偿寄存器也救不回来,因为补偿是假设两根线电阻相等、只补偿共模电阻的。所以PCB布局时一定要让D+和D-等长,最好并行走线,减少不对称。
6.3 长时间运行后的数据漂移:自热效应与PCB布局的影响
有一个项目跑了半年后,客户反馈温度读数比实际偏高约1°C。我去现场排查,发现控制器安装在密闭的电气柜里,柜内温度比柜外高5°C。本地温度测的是柜内温度,自然偏高。远程温度探头在柜外,读数正常。但控制策略用的是本地温度做补偿,导致整体控制偏高。
解决办法是把本地温度只用于设备保护,不参与房间温度控制。房间控制完全依赖远程温度。同时建议客户改善电气柜的通风,降低柜内温升。调整后,控制精度恢复到±0.5°C。
这个案例说明,本地温度虽然方便,但它测的是控制器所在位置的温度,不一定代表被控环境的温度。在HVAC应用里,远程温度才是控制的核心,本地温度是辅助。设计控制策略时一定要明确这一点,否则容易出系统性偏差。
还有一个自热效应的问题。PJ85718DM在连续工作时,芯片自身会发热,尤其是供电电压较高时。我实测在5V供电、每秒8次采样的情况下,芯片自热约0.5°C。如果本地温度用于精密测量,这个自热必须考虑。解决办法是降低采样速率、降低供电电压,或者在软件里减去一个固定的自热偏移。我一般用3.3V供电、每秒1次采样,自热控制在0.1°C以内,基本可以忽略。
6.4 用已知温度源做两点校准的实操步骤
如果对精度要求特别高,可以做两点校准。准备一个恒温槽或者冰水混合物(0°C)和沸水(100°C,注意海拔影响),把远程三极管放进去,读出差值,然后计算校准系数。
具体步骤:先把三极管放在冰水混合物里,等读数稳定后记录原始值R0;再放到沸水里,记录原始值R100。理论上R100 - R0应该等于100 / 0.125 = 800。如果实际差值不是800,说明传感器的增益有偏差,需要在校准系数里修正。修正公式是:实际温度 = (原始值 - R0) × (100 / (R100 - R0))。
本地通道的校准类似,但本地通道测的是芯片自身温度,没法单独把芯片放进恒温槽。所以本地通道一般不做两点校准,依赖出厂校准即可。如果一定要校准,可以把整个板子放进恒温箱,做多点校准,但成本较高,一般项目没必要。
我在一个高精度项目里做过远程通道的两点校准,校准后精度从±1°C提升到±0.3°C。但校准过程比较耗时,需要稳定的温度源和足够长的稳定时间(至少10分钟)。对于大多数HVAC应用,出厂校准加软件补偿已经足够,不需要额外做两点校准。
7. 方案扩展:从单点测温到多点分布式监测
7.1 用模拟开关扩展多路远程测温通道
PJ85718DM只有一路远程通道,如果要在多个房间布点,怎么办?一个办法是用模拟开关(比如CD4051)切换多个三极管,分时复用同一路远程通道。CD4051是8选1的模拟开关,导通电阻约100欧姆,会引入额外的串联电阻,需要在补偿寄存器里扣除。
具体接法是:8个三极管分别接到CD4051的8个输入通道,CD4051的公共输出接到PJ85718DM的D+和D-。MCU通过3根GPIO控制CD4051的通道选择。每次切换通道后,等待一段时间(让注入电流稳定),再读取温度。8个通道轮询一遍,按每秒1次的速率,每个通道约8秒更新一次。对于房间温度监测来说,8秒的更新周期完全可以接受。
这个方案的优点是成本低,一颗传感器加一颗模拟开关就能管8个点。缺点是分时复用,不能同时测量,而且模拟开关的导通电阻会引入误差,需要仔细补偿。我在一个8房间的办公楼项目里用过这个方案,实测各通道之间的一致性在±0.5°C以内,满足需求。
7.2 多颗传感器挂同一I2C总线的地址配置
如果不想用模拟开关,也可以挂多颗PJ85718DM在同一I2C总线上。这颗芯片的I2C地址可以通过地址引脚配置,支持4个不同地址(0x48、0x49、0x4A、0x4B)。所以一条总线最多挂4颗,实现4路本地加4路远程,共8个测温点。
地址配置是通过一个引脚(A0、A1)接高或接低来实现的。具体对应关系看数据手册的地址表。挂多颗时要注意总线电容,每颗芯片的引脚电容约10pF,4颗加起来40pF,加上走线电容,总电容可能超过100pF。这时候上拉电阻要相应减小,保证上升时间满足要求。
多颗传感器的读取流程和单颗一样,只是每次要指定不同的从机地址。我一般把4颗传感器的数据放在一个结构体数组里,轮询读取,然后统一滤波和上报。这个方案比模拟开关方案更简单,不需要额外的切换逻辑,但成本稍高,而且受限于4个地址。
7.3 与上位机联动的温度报警与联动控制逻辑
温度数据采集上来后,最终要用于控制。我一般设三级报警:预警、报警、紧急。预警是温度超过设定值2°C,只上报不动作;报警是超过5°C,启动风机或阀门调节;紧急是超过10°C,直接切断设备并上报故障。
联动控制逻辑上,远程温度用于PID调节,输出控制风机转速或阀门开度。本地温度用于限幅,比如当本地温度超过60°C时,强制降低风机转速,保护设备。两路温度都参与逻辑判断,但优先级不同。
PID参数整定是个经验活。HVAC系统的热惯性大,PID的积分时间要设长一些,一般几分钟到十几分钟。微分时间可以设短一些或者不用,因为温度噪声会影响微分项。我一般用PI控制,不用D,参数根据房间大小和风量来调。小房间响应快,积分时间短一些;大房间热惯性大,积分时间长一些。
实际调试时,我先把积分时间设得很长(比如30分钟),只让比例项起作用,观察系统响应。然后逐渐减小积分时间,直到系统能在设定值附近稳定,且超调不超过1°C。这个过程需要耐心,一般要调几个小时。但调好之后,系统能稳定运行,温度波动控制在±0.5°C以内。
温度监测这块,从传感器选型到MCU驱动,从数据换算到滤波校准,再到控制策略和故障处理,每个环节都有细节。PJ85718DM加STM32F042K6这个组合,在HVAC应用里算是性价比很高的方案,硬件成本低,软件资源够用,精度满足需求。我在多个项目里用过这个组合,稳定性不错,值得推荐给做类似应用的同行。