1. 从一颗传感器和一颗MCU说起:这个组合到底在解决什么问题
温度监测这件事,听起来简单,做起来全是细节。尤其是当你需要同时盯着本地机箱内的温度和几十米外某个房间的温度时,问题就来了:用同一个传感器?信号拉不了那么远。用两套独立系统?成本翻倍,数据还对不上。我最初接触这个需求是在一个嵌入式温控项目里,设备本身要监测自身发热情况,同时还要读取远端一个HVAC风管里的温度。当时试过几种方案,最后落到PJ85718DM配合STM32F031C6这个组合上,实测下来在成本、精度和灵活性之间找到了一个很舒服的平衡点。
先把这个组合的角色分工说清楚。PJ85718DM是一颗远端温度传感器,它的核心能力是把感温元件和信号调理电路分开——感温部分可以做成一个很小的探头,通过线缆拉到远处,而信号处理部分留在主板上。它输出的是数字信号,直接给MCU读取,不需要额外的ADC。STM32F031C6则是那颗“大脑”,基于ARM Cortex-M0内核,主频48MHz,自带12位ADC、多个定时器和丰富的外设接口。它在这个方案里干三件事:读取PJ85718DM的远端温度、读取板载温度传感器(或者另一颗本地传感器)的本地温度、然后根据两路温度做逻辑判断和输出控制。
为什么不用STM32自带的内部温度传感器?这个问题我踩过坑。STM32F031C6内部确实有一个温度传感器通道,但它的精度实在感人——数据手册标称±1.5°C的误差,实际用下来在25°C附近偏差能到3°C以上,而且受MCU自身发热影响极大。你让它测环境温度,它测的其实是芯片结温。所以本地温度这一路,我建议要么用一颗独立的数字温度芯片(比如常见的I2C接口型号),要么用NTC热敏电阻加ADC采样。远端那一路,PJ85718DM是专门干这个的,它的设计初衷就是解决远距离测温的信号衰减和噪声问题。
这个方案适合谁?如果你正在做HVAC控制板、嵌入式设备的温度监控、工业现场的远程测温节点,或者任何需要“本地+远端”双温度监测的场景,这套组合值得认真考虑。它不追求极致性能,但胜在成熟、便宜、容易上手。下面我会把选型逻辑、硬件设计、软件实现、标定方法和踩坑经验全部拆开讲,尽量让不同基础的读者都能找到能直接用的东西。
2. PJ85718DM的测温原理与远端信号传输的关键设计
2.1 远端测温为什么不能用普通传感器直接拉线
很多人第一反应是:我拿一颗普通的数字温度传感器,比如DS18B20那种,用长线拉到远端不就行了?理论上可以,但实际会碰到几个硬伤。第一,长线缆会引入寄生电容和电感,数字信号的边沿会变缓,通信误码率飙升。第二,线缆电阻会分压,对于依赖精确电压或电流的模拟传感器来说,误差直接叠加到读数上。第三,远端如果和主板之间存在地电位差,共模干扰会让信号彻底不可用。
PJ85718DM的设计思路是:把敏感模拟部分放在远端探头里,输出一个与温度相关的数字脉冲信号,这个信号的抗干扰能力比模拟电压强得多。它的工作方式类似于很多远端温度传感器的经典架构——通过一个精确的电流源激励感温元件,然后测量其上的电压降,再经过内部ADC转换成数字量。因为电流源和参考电压都在芯片内部,线缆电阻的影响被大幅削弱。具体来说,线缆电阻会和内部电流源形成串联,但芯片测量的是感温元件两端的差分电压,只要线缆电阻不造成电流源饱和,读数基本不受影响。
2.2 线缆选型和长度限制的实测数据
我在实验室里用不同线缆做过对比测试。普通杜邦线在1米以内没问题,超过3米读数开始跳动。换成屏蔽双绞线之后,5米以内非常稳定,10米时偶尔出现单次跳变,加一个简单的RC低通滤波就解决了。如果距离超过20米,建议用带屏蔽层的双绞线,并且在接收端加共模扼流圈。
这里有一个关键参数:PJ85718DM的电流源输出能力。如果线缆太长,线阻太大,电流源可能进入非线性区。我实测的线阻阈值大约在20欧姆左右,对应普通24AWG线缆大约15到20米的长度。超过这个长度,要么加粗线径,要么降低通信速率。通信速率降低意味着每次读取需要更长时间,但对于温度这种慢变量来说,1秒读一次和100毫秒读一次没有本质区别。
注意:远端探头和主板之间的连接线一定要远离高压线或电机驱动线。HVAC系统里风机和压缩机的电源线是巨大的噪声源,平行走线超过10厘米就可能引入干扰。交叉走线时尽量保持90度夹角。
2.3 本地温度通道的两种实现路径对比
本地温度这一路,我试过两种方案。方案A是用STM32F031C6的内部温度传感器,优点是零成本、零外围元件,缺点是精度差、受MCU负载影响大。方案B是用一颗独立的I2C数字温度芯片,比如常见的TMP102或LM75兼容型号,精度可以做到±0.5°C,成本增加不到两块钱。
| 对比项 | STM32内部传感器 | 独立I2C温度芯片 |
|---|---|---|
| 典型精度 | ±1.5°C(实际更差) | ±0.5°C |
| 受MCU发热影响 | 严重 | 很小 |
| 额外引脚 | 无 | 2个(SCL/SDA) |
| 额外成本 | 0 | 约1-2元 |
| 校准难度 | 需要多点校准 | 出厂校准,基本免调 |
| 响应速度 | 快 | 中等(取决于配置) |
如果你的应用对本地温度精度要求不高,比如只是做粗略的过温保护,内部传感器凑合能用。但如果要做精确的温控逻辑,比如根据温度调节风机转速,那独立芯片是必须的。我现在的做法是:内部传感器只作为“MCU是否过热”的粗略判断,真正的环境温度用独立芯片读。
3. STM32F031C6的资源配置与两路温度采集的软件架构
3.1 为什么选F031C6而不是更大的型号
STM32F031C6在STM32家族里属于入门级,Cortex-M0内核,32KB Flash,4KB RAM,TSSOP20封装。有人会问:温度采集这么简单的任务,用得着上STM32吗?用8位机不就行了?我的理由是:第一,STM32F031C6的价格已经和很多8位机持平甚至更低;第二,它的外设资源刚好够用——一个I2C接口接本地温度芯片,一个UART接口输出调试信息,一个定时器做周期采样,还有多余的GPIO做报警输出;第三,开发工具链成熟,用STM32CubeMX生成初始化代码,半天就能跑通。
4KB RAM听起来很少,但对于温度采集来说绰绰有余。两路温度各占2字节,加上滤波缓冲区和通信缓冲区,1KB都用不到。32KB Flash也足够放下采集逻辑、简单的滤波算法和通信协议。如果你需要更复杂的控制算法,比如PID调节,F031C6也能跑,只是别指望它同时处理太多任务。
3.2 两路温度采集的时序安排
软件架构上,我用了一个简单的时间片轮询结构。TIM14配置为1ms中断,在中断里维护一个软件计数器。每100ms触发一次本地温度采集(I2C读取),每500ms触发一次远端温度采集(PJ85718DM读取)。为什么远端慢一些?因为远端信号经过长线缆,读取时间本身较长,而且温度变化慢,没必要频繁读。
// 简化的时间片调度逻辑 volatile uint32_t tick_ms = 0; void TIM14_IRQHandler(void) { if (TIM14->SR & TIM_SR_UIF) { TIM14->SR &= ~TIM_SR_UIF; tick_ms++; } } void main_loop(void) { static uint32_t last_local = 0; static uint32_t last_remote = 0; while (1) { if (tick_ms - last_local >= 100) { last_local = tick_ms; read_local_temp(); } if (tick_ms - last_remote >= 500) { last_remote = tick_ms; read_remote_temp(); } // 其他任务 } }这个结构的好处是简单、可预测,不会因为某个传感器响应慢而阻塞整个系统。如果你用RTOS当然也可以,但对于这个规模的应用,RTOS反而增加了复杂度和RAM开销。
3.3 PJ85718DM的读取时序与STM32的GPIO配置
PJ85718DM的通信协议是单线双向的,类似于很多远端温度传感器的“脉冲计数”方式。MCU先拉低数据线一段时间作为启动信号,然后释放,传感器开始回应一串脉冲,脉冲的数量或宽度对应温度值。具体时序需要参考数据手册,但核心是:GPIO必须配置为开漏输出加外部上拉电阻,上拉电阻典型值4.7kΩ到10kΩ。
我一开始用推挽输出,结果传感器和MCU同时驱动数据线,差点把IO口烧了。后来改成开漏模式,加上4.7kΩ上拉,一切正常。读取的时候,先配置为输出模式拉低,延时约1ms,然后切换为输入模式,用定时器捕获或者简单的循环计数来测量脉冲。因为STM32F031C6没有专门的单线协议外设,我用的是TIM3的输入捕获功能,精度足够。
提示:上拉电阻的阻值会影响信号边沿。阻值太小,功耗增加,传感器可能拉不低;阻值太大,边沿变缓,高速读取时误码率上升。4.7kΩ在3.3V系统里是一个比较稳妥的选择。
4. 从原始读数到可用温度:标定、滤波与误差处理
4.1 为什么出厂校准还不够
任何温度传感器都有误差,PJ85718DM也不例外。数据手册给的精度是±0.5°C(典型值),但这是在特定条件下的。实际系统中,线缆电阻、PCB布局、附近热源都会引入额外误差。我做过一个对比:同一颗PJ85718DM,在25°C恒温槽里读数偏差0.3°C,但装到板子上之后,因为旁边有一个LDO稳压器发热,读数偏高1.2°C。
所以标定是必须的。我的做法是两点标定:用一个已知精度的参考温度计,在低温点(比如冰箱冷藏室,约4°C)和高温点(比如恒温箱,约50°C)各测一次,记录PJ85718DM的原始读数和参考温度,然后计算偏移量和增益修正系数。对于大多数应用,线性修正就够了。如果要求更高,可以做多点分段线性修正。
4.2 滑动平均滤波的参数选择
温度信号本身变化缓慢,但噪声可能让读数跳动。我用的是滑动平均滤波,窗口大小取8。为什么是8?太小了滤波效果不明显,太大了响应迟钝。8个采样点,在500ms采样周期下,相当于4秒的平滑窗口,对于HVAC这种热惯性很大的系统来说完全够用,而且不会掩盖真实的温度变化趋势。
#define FILTER_WINDOW 8 typedef struct { int32_t buffer[FILTER_WINDOW]; uint8_t index; uint8_t count; int32_t sum; } MovingAvg; int32_t moving_avg_update(MovingAvg *ma, int32_t new_val) { ma->sum -= ma->buffer[ma->index]; ma->buffer[ma->index] = new_val; ma->sum += new_val; ma->index = (ma->index + 1) % FILTER_WINDOW; if (ma->count < FILTER_WINDOW) ma->count++; return ma->sum / ma->count; }这个实现用了一个小技巧:维护一个累加和,每次更新只做减法和加法,不需要每次重新遍历数组求和。在M0这种没有硬件除法器的内核上,除法操作比较耗时,但每500ms才做一次,影响可以忽略。
4.3 异常值的识别与剔除
线缆接触不良或者强干扰会导致单次读数严重偏离。如果直接把这个值送进滤波器,会污染整个窗口。我的做法是加一个限幅判断:如果新读数与当前滤波值的偏差超过5°C,就认为这次读数无效,直接丢弃,不更新滤波器。5°C这个阈值是根据实际温度变化速率定的——在HVAC系统里,温度不可能在500ms内变化超过5°C。
还有一种情况是传感器断线。PJ85718DM在断线时通常会输出一个固定的异常值(比如全高或全低)。我在初始化时记录一个“合理范围”,比如-40°C到125°C,超出这个范围的读数直接标记为故障,触发报警输出。
5. 本地与远端温度的协同逻辑:从数据到决策
5.1 双温度差的利用:判断是设备发热还是环境升温
单独看本地温度或远端温度,信息量有限。但把两路温度放在一起,就能做出更有价值的判断。比如:本地温度升高,远端温度不变,说明是设备自身发热增加,可能需要启动本地散热风扇。本地和远端同时升高,说明环境温度在上升,可能需要加大远端制冷量。本地温度正常,远端温度异常升高,说明远端区域有热源或者风管堵塞。
我在代码里定义了一个温差变量:delta = local_temp - remote_temp。正常情况下,设备内部温度会比远端环境温度高5到15°C,取决于设备功耗和散热设计。如果delta突然增大,说明本地散热出了问题;如果delta变成负值,说明远端比本地还热,这在HVAC制冷场景下意味着制冷失效。
5.2 报警阈值的设定与迟滞处理
报警阈值不能设得太死,否则温度在阈值附近波动时会频繁触发报警。我用的是迟滞比较:比如高温报警阈值设为60°C,但报警触发后,要等到温度降到55°C以下才解除报警。这5°C的迟滞区间可以有效防止抖动。
| 参数 | 设定值 | 说明 |
|---|---|---|
| 本地高温报警 | 60°C触发,55°C解除 | 防止MCU过热 |
| 远端高温报警 | 45°C触发,40°C解除 | HVAC制冷失效判断 |
| 温差报警 | delta>25°C触发 | 本地散热异常 |
| 传感器故障 | 读数超范围或断线 | 立即触发 |
这些阈值需要根据具体应用调整。比如工业场景可能允许更高的工作温度,而医疗或食品存储场景要求更严格。关键是迟滞区间要合理,太小了没用,太大了响应迟钝。
5.3 输出控制:继电器、PWM还是通信上报
根据温度判断结果,系统可能需要驱动继电器(开关风机或压缩机)、输出PWM(调节风机转速)或者通过通信接口上报数据。STM32F031C6的GPIO可以直接驱动小功率继电器(加一个三极管和续流二极管),PWM可以用TIM1或TIM3输出。如果只是上报数据,UART是最简单的选择,接一个UART转USB芯片就能在电脑上看数据。
我现在的做法是:本地温度超过55°C时,PWM输出逐步提高风扇转速;超过60°C时,继电器直接接通备用散热器;同时所有温度数据通过UART每秒钟上报一次,方便上位机记录和趋势分析。
6. 实际部署中遇到的坑与排查过程
6.1 远端读数周期性跳变:从电源纹波找到根因
板子刚做回来的时候,远端温度读数每隔几秒就跳变一次,幅度大约2到3°C。一开始怀疑是线缆干扰,换了屏蔽线,加了滤波电容,问题依旧。后来用示波器看电源轨,发现3.3V上有大约50mV的周期性纹波,频率和开关电源的开关频率一致。PJ85718DM的内部电流源对电源纹波比较敏感,纹波通过电流源调制到了测量结果上。
解决办法是在PJ85718DM的电源引脚旁边加一个10μF的钽电容并联一个100nF的陶瓷电容,同时把LDO换成PSRR更高的型号。改完之后纹波降到5mV以下,读数稳定了。这个坑让我记住:远端传感器的精度不仅取决于传感器本身,还取决于给它供电的电源有多干净。
6.2 I2C总线锁死:一个上拉电阻引发的血案
本地温度芯片用的是I2C接口。调试时偶尔出现I2C总线锁死,MCU再也读不到数据。排查后发现是上拉电阻用了10kΩ,而总线电容因为走线较长偏大,导致上升沿太缓,在某些时序条件下从机误判了起始条件。换成4.7kΩ之后问题消失。另外,STM32的I2C外设在总线锁死时可以通过重新初始化来恢复,我在代码里加了一个超时检测:如果连续3次读取失败,就重新初始化I2C外设。
void i2c_recovery(void) { // 关闭I2C外设 I2C1->CR1 &= ~I2C_CR1_PE; // 手动翻转SCL线9次,尝试释放总线 for (int i = 0; i < 9; i++) { gpio_set_scl(0); delay_us(5); gpio_set_scl(1); delay_us(5); } // 重新初始化 MX_I2C1_Init(); }这个恢复逻辑在实际运行中救了好几次现场。虽然I2C协议本身有完善的错误处理机制,但在强干扰环境下,硬件层面的锁死还是可能发生,软件兜底是必要的。
6.3 温度读数整体偏移:PCB布局的热设计问题
前面提到过,LDO发热导致本地温度偏高。后来我把LDO挪到板子另一侧,并且在温度芯片和LDO之间开了几条隔离槽,偏移从1.2°C降到了0.3°C。远端探头倒是没这个问题,因为它离主板远。但远端探头的安装位置也有讲究:如果贴在金属外壳上,外壳的温度会通过探头传导进来,读数会偏向外壳温度而不是空气温度。正确的做法是用导热硅胶把探头固定在空气中,或者用一个小支架让探头悬空。
注意:温度传感器的安装位置往往比传感器本身的精度更重要。一个±0.1°C精度的传感器装错位置,误差可能比一个±1°C的传感器装对位置还要大。
7. 这套方案还能怎么扩展
7.1 增加多路远端测温
PJ85718DM是单通道的,如果需要监测多个远端位置,可以用多颗传感器配合多路模拟开关,或者换用支持多路复用的型号。STM32F031C6的GPIO数量有限,但通过I2C扩展IO芯片可以轻松增加控制线。我在一个项目里用了一颗I2C的8路模拟开关,轮流切换4颗远端传感器,采样周期从500ms延长到2秒,对于大多数HVAC应用来说仍然足够。
7.2 加入无线通信模块
如果远端位置布线困难,可以在远端加一颗低功耗无线模块,把温度数据发回主板。STM32F031C6的UART接口可以直接接常见的无线透传模块。不过要注意功耗问题:如果远端是电池供电,需要让MCU和传感器周期性休眠,醒来采集一次就发出去,然后继续休眠。F031C6的低功耗模式(Stop模式)可以把电流降到微安级别,配合传感器的间歇工作,一颗纽扣电池撑几个月是可行的。
7.3 数据记录与趋势分析
温度数据的价值在于趋势。我在UART输出里加了一个简单的协议,每秒钟发送一次本地温度、远端温度和状态标志。上位机用Python脚本接收并存入CSV文件,然后用任何图表工具画趋势图。如果不想接电脑,也可以在板子上加一颗SPI Flash,每隔一分钟记录一次温度,存满之后通过UART导出。STM32F031C6的32KB Flash里划出8KB做数据存储,可以存几千条记录,对于短期趋势分析足够了。
这套PJ85718DM加STM32F031C6的组合,我从第一次打样到现在已经用了好几个项目,硬件方案基本没变过,软件框架也一直沿用。它的价值不在于技术有多先进,而在于稳定、便宜、容易复制。如果你正在找一套靠谱的双温度监测方案,不妨从这个组合开始试。