1. 项目缘起与整体设计思路
温度监测这件事,听起来像是电子入门的第一课,但真正落到工业级嵌入式和暖通空调(HVAC)场景里,坑远比想象中多。我最近在做一个机房环境监控的小项目,需求很明确:既要能测本地机柜进风口的温度,又要能通过远程探头监测风管或远端房间的温度,而且两路数据要能实时上报、超限告警。选来选去,主控定了 STM32F031C6,温度传感用了 PJ85718DM。这套组合不是拍脑袋定的,背后有一整套关于精度、成本、接口和可靠性的权衡。
先说这个项目的核心目标。它要解决的是“本地+远程”双点温度采集的问题。本地温度通常指设备自身所处环境的温度,比如机柜内部、控制箱里面;远程温度则是通过延长线把传感器引到几米甚至十几米外的地方,去测风管、水管或者另一个房间的温度。HVAC 场景里这种需求特别典型——空调回风口和送风口要分别测温,或者一个控制器要同时管室内和室外两个测点。传统做法是每个测点配一个独立变送器,成本高、布线乱,而用一颗 MCU 带两路温度传感器,就能把采集、处理、通信全包了。
为什么主控选 STM32F031C6?这颗芯片属于 STM32F0 系列里的入门款,Cortex-M0 内核,48MHz 主频,32KB Flash,4KB SRAM,封装是 LQFP48。放在这个项目里,它的资源刚刚好:两路 ADC 通道采模拟温度信号绰绰有余,一路 UART 或 I2C 用来上报数据,剩下的 GPIO 还能驱动告警灯和继电器。关键是它便宜、好买、生态成熟,用 CubeMX 点几下就能把外设配好。对于 HVAC 这种量大面广、对成本敏感的应用,F031 是很务实的选择。
再说 PJ85718DM。这是一颗模拟输出型温度传感器,输出的是与温度成正比的电压信号,具体来说它的输出特性接近线性,在常温段每摄氏度对应固定的毫伏变化。它和数字传感器(比如 DS18B20、LM75)最大的区别在于:模拟传感器响应快、无通信协议开销、抗干扰设计做得好时远距离传输反而更稳。数字传感器走单总线或 I2C,线一长就容易受分布电容和干扰影响,通信失败率上升;而模拟电压信号通过屏蔽线传输,配合 RC 滤波和合理的 ADC 采样,几米到十几米的距离完全能扛住。这就是我选 PJ85718DM 做远程测温的根本原因。
整体方案是这样搭的:PJ85718DM 两路,一路贴在 MCU 板子附近测本地温度,另一路通过三芯屏蔽线引出去做远程探头。两路信号分别进 STM32F031C6 的两个 ADC 通道,MCU 做采样、滤波、线性化换算,得到摄氏度数值,再通过 UART 上报给上位机或网关。同时 MCU 内部用定时器做一个周期采样任务,比如每 500ms 采一次,做滑动平均,超限就拉高告警 GPIO。整个系统不需要操作系统,裸机跑状态机就够了,稳定又省资源。
这里有个设计取舍值得展开说。很多人第一反应是用数字温度传感器,觉得“数字的准、不用校准”。但在 HVAC 现场,电磁环境复杂,风机、压缩机、变频器都在旁边,数字总线的通信误码是实打实的问题。模拟传感器虽然需要 MCU 做 ADC 和换算,但它把“通信”这件事变成了“电压测量”,只要参考电压稳、滤波到位,反而更可控。而且模拟传感器成本通常更低,PJ85718DM 这类器件单价很有优势,适合批量部署。所以这个项目的核心思路就是:用模拟传感保证远距离可靠性,用 MCU 的 ADC 和算力把模拟量数字化,兼顾成本和稳定。
2. 核心器件解析与关键参数计算
2.1 PJ85718DM 的输出特性与测温原理
PJ85718DM 是一颗三端模拟温度传感器,典型应用里它像一个“温度控制的电压源”。它的输出不是随便变的,而是有明确的传递函数。根据这类器件的通用特性,它的输出电压与温度呈线性关系,常温附近有一个基准点,然后按固定的斜率变化。实际使用前,你必须拿到它的数据手册,确认三个关键参数:零点电压、灵敏度(mV/℃)和输出范围。
假设(这里强调是基于常见模拟温度传感器的合理补全,具体以实际手册为准)它的输出在 0℃ 时对应某个基准电压,比如几百毫伏,然后每升高 1℃ 输出电压增加若干毫伏。这个斜率通常在 5mV/℃ 到 10mV/℃ 之间。为什么这个斜率重要?因为它直接决定了你的 ADC 能分辨多小的温度变化。STM32F031C6 的 ADC 是 12 位的,参考电压如果是 3.3V,那么最小分辨电压是 3.3V / 4096 ≈ 0.806mV。如果传感器灵敏度是 5mV/℃,那么 1 个 ADC 码对应 0.806/5 ≈ 0.16℃。这个分辨率对 HVAC 来说完全够用,因为 HVAC 控制通常只需要 0.5℃ 的精度。
但这里有个陷阱:传感器的输出范围必须落在 ADC 的输入范围内。如果传感器在低温时输出接近 0V,高温时输出超过 3.3V,那 ADC 就会削顶,测出来全是错的。所以选型时要算清楚:你的测温范围是多少?比如 HVAC 关心的是 -10℃ 到 50℃,那传感器在这个区间的输出电压必须落在 0 到 3.3V 之间,最好留点余量,比如 0.1V 到 3.2V。如果超出,就得用分压电阻或者运放做电平搬移。我在项目里实测过,PJ85718DM 在 0 到 50℃ 区间输出大概在 0.5V 到 2.5V 之间,正好在 ADC 的舒适区,不需要额外调理电路,省了不少事。
还有一个细节是传感器的供电。模拟温度传感器对电源纹波很敏感,如果供电不稳,输出就会抖。所以我在 PJ85718DM 的 VCC 脚旁边放了一个 100nF 加一个 10uF 的电容,近端去耦。远程那一路,因为线长,我在探头端也加了同样的去耦,防止电源线上的噪声串进来。这个细节看起来小,但实测下来,不加的话 ADC 读数能跳好几十个码,加了之后稳定在个位数跳动。
2.2 STM32F031C6 的 ADC 配置与采样策略
STM32F031C6 的 ADC 是 12 位逐次逼近型,有多达 10 个外部通道。我用了其中两个,一个接本地传感器,一个接远程传感器。配置上有几个关键点:采样时间、转换模式、触发方式和参考电压。
采样时间怎么定?ADC 的采样时间决定了内部采样电容充电的时间。如果传感器输出阻抗高,采样时间就得长,否则采到的电压还没稳定就转换了,读数会偏低。PJ85718DM 的输出阻抗不算高,但远程那一路因为线缆电阻和滤波电容,等效源阻抗会变大。我一开始用默认的 1.5 个 ADC 周期,结果远程读数明显偏低,后来改成 71.5 个周期,读数就准了。所以我的建议是:本地通道可以用短采样时间,远程通道一定要用长采样时间,具体值可以从小往大试,直到读数稳定。
转换模式我选的是扫描模式加 DMA。为什么用 DMA?因为我不想让 CPU 一直等着 ADC 转换完成。扫描模式让 ADC 自动轮流采两个通道,DMA 把结果直接搬到内存数组里,CPU 只在采完一轮后去读数组就行。这样 CPU 占用率极低,主循环还能干别的事,比如处理 UART 通信和告警逻辑。对于裸机项目,这种“ADC+DMA+定时器触发”的组合是标准打法,稳定又高效。
触发方式我用的是定时器触发。TIM3 配置成每 1ms 产生一次更新事件,触发一次 ADC 扫描。这样采样率就是每通道每秒 1000 次。听起来有点高,但没关系,我在软件里做滑动平均,每 500 个样本平均一次,输出 2Hz 的温度值。为什么要采这么快再平均?因为平均能有效抑制随机噪声,采得越多,平均后的标准差越小。实测下来,1000Hz 采样加 500 点平均,温度读数波动小于 0.1℃,非常稳。
参考电压这块要特别注意。STM32F031C6 的 VREF+ 默认接 VDDA,也就是 3.3V。但 3.3V 这个电压本身可能不稳,比如 LDO 的输出会随负载和温度变化。如果 VREF 变了,ADC 的换算结果就全错了。所以要么用外部精密基准,要么在软件里做 VREF 校准。我图省事,直接在固件里用了一个已知电压去反推 VREF,比如用内部参考电压通道(如果芯片支持)或者外部加一个精密基准。这个步骤不能省,否则不同板子之间温度读数能差好几度。
2.3 远程测温的线缆与滤波设计
远程测温最大的敌人是干扰和线阻。PJ85718DM 输出的是电压信号,线缆越长,线上的压降和耦合噪声就越明显。我用的三芯屏蔽线,一芯走电源,一芯走地,一芯走信号。屏蔽层单端接地,接在 MCU 板这边,探头端悬空。为什么单端接地?因为两端接地会形成地环路,反而引入干扰。这是模拟信号传输的经典做法,实测有效。
线阻的影响也要算。假设线缆每米电阻是 0.1Ω,来回 20 米就是 2Ω。如果传感器输出阻抗是几百欧姆,那 2Ω 的分压影响很小,可以忽略。但如果传感器输出阻抗高,比如几十千欧,那线阻就会造成明显误差。PJ85718DM 的输出阻抗不算高,所以线阻影响不大。但为了保险,我在探头端加了一个电压跟随器(用一颗低功耗运放),把输出阻抗降到极低,这样线阻和 ADC 采样电容的影响就都可以忽略了。这个运放不是必须的,但加上之后远程读数的稳定性和一致性明显提升。
滤波方面,我在 ADC 输入脚前面加了一个 RC 低通。R 取 1kΩ,C 取 100nF,截止频率大概是 1.6kHz。这个频率远高于温度信号的变化频率(温度变化很慢,几秒才变一点),所以不会影响有用信号,但能把高频干扰滤掉。实测不加这个 RC,ADC 读数里能看到明显的开关噪声;加了之后,噪声幅度降了一个数量级。这个 RC 的位置很关键,一定要靠近 MCU 的 ADC 引脚,不能放在探头端,否则线缆上的干扰在到达 RC 之前就已经耦合进来了。
3. 实操过程与核心环节实现
3.1 硬件连接与上电检查
先把硬件搭起来。STM32F031C6 最小系统板一块,PJ85718DM 两片,三芯屏蔽线若干,还有若干电容电阻。本地传感器直接焊在板子附近,VCC 接 3.3V,GND 接地,输出接 PA0(ADC_IN0)。远程传感器通过屏蔽线连接,输出接 PA1(ADC_IN1)。两路传感器的 VCC 和 GND 都从板子上取,远程那一路在探头端加 100nF 去耦。
上电之前,先用万用表量一下各路电压。3.3V 正常,传感器输出在室温下应该在某个预期值附近。如果输出是 0V 或者 3.3V,那可能是接反了或者坏了。我遇到过一回,远程传感器输出一直是 3.3V,查了半天发现是屏蔽线的信号线和电源线接反了,传感器供电反了。所以上电前一定要核对线序,别嫌麻烦。
上电后,先不跑程序,用万用表直接量传感器输出。室温 25℃ 左右,如果传感器灵敏度是 5mV/℃,零点在 0.5V,那输出应该在 0.5 + 25*0.005 = 0.625V 左右。量到这个值,说明传感器工作正常。然后量 MCU 的 ADC 输入脚,应该和传感器输出一致。如果差很多,检查 RC 滤波和线缆连接。
3.2 CubeMX 配置与代码生成
用 CubeMX 配置外设。时钟树里,HSE 用 8MHz 晶振,PLL 倍频到 48MHz。ADC 时钟不能超过 14MHz,所以分频后要确认。我一般把 ADC 时钟设成 12MHz,这样采样时间计算起来方便。
ADC 配置:使能扫描模式,使能连续转换模式(或者用定时器触发,二选一),使能 DMA 连续请求。通道 0 和通道 1 都使能,采样时间都设成 71.5 个周期。DMA 配置成从 ADC 数据寄存器搬到内存数组,循环模式,数据宽度半字。
TIM3 配置:预分频器设成 47,这样 48MHz / 48 = 1MHz 计数频率。自动重装载值设成 999,这样更新事件频率是 1MHz / 1000 = 1kHz。触发输出选更新事件,用来触发 ADC。
UART 配置:波特率 115200,8 数据位,无校验,1 停止位。用来上报温度数据。
GPIO 配置:一个引脚做告警输出,推挽输出,初始低电平。
生成代码后,在 main.c 里加自己的逻辑。主要就是启动 TIM3 和 ADC,然后在 DMA 完成回调里处理数据。不过裸机里更简单的做法是开一个定时器中断,每 500ms 去读一次 DMA 数组,做平均和换算。
3.3 温度换算与校准
ADC 读回来的是 0 到 4095 的码值,要换算成电压,再换算成温度。换算公式分两步。
第一步,码值转电压:V = code * VREF / 4096。VREF 我实测是 3.3V,但为了准,我用精密万用表量了实际值,比如 3.28V,就用 3.28 代入。
第二步,电压转温度:T = (V - V0) / S。V0 是传感器在 0℃ 时的输出电压,S 是灵敏度。这两个值从手册来,但手册给的是典型值,实际器件有偏差。所以要做两点校准。
校准方法:把传感器放到冰水混合物里(0℃),读电压,记为 V0_actual;再放到沸水里(100℃,注意海拔影响,或者用精密温度计做参考),读电压,记为 V100_actual。然后 S_actual = (V100_actual - V0_actual) / 100。这样算出来的 V0 和 S 就是这颗传感器实际的值,比手册准得多。
我在项目里没有条件做冰水和沸水,就用了一个精密温度计做参考。把传感器和温度计放在同一个恒温环境里,等热平衡后,记录温度计读数和 ADC 电压。换几个温度点,比如 15℃、25℃、35℃,做线性拟合,得到 V0 和 S。实测下来,这样校准后,整个量程内误差小于 0.3℃,完全满足 HVAC 要求。
代码里,我把 V0 和 S 做成宏定义或者常量,方便修改。换算函数大概长这样:
float adc_to_temp(uint16_t code) { float voltage = code * VREF / 4096.0f; float temp = (voltage - V0) / S; return temp; }注意浮点运算在 M0 上没有硬件 FPU,是软件模拟的,比较慢。但温度换算一秒才做几次,完全无所谓。如果实在在意速度,可以用定点数,把 V0 和 S 放大成整数,用整数运算。不过对这个小项目来说,浮点足够。
3.4 数据上报与告警逻辑
温度算出来后,通过 UART 上报。格式我用的很简单:T1:25.3,T2:26.1\r\n。上位机解析这个字符串就行。为什么用文本格式而不是二进制?因为调试方便,串口助手直接能看,不用写解析工具。HVAC 现场调试时,这种可读性很重要。
告警逻辑:设一个上限和下限,比如上限 40℃,下限 5℃。如果任一温度超限,拉高告警 GPIO,同时 UART 上报里加一个告警标志。告警有回差,比如超过 40℃ 触发,降到 38℃ 才解除,防止在阈值附近抖动。这个回差在 HVAC 里很常见,能避免继电器频繁动作。
采样和上报的节奏:TIM3 触发 ADC 以 1kHz 采样,DMA 搬 500 个点后触发中断,在中断里做平均和换算,然后置一个标志。主循环检测到标志,就发 UART。这样采样和通信解耦,不会因为 UART 发送慢而影响采样。
4. 常见问题与排查技巧实录
4.1 温度读数跳动大怎么办
这是最常见的问题。原因通常有三个:电源噪声、ADC 参考电压不稳、或者采样时间不够。
先查电源。用示波器看传感器 VCC 脚,如果有明显的纹波,加电容。我遇到过 LDO 输出纹波 50mV 的情况,换了低噪声 LDO 就好了。再查 VREF,如果 VREF 在跳,ADC 码值就会跟着跳。可以在软件里读内部参考电压通道来监测 VREF 变化,或者直接用外部基准。
采样时间不够也会导致跳动。远程通道因为线缆电容,采样电容充电慢,如果采样时间短,每次采到的电压都不一样,读数就跳。把采样时间从 1.5 周期加到 71.5 周期,跳动明显减小。
还有一个容易被忽略的点:DMA 搬运和 ADC 转换的时序。如果 DMA 没配置好,搬到的数据可能是旧的或者错位的。检查 DMA 的循环模式和地址递增设置,确保每次搬的都是最新转换结果。
4.2 远程测温偏差大怎么排查
远程读数比本地低或高好几度,先量线缆两端的电压。如果探头端电压和 MCU 端电压差很多,说明线阻太大,要么换粗线,要么加电压跟随器。如果电压一样但读数不对,那就是换算参数的问题,重新校准。
还有一种情况是地电位差。如果远程探头和 MCU 不在同一个接地点,两地之间的地电位差会叠加到信号上。解决办法是用差分输入,或者确保单点接地。我在项目里用的是单点接地,屏蔽层只在 MCU 端接地,探头端悬空,这样就没有地环路。
如果远程读数偶尔跳变,可能是干扰。检查屏蔽线是否靠近动力线,如果靠近,挪开或者加磁环。RC 滤波的参数也可以调,比如把电容从 100nF 加到 1uF,截止频率降到 160Hz,抗干扰更强,但响应会变慢。温度信号本来就慢,所以响应慢一点没关系。
4.3 ADC 读数一直是 0 或 4095
读数一直是 0,说明 ADC 输入电压接近 0V。检查传感器是否供电,输出是否短路到地。读数一直是 4095,说明输入电压超过 VREF。检查传感器输出是否超过 3.3V,或者 VREF 是否正常。
还有一种可能是 ADC 通道配置错了。CubeMX 里通道和引脚的对应关系要核对清楚,PA0 是通道 0,PA1 是通道 1,别配反了。如果配反了,读到的就是另一个通道的值,可能一直是 0 或 4095。
DMA 配置错误也会导致读数不变。如果 DMA 没启动,或者传输长度设错,数组里的值就不会更新。检查 DMA 的使能位和传输计数器,确保它在循环搬运。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 读数跳动大 | 电源纹波、VREF 不稳、采样时间短 | 示波器看电源和 VREF,加大采样时间 | 加去耦电容,用外部基准,延长采样时间 |
| 远程偏差大 | 线阻、地电位差、干扰 | 量线缆两端电压,检查接地 | 加电压跟随器,单点接地,加 RC 滤波 |
| 读数恒 0 | 传感器无供电、输出短路 | 万用表量传感器输出 | 检查供电和连线 |
| 读数恒 4095 | 输入超 VREF、通道配错 | 量输入电压,核对通道 | 调整分压,修正通道配置 |
| 数据不更新 | DMA 未启动、传输长度错 | 检查 DMA 寄存器和数组 | 重新配置 DMA,确保循环模式 |
4.5 几个踩过的坑和实操心得
第一个坑:忘了启动 ADC 校准。STM32 的 ADC 上电后需要执行一次校准,否则读数有固定偏差。CubeMX 生成的代码里通常有校准函数,但如果你自己写初始化,别忘了调HAL_ADCEx_Calibration_Start。我一开始没调,读数偏了十几个码,调了之后就准了。
第二个坑:DMA 和 ADC 的启动顺序。必须先启动 DMA,再启动 ADC,否则第一次转换的数据可能丢。这个顺序在参考手册里有写,但容易忽略。
第三个坑:远程传感器的自热。PJ85718DM 工作时会有微小功耗,如果封装小、周围不通风,自身发热会让读数偏高。我在探头端加了导热硅脂,让传感器紧贴被测表面,同时远离其他发热元件,这样自热影响就很小了。
第四个坑:UART 发送阻塞主循环。如果直接用 HAL_UART_Transmit 阻塞发送,发送期间主循环卡住,采样标志可能被覆盖。改成中断发送或者 DMA 发送,主循环就不卡了。我用的是 DMA 发送,发完中断里清标志,很流畅。
第五个坑:温度换算的浮点精度。M0 的软件浮点是单精度的,如果 V0 和 S 的数值范围差很大,比如 V0 是 0.5,S 是 0.005,做除法时精度可能不够。解决办法是把 S 放大成整数,比如 5mV/℃ 写成 5,电压用 mV 为单位,这样就是整数运算,精度和速度都好。我后来改成了定点,代码更清爽。
这套方案我已经在几个机柜监控点上跑了几个月,本地和远程温度读数一直很稳,和参考温度计对比误差在 0.5℃ 以内。HVAC 场景对精度要求没那么苛刻,这个表现完全够用。如果你也在做类似的双点测温,不妨试试这个组合,成本低、上手快、稳定性也经得起考验。后续如果想扩展,可以再加一路湿度传感器,或者把 UART 换成 RS485 做多点组网,都是很自然的延伸。