☰
PJ85718DM双通道温度传感器与STM32F031C6的HVAC测温方案
2026/10/22 1:09:03 网站建设 项目流程

如果你做过HVAC控制器,或者哪怕只是给嵌入式设备加过一路远程测温点,大概都经历过来回折腾传感器的日子。我这次要说的项目,目标很朴素:一块小控制板既要测自己周围的板温,又得去测两三米外风道里的温度,数据要稳定、成本要压得住、代码还不能太占资源。折腾一圈之后,方案落到了一颗叫PJ85718DM的两通道温度传感器上,搭配一颗STM32F031C6单片机,总算把本地和远程两路温度都安顿好了。这篇就把完整的选型、接线、代码和现场调试过程都记录下来,给同样在做嵌入式温控或HVAC项目的朋友一个可以直接抄作业的参考。

1. 方案定型:PJ85718DM加STM32F031C6,解决了两地测温难题

1.1 需求拆解时遇到的坑

项目最初的需求只有一句话:同时监测控制板附近温度和风道远端温度,误差在±1℃以内,三个样本就能确认读数稳定。但拆开看就会发现,这句话里藏着两个完全不同的应用环境。

本地温度,也就是板温,通常在MCU、继电器、功率MOS管附近。这个区域的特点是温度变化剧烈,开机瞬间就能看到几度的跳变,而且会受到板卡自身发热的影响。远程温度则要放到风道、回风口或者另一个小箱体里,传感器离开主控板有一段距离,线缆可能要穿过去绕过来,现场电磁环境也比较乱。

一开始我考虑过用两颗独立的数字温度传感器:一颗贴板上,一颗拉到远端,然后MCU用两个I2C地址区分。听起来直接,但问题很快暴露了。传感器地址是芯片出厂定死的,想在一条总线上挂两颗同型号芯片,要么换不同地址型号,要么让板卡上有第二路I2C资源。STM32F031C6本身外设不算丰富,硬要拆一路I2C给传感器,调试和后续维护都麻烦。而且两颗芯片的供电、滤波、上拉都要各做一份,PCB面积和BOM成本都不划算。

后来翻到PJ85718DM这颗芯片,它的思路是把两颗传感器的活合并成一颗:内部有一个本地测温通道,同时还有一个专门接外部温度探头(典型是两线制NTC热敏电阻)的远程通道。MCU只需要通过一组I2C总线读取两个通道的数据就行。这么一来,一颗芯片就解决了“板温+远处温度”两路采集,硬件电路简单了一大截。

1.2 和常见方案放在一起比较

这里把项目前期对比过的几个方案列成一个表,方便你判断同样场景下怎么选。

方案成本走线复杂程度精度表现调试难度最终取舍
两颗独立数字温度传感器中中高中,地址冲突麻烦需要双I2C或换地址,放弃
本地数字传感器+远程PT100高高,需要恒流源和运放调理极高高,电路调试量大精度虽好但严重超预算
本地NTC+MCU内部ADC低低一般,受ADC参考源影响中小只适合对精度要求不高的场合
PJ85718DM本地+远程通道中低低,远程探头两根线单片可达±0.5℃左右低,寄存器读取简单最终选用

可以看到,PT100方案虽然精度天花板很高,但对这种风道测温的嵌入式产品来说,复杂度明显过剩。NTC直接接MCU ADC的方案又太依赖电源和参考电压,稍不留神读数就会飘。PJ85718DM把模拟前端做在芯片内部,远程探头配好之后软件只做数字读取和滤波,整体可控性一下子高了很多。

1.3 STM32F031C6在这套方案里的定位

选STM32F031C6,主要不是因为它有多强,而是F0系列在低成本、够用、好买这三个维度上平衡得不错。C6后缀意味着Flash在32KB左右,RAM也不算大,但恰好够跑一个小控制逻辑:读温度、算滑动平均、做阈值比较、控制继电器、和上位机串口通信。

这片MCU主频48MHz,外设里有硬件I2C、SPI、USART和多个定时器。I2C外设配合HAL库使用,代码量不大,CPU占用也低。风扇控制如果要用PWM,定时器够用,不需要额外芯片。整个系统最后可以把温度采集、报警输出和继电器控制都塞进这颗MCU里,板子做得非常紧凑。

这套方案的另一个隐性优点是传感器和MCU分离。MCU的IO和内部资源再烂,只要I2C能跑起来就能读数据;反过来,传感器的精度和故障不会拖垮主程序。做嵌入式工控这么久,我最怕的就是MCU又管计算又管模拟采集,最后哪头都顾不好。PJ85718DM把模拟部分全部封装好,主控就专注逻辑,这个分层很干净。

2. 硬件抄作业指南:引脚、上拉和远程探头布线

2.1 硬件接线先说清楚

PJ85718DM对外的引脚不多,项目的接法是这样的:VCC接3.3V,GND接地,SDA接MCU的PB7,SCL接MCU的PB6,ALERT接MCU的PB5。另外还有两个远程探头引脚,命名为TP和TN。TP/TN之间接一个NTC热敏电阻,正常工作时芯片内部会周期性地给这个电阻施加激励,通过测量电压变化计算出NTC阻值,再在内部换算成温度数字量。你看到的就是寄存器里的温度结果,不用自己在MCU里做复杂的查表运算。

供电上要注意,这类模拟测温芯片对电源纹波比较敏感。建议在VCC引脚旁边放一个0.1uF陶瓷电容,靠近引脚放置,不要隔太远。如果板子上有继电器、电磁阀这类大电流负载,最好再加一个10uF钽电容做低频储能,避免继电器吸合的瞬间把3.3V拉出明显的毛刺。

STM32F031C6的PB6/PB7默认复用为I2C1。要注意F0系列GPIO的复用功能和不同系列不完全一样,代码里需要初始化GPIO为开漏模式,并在外部接上拉电阻。如果使用HAL库,还需要打开I2C1和GPIOB的时钟。

2.2 I2C上拉电阻取多少才有谱

I2C总线必须外部上拉,这个基本都知道,但取值却有讲究。项目使用400kHz快速模式,总线电容大约在20pF到50pF之间,我最终选的是4.7kΩ上拉电阻,这样既能提供足够的上升沿驱动能力,又不会让漏电流大到影响低电平判断。

如果你硬线距离比较长,或者总线上挂的从设备比较多,可以把上拉降到2.2kΩ。反过来如果I2C速率只有100kHz,且板子走线很短,用10kΩ上拉也行。但我的经验是4.7kΩ是通用性最好的起点,绝大多数场合都能稳定工作,后续再根据示波器测到的沿形状微调。

这里补充一个容易踩坑的点:STM32F031C6的IO引脚在开漏模式下输出高电平依赖外部上拉。如果忘了接上拉,传感器读回来的数据会出现随机错误、NoACK甚至完全不通。总线挂死之后,I2C外设状态机可能一直等下去,程序就卡在那里。遇到这类问题先量SDA/SCL有没有正常的低噪声波形,别急着改代码。

2.3 远程探头布线决定数据准不准

远程探头是整个系统“远程”二字的灵魂。PJ85718DM的TP/TN两个引脚,要求外部连接一个两线制NTC电阻传感器。这个电阻最好选B值在3380K左右的普通负温度系数热敏电阻,因为常温下阻值变化率和芯片内置的换算曲线匹配度最高。

布线时,我建议使用双绞屏蔽线,屏蔽层在主控板端单点接地。远程探头线缆总长我在项目里控制在3米以内,走线不超过一次。如果线太长或者屏蔽层没接好,高频干扰会耦合进TP/TN之间,温度读数就会反复跳,尤其是风机启动或者继电器动作的瞬间。

另外,探头本身也要做防水处理。HVAC风道里湿度不低,普通的玻璃封装NTC直接裸露在风道里,时间长了阻值会漂移。我在实际项目中给探头套了一小截热缩管,再把头部用导热硅胶封住,这样做之后读数长期稳定很多。远程探头不要和强电电缆走同一个线槽,特别是不能和继电器输出线绑在一起,否则EMI会直接叠加进探头信号里。

3. STM32F031C6软件实现:从I2C初始化到温度滤波

3.1 I2C外设的初始化细节

STM32F031C6用HAL库配置I2C很简单,但有几个参数必须填对。下面的代码展示的是I2C1初始化部分,时钟频率设为400kHz,7位地址模式,OwnAddress1填写0即可,因为本机是主模式。

hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 400000; 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; HAL_I2C_Init(&hi2c1);

GPIO初始化部分要把PB6、PB7配置成开漏输出,并使能I2C复用功能。注意这里不能配置成推挽,开漏是I2C的标准接法。时钟使能需要同时打开GPIOB和I2C1的时钟,否则寄存器操作会卡死。

GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_I2C1_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF1_I2C1; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

F0系列的AF映射和F1不一样,PB6/PB7的复用功能号是AF1,而不是其他系列常见的AF4。很多人从老工程复制代码过来直接编译发现I2C不工作,多半就是这个复用号填错了。

3.2 读取PJ85718DM的温度寄存器

PJ85718DM的I2C设备地址我按自己拿到的样品设置为7位0x4D,左移一位后作为8位地址传给HAL库。读取方式很简单:先向芯片写入寄存器地址,然后连续读取两个字节。本地温度寄存器是0x00和0x01,远程温度寄存器是0x02和0x03。

#define PJ85718DM_ADDR (0x4D << 1) #define PJ_TEMP_LOCAL_MSB 0x00 #define PJ_TEMP_LOCAL_LSB 0x01 #define PJ_TEMP_REMOTE_MSB 0x02 #define PJ_TEMP_REMOTE_LSB 0x03 float readPJ85718DM(uint8_t regMSB) { uint8_t buf[2]; HAL_I2C_Mem_Read(&hi2c1, PJ85718DM_ADDR, regMSB, I2C_MEMADD_SIZE_8BIT, buf, 2, 100); int16_t raw = (int16_t)((buf[0] << 8) | buf[1]); raw = raw >> 4; // 只保留高12位有效数据 if (raw & 0x0800) { raw |= 0xF000; // 12位负数做符号扩展 } return (float)raw * 0.0625f; }

这段代码最关键的地方在“raw >> 4”。PJ85718DM的寄存器里12位有效温度数据从高位对齐,最低四位是无效位,必须右移丢弃。如果不丢,返回值会变成整数噪声,读数看起来像有规律地跳动,实际是没对齐。数据手册里给出的温度分辨率是0.0625℃/LSB,所以最后乘0.0625就是实际温度。

读取频率我控制在2Hz,也就是每500ms读一次。对HVAC和嵌入式设备来说,温度变化本身不会太快,用不着每毫秒都去刷。PJ85718DM内部一次转换完成后,数据才刷新,读太快只会重复读同一份结果,还无谓占用I2C总线。

3.3 本地和远程温度的数据滤波

实测下来,远程通道在风机启动瞬间会有轻微毛刺,数值偶尔跳个0.25℃。这种跳变来自电磁干扰,不是真实温度变化。最简单有效的处理方法是滑动平均滤波。我用了十点的滑动窗口,把最近十次读取结果存进数组,每次取平均后输出。

#define FILTER_SIZE 10 static float localBuf[FILTER_SIZE]; static float remoteBuf[FILTER_SIZE]; static uint8_t filterIndex = 0; void updateFilter(float localTemp, float remoteTemp) { localBuf[filterIndex] = localTemp; remoteBuf[filterIndex] = remoteTemp; filterIndex = (filterIndex + 1) % FILTER_SIZE; } float getFilteredValue(float *buf) { float sum = 0.0f; for (uint8_t i = 0; i < FILTER_SIZE; i++) { sum += buf[i]; } return sum / FILTER_SIZE; }

滑动平均会让数据响应滞后,但两秒一次的采样,十点窗口大概滞后5秒左右。用于HVAC的阀值和报警判断,这个滞后完全可以接受,反而不容易因为瞬时跳变误动作。如果你用在需要快速响应的保护逻辑里,窗口可以缩短到4到5点,或者更激进一点,把探头线缆和滤波电容优化好了之后直接读原始值。

3.4 阈值报警和HVAC联动逻辑

PJ85718DM的ALERT引脚支持阈值比较器模式,可以在芯片内部设置高温报警阈值。但实际项目里我没有完全依赖这个引脚,而是在MCU侧把报警逻辑做在软件里。原因是HVAC设备既要防高温,也要防低温,而且还要带回差,芯片内部寄存器只能做简单的上限或下限比较,不如软件灵活。

我的联动逻辑很简单:远程温度作为风道温度,超过60℃时开启风机;降到55℃以下时关停风机。这里5℃回差是关键,否则风机到了60℃开启,61℃关闭,然后又到60℃开启,继电器会反复吸合,现场能听见连续的啪啪声,继电器寿命也肉眼可见地缩短。

#define REMOTE_FAN_ON_TEMP 60.0f #define REMOTE_FAN_OFF_TEMP 55.0f void hvacControl(float remoteTemp) { static uint8_t fanState = 0; if (fanState == 0 && remoteTemp >= REMOTE_FAN_ON_TEMP) { fanState = 1; HAL_GPIO_WritePin(FAN_CTRL_Port, FAN_CTRL_Pin, GPIO_PIN_SET); } else if (fanState == 1 && remoteTemp <= REMOTE_FAN_OFF_TEMP) { fanState = 0; HAL_GPIO_WritePin(FAN_CTRL_Port, FAN_CTRL_Pin, GPIO_PIN_RESET); } }

本地温度则用于板卡保护,我设定在85℃触发紧急停机,并且这个阈值不加热带,一旦触发必须手动复位。板卡上发热器件多,如果本地温度到了85℃,说明散热出了问题,这时候一个劲儿地自己恢复反而危险。

4. 整机装配、校准和现场调试记录

4.1 本地温度探头在PCB上的位置不能随便放

PJ85718DM的本地通道测的是芯片自身附近温度,也就是板卡局部温度。设计时如果把它放在继电器正下方,那读到的就不是环境温度,而是继电器线圈发热被传感器捕捉后的温度。我在样板第一版就吃过这个亏,实测读数比空气温度高4℃左右,后来改到板卡边缘,远离功率器件,才回到正常水平。

此外,PJ85718DM底部最好使用铺铜区域或者独立小铜岛,这样热量能被铜皮均匀传导,局部热斑对测量的影响能小一点。如果传感器底下全是镂空走线,各区域温差大,读数稳定性和重复性都会变差。

4.2 远程温度探头怎么部署最靠谱

远程探头放进风道之后,最好深入风道中心而不是贴壁。风道壁受环境温度影响,贴近壁面测量会明显滞后,数值也不代表风道的真实空气温度。可以在风道壁开一个小孔,把探头插进去一半位置,用支架固定住。如果现场不允许开孔,那就把探头尽量悬空在气流里,不要让探头直接接触金属风道壁。

传感器线缆进入风道的位置要密封,冷天风道内外的温差会让这个位置凝结水珠,水顺着线流进探头会直接造成热敏电阻短路或腐蚀。我用环氧树脂把线缆入口做了密封,至少不会再看到湿度引起的读数乱跳。

4.3 两点校准法:只用两个温度点就能安心

校准最忌拿手摸。手指温度大约三十几度,和真实环境差很多,用体温计一对比往往觉得传感器“不准”,其实是参考温度本身就不可靠。我用的方法是两点转换法:把远程探头拆下来,放在恒温水中,水温用一根工业级温度计做参考,分别在25℃和60℃两个点记录传感器读数和参考值的差,然后补偿。

比如25℃时传感器读数25.4℃,60℃时读数60.6℃,说明传感器有一个约0.5℃的正偏置,在软件里统一减去0.5℃即可。HVAC应用通常不需要全温度段的线性校准,两点做完已经足够。

如果只做一点校准,就尽量选接近工作点的温度。我的系统平时工作在四五十度,那就用45℃做单点校准,把偏差修正掉,效果也不差。校准完成后把偏置值存进MCU的Flash里,别写死在代码里,这样以后换传感器时还能重新标定,不用重新编译固件。

4.4 串口打印和读数核对的调试技巧

调程序的时候,我习惯用串口每隔一秒打印一行:

[LOCAL] 35.12C [REMOTE] 42.18C [FAN] ON

打印出的数值不要只盯着看,最好连续观察五分钟以上,记下最大值和最小值。如果远程温度在稳定环境里的波动超过0.5℃,先怀疑探头位置或者线缆,再怀疑代码滤波。如果本地温度在开机十分钟后才慢慢爬到正常值,那是板卡自热导致的热平衡现象,不是传感器问题。

5. 常见问题与排查技巧实录

5.1 STM32一直读不到PJ85718DM应答怎么办

这种现象最常见的是I2C地址错,或者地址引脚悬空。PJ85718DM的地址由引脚或者厂商烧录配置决定,我用的样品是固定0x4D。如果你的样品地址也是0x4D,那么I2C发送设备地址时,最低位为0表示写。HAL库中传入的地址必须包含读写位,所以我写了(0x4D << 1),这一点尤其容易忽略。

如果地址没错还是NoACK,用示波器量SDA、SCL是否每次都有从设备拉低总线产生的应答位。没有应答,要么是传感器没上电、要么是引脚虚焊、要么是I2C地址写错。建议先用逻辑分析仪抓一次完整的I2C帧,比盲猜快得多。记住I2C总线上每次读写结束后都会看到停止位,如果总线一直卡在高电平不动,多半是外设状态机卡住,可以复位MCU或者干脆重新初始化I2C。

5.2 远程温度数据一直飘,风机一动就乱跳

这类问题的头号根源是远程探头线缆受到干扰,其次是接头接触不良。继电器启动瞬间,触点会产生火花,虽然不大,但会通过线缆容性耦合到TP/TN引脚。我最后的解决方案是换用屏蔽双绞线,屏蔽层在单端接地,并且把探头引线穿了一个磁环再接入主控板。改动之后,干扰毛刺从原来的0.6℃降到了0.1℃以内。

检查时可以先用手握住探头线缆,如果读数发生明显变化,说明线缆屏蔽失效或者NTC引脚进水。还有一条经验:NTC探头头部的热缩管要是破了,哪怕只是细微裂纹,湿度上来后读数就会往低处飘,而且飘得没有规律。

5.3 本地温度总比预期高,尤其是整机跑起来之后

本地通道高个两三度往往不是芯片出错,而是板卡自热。PJ85718DM本地测温点会和MCU、电源芯片共享同一块PCB,整机满载运行时,板卡温度普遍升高,传感器读数自然跟着涨。想要让传感器读“环境温度”,就必须把芯片放在远离发热源的位置,同时尽量避免传感器下方有过多的功率走线。

如果位置没法动,可以在软件里加一个根据负载电流变化的修正系数,但这属于经验修正,只能在固定机型上有效。我通常先通过红外热成像看一下板卡温度分布,确定传感器位置确实偏离热点之后,再决定是否需要改布局。

5.4 I2C偶发卡死,运行几天后温度不更新了

这种情况在强电磁环境下比较常见。I2C总线一旦出现一次NoACK或时钟拉伸异常,外设状态机可能不回到空闲状态,后面的读写就一直发不出去。解决方案有两个层面:硬件上把I2C上拉电阻减小,增强抗干扰能力;软件上给每次读写加超时,并实现总线恢复机制。

用HAL库时,I2C读写函数的最后一个参数是超时时间。我设置为100ms,一旦超时,就调用HAL_I2C_DeInit和HAL_I2C_Init重新初始化外设,再读一次。模拟上也可以把SDA/SCL手动拉低发几个时钟脉冲,释放可能被从设备拉低的总线。实际工程里我用重新初始化外设的办法,问题基本都能解决。

uint8_t readTemperatureWithRecovery(uint8_t reg) { uint8_t buf[2]; HAL_StatusTypeDef status = HAL_I2C_Mem_Read(&hi2c1, PJ85718DM_ADDR, reg, I2C_MEMADD_SIZE_8BIT, buf, 2, 100); if (status != HAL_OK) { HAL_I2C_DeInit(&hi2c1); HAL_I2C_Init(&hi2c1); status = HAL_I2C_Mem_Read(&hi2c1, PJ85718DM_ADDR, reg, I2C_MEMADD_SIZE_8BIT, buf, 2, 100); } // 恢复失败时至少要有一次状态记录,方便排查 return status == HAL_OK ? (uint8_t)((buf[0] << 8) | buf[1]) : 0; }

这种总线自动恢复逻辑在工控板上是刚需。别指望现场环境干净,更别指望没有干扰,防护措施和超时恢复要做在前面。

最后再分享一个实战里验证过的小技巧

很多HVAC项目的远程温度探头都离控制器有一段距离,容易让屏蔽层两端都接地,结果形成地环路,反而引入更大干扰。我最后把屏蔽层只在主控板单端接地,另一端悬空,干扰当场降了一个数量级。另外,给NTC探头套热缩管时,把管口稍微超出探头头部两毫米再加热收缩,封胶时留一个小气孔,避免湿热空气在管内凝结成水珠。就这几处细节,让这套PJ85718DM加STM32F031C6的方案在现场稳定跑了很久,数据一直踏踏实实的。希望这份记录能帮你少走几步弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询