做嵌入式温度测量这些年,NTC、PT100、I2C 传感器、单总线传感器我都摸过一遍。最近在一个 HVAC 风管温度监测项目里,我把方案收敛成了 STM32L031C6 做主控、配 PJ85718DM 做远程温度采集的组合。这套组合既能读控制器主板上的本地温度,也能在一条总线上挂多颗远离主板的测温点,实际工程里拉到几十米线长依然稳。嵌入式设备想做多点温度监测,或者暖通空调系统想采集回风、送风、室外温度,都可以直接参考这套设计。器件选型、硬件接线、驱动代码、低功耗策略、实测数据和踩坑记录,我按项目推进的顺序完整写出来。
1. 整体方案与器件选型思路
1.1 为什么不是 NTC 或 I2C 传感器
很多人一上来就选 NTC 热敏电阻加 ADC,成本确实低,但量产一致性要人命。NTC 的 B 值离散、长线缆引入压降和干扰、每台设备都要做两点校准,工程时间全耗在标定上。早期我在某个温控器项目里用 NTC 测风管温度,同一批料换到另一批后读数差了 2℃ 多,后来被迫在生产线上加校准工位,代价比换传感器高得多。
I2C 数字传感器精度好,可 I2C 总线不适合长距离传输。标准 I2C 在 1 米以内的板级通信没问题,一旦把传感器放到风管深处或者室外,线缆超过两三米,总线电容一大,通信就开始报错。HVAC 和嵌入式设备里,远程测温点往往离主控板有 5 到 30 米,I2C 根本扛不住。
PJ85718DM 这类单总线数字传感器正好补上这个空档。单总线只需要一根数据线,地线共用,线缆成本低,而且一颗芯片上有一个全球唯一的 64 位序列号,一条总线上挂多少颗都不会地址冲突。我选它还有一个原因:寄存器格式和市面上常见的单总线传感器兼容,代码写得快,将来缺货换料也容易。虽然这颗型号不算大路货,但驱动逻辑完全通用,项目跑起来没有绑定风险。
1.2 STM32L031C6 在这套方案里的定位
STM32L031C6 是 Cortex-M0+ 内核的低功耗 MCU,主频最高 32MHz,Flash 32KB,SRAM 8KB,工作电压 1.8V 到 3.6V。选它不是因为性能多强,而是这套测温系统的瓶颈压根不在算力。温度采集是典型的低频小数据量任务,真正要考虑的是待机功耗、外设数量和封装体积。
它在低功耗模式下表现很突出,Stop 模式配合 RTC 唤醒可以把平均电流压到微安级。这对 HVAC 温控器、电池供电的传感器节点特别重要。设备大部分时间在等下一个采集周期,只有唤醒瞬间工作几毫秒。Cortex-M0+ 没有复杂的中断嵌套和总线矩阵,代码逻辑简单直接,写单总线时序不容易被莫名奇妙的外设干扰。
另外,这颗芯片的 ADC 自带 12 位分辨率,用来读本地 NTC 或内部温度传感器足够用。I2C、SPI、USART 都有,后续想加 OLED 显示、RS485 通讯或者无线模块,引脚和外设都不用重新规划。对于一套以温度采集为核心的嵌入式系统,它属于性能有富余、功耗够低、外设够用的稳妥选择。
1.3 系统架构一句话版
整体架构可以概括成:STM32L031C6 作为中心节点,负责本地温度测量、远端单总线轮询、数据处理和对外输出;本地温度由板载 NTC 和 MCU 内部温度传感器共同完成,远程温度由挂在单总线上的多颗 PJ85718DM 完成。
单总线拓扑上,每颗 PJ85718DM 并联在同一条数据线上,通过 64 位序列号区分。主控按顺序发送匹配序列号命令,逐一读取每颗传感器的温度寄存器。数据在 MCU 内部做滤波、校准和单位换算后,通过 UART 或 RS485 发给上层控制器,也可以直接驱动本地显示屏。
这套架构的好处是清晰。本地和远程两条测温路径完全独立:本地 NTC 坏了不影响远程单总线,单总线某颗传感器掉线也不会拖垮主控。工程上故障隔离做得越好,现场维护越省事。我后来给这个系统加了自检逻辑,每次轮询完自动标记掉线节点,调试时一眼就能看出哪一段线缆出了问题。
2. 硬件连接与工程细节
2.1 本地温度采样的两种做法
本地温度我用的是双保险方案。第一路是板载 NTC,10kΩ 电阻、B 值 3950,放在主控板发热源附近,通过分压电路接入 ADC。分压电阻也选 10kΩ,供电 3.3V,这样室温 25℃ 时 NTC 阻值 10kΩ,分压点正好是 1.65V,处于 ADC 输入范围的中间,线性度最好。ADC 用 12 位模式,参考电压用内部 VREF 或稳定的 3.3V,采样时间设长一点,比如 39.5 个 ADC 时钟周期,避免信号源阻抗太高导致采样电容充不满。
第二路是 MCU 内部温度传感器。它只用来监测芯片自身发热,精度一般,但可以判断板级散热是否异常。内部传感器的输出电压和温度近似线性,读取一次 ADC 后用公式换算。工程上我不拿它当真实温度用,只做保护逻辑:比如芯片温度超过 85℃ 就强制降低对外输出功率,或者触发报警。
这里有个容易犯的错:NTC 分压电路的 ADC 参考电压必须和传感器供电同源。如果 NTC 用 3.3V 供电,ADC 参考却用内部 2.5V,换算出来的阻值会整体偏移。我第一版原理图就吃过这个亏,后来统一让 NTC 分压直接接在 VDDA 上,并用 VDDA 做 ADC 参考,才把读数稳定住。
2.2 远端传感器总线拓扑与上拉电阻
远程测温部分,所有 PJ85718DM 并联在同一条单总线上。单总线的空闲状态是高电平,主机通过拉低数据线产生时序窗口,传感器通过控制拉低时间来回复数据。因为数据线是开漏结构,必须有一颗上拉电阻,阻值选 4.7kΩ 还是 1kΩ,取决于总线长度和挂载数量。
短距离板级测试用 4.7kΩ 没问题,但一旦线缆超过 10 米,总线分布电容变大,4.7kΩ 上拉会让上升沿变缓,时序容易超时。我实测下来,20 米四芯屏蔽线挂 6 颗传感器,换成 1kΩ 上拉后波形明显更干净。如果距离再长,还可以在远端额外并联一颗上拉,但要保证总线上拉等效阻值不低于 500Ω,不然传感器拉低能力不够,通信反而失败。
供电方面,PJ85718DM 支持寄生供电,也就是从数据线上窃取能量工作。我做样板时想过省两根线,但寄生供电对时序要求严格,长线、低速、上拉电阻偏大时电压跌落明显,温度读数偶尔会出乱码。后来老老实实走了三线制:VCC、GND、DQ,传感器外接一个 100nF 去耦电容。多花的线缆钱,换来了现场少跑几趟的省心。
2.3 供电与 ESD 防护要点
HVAC 现场环境比实验室恶劣得多。风管附近有风机启停,电机感应出来的浪涌会顺着线缆打进数据口。如果传感器线缆从配电箱旁边走,感应的尖峰更夸张。所以我在单总线接口上加了三项防护:一个 100nF 对地电容滤高频、一个单向 TVS 管钳位浪涌、数据线串联一个 220Ω 电阻限制注入电流。TVS 管选 5V 或 6V 的,D3 封装就行,成本不高但能救回一整批板子。
主控板和传感器节点之间,远端 PJ85718DM 的电源我要求必须在传感器端就地滤波,不能只靠主控板上的电容。每颗传感器附近放一个 100nF 陶瓷电容,必要时再并一个 10μF 电解电容。原因是线缆本身也是天线,从空间耦合进来的噪声如果不就地滤掉,会直接影响传感器内部模拟电路的稳定性。
另外一个细节:所有传感器的地线必须在主控板单点汇接,不能形成地环路。多颗传感器如果分别从不同位置接地,地线之间的电位差会造成读数抖动。我在现场遇到过远端和本地温度差 0.5℃ 的怪问题,排查到最后是地线接成了环路。把传感器地线统一汇总后再接到主控板地,问题立刻消失。
3. 软件驱动与数据链路
3.1 单总线时序怎么才稳定
单总线时序说白了就是精确的拉低和释放操作,所有时序都靠延时控制。PJ85718DM 的时序和标准单总线器件一致,复位脉冲、写时序、读时序三种基本操作。写时序里,写 0 是拉低总线保持 60μs,写 1 是拉低 1μs 后释放,然后在 60μs 内让总线回到高电平。读时序是主机拉低 1μs 后释放,然后在 15μs 内采样电平,传感器输出 0 会把总线拉低 15μs 左右,输出 1 则保持高电平。
刚开始做驱动时,我习惯把延时封成 delay_us 函数直接调,后来发现时钟配置一变,时序就飘。正确做法是初始化的时候直接用定时器测量一个空循环的耗时,动态校准延时参数。这样即使换了主频或者换了编译器优化等级,时序因子也能自动调整,省去反复改参数的痛苦。
static void delay_us(uint32_t us) { uint32_t count = us * (SystemCoreClock / 1000000); while (count--) { __NOP(); } } static uint8_t ow_reset(void) { OW_OUT_LOW(); delay_us(480); OW_INPUT(); delay_us(70); uint8_t presence = OW_READ() ? 0 : 1; delay_us(410); return presence; }采样点很重要。读时隙总线释放后不能马上去读,要等 5μs 到 10μs,等远端传感器的输出稳定下来。过早采样会读到自己的电平残留,过晚采样会读到下一个时序的起始拉低。我调试时用逻辑分析仪抓波形,最后确定在释放后 9μs 左右采样最稳。
3.2 驱动层函数划分
驱动代码我不喜欢塞成一个文件。按功能拆成三层:最底层是 GPIO 操作和延时原语,单独放;第二层是单总线协议,包括复位、写字节、读字节、搜索序列号;第三层才是 PJ85718DM 的业务逻辑,比如读温度、配置分辨率、告警阈值。
这样分层最大的好处是换主控时只改底层。之前我把整套代码从某个 M0+ MCU 移植到另一个型号,只重写了 GPIO 原语和时钟初始化,中间两层完全没动,两天就调通了。如果一上来就把 GPIO 操作混在温度读取函数里,拆起来会想哭。
读温度的核心流程是:复位总线、发送跳过 ROM 命令或匹配 ROM 命令、发送转换命令、等待转换完成、复位总线、再发读暂存器命令、连续读出温度寄存器的低位和高位。每颗传感器读完一个完整温度值,主控再切到下一颗。
int16_t pj85718_read_temp(uint8_t* rom) { ow_reset(); if (rom == NULL) { ow_write_byte(0xCC); } else { ow_write_byte(0x55); for (int i = 0; i < 8; i++) { ow_write_byte(rom[i]); } } ow_write_byte(0x44); while (ow_read_bit() == 0); ow_reset(); ow_write_byte(0xCC); ow_write_byte(0xBE); uint8_t lsb = ow_read_byte(); uint8_t msb = ow_read_byte(); return (int16_t)((msb << 8) | lsb); }上面这段是典型的转换加读取流程。注意转换命令返回的是忙标志位:传感器在转换期间拉低总线,转换完成后释放。主机读这个位可以判断是否完成,比傻等固定时间高效,尤其在低功耗场景里,转换一完成就立刻进入休眠。
3.3 温度换算与滤波
PJ85718DM 的 16 位温度寄存器,最高位到第 12 位是有效温度数据,低 4 位是小数部分,分辨率 0.0625℃。换算很简单:有符号整数除以 16,就是实际的摄氏度值。用整数运算代替浮点可以明显减少 Cortex-M0+ 的负担,M0+ 没有硬件浮点,满是浮点运算会拖慢轮询节奏。
float raw_to_celsius(int16_t raw) { return raw / 16.0f; } int32_t raw_to_deci_celsius(int16_t raw) { return ((int32_t)raw * 625) / 100; }第二种写法是直接换算成 0.1℃ 精度的整数,比如 25.6℃ 就是 256。整数传给液晶显示或通过串口上报,省掉浮点格式化,逻辑也更清爽。
滤波方面,HVAC 风管里的温度变化本来就慢,单次读数的噪声往往来自线缆感应和传感器自身量化误差。我用的是一阶滑动平均,窗口取 8 次。每 1 秒读一次,窗口 8 秒,既能把瞬时干扰平滑掉,又不会让迟滞大到影响控温响应。窗口太大在实测中的问题是,空调开机后风管温度快速上升,滤波后的数值会明显滞后真实温度,控制算法跟着迟钝,所以要控制窗口长度。
4. 低功耗与长稳运行
4.1 让 MCU 在两次采集之间真正睡过去
STM32L031C6 的功耗表现很吃配置。如果只是让 CPU 空转等待,active 模式下电流轻松上到 3mA,电池供电撑不了几天。正确姿势是测完一轮温度后进入 Stop 模式,只保留 RTC 和必要的唤醒源,让系统电流降到 2μA 以下。
进入 Stop 之前要做几件事:关闭 ADC、关闭单总线接口的外部上拉或者把它切成高阻态、关掉没用到的外设时钟、把 GPIO 全部设为固定电平避免浮空输入漏电。很多人忽略最后一步,GPIO 配置成 floating input 后,引脚电压在半高半低状态,内部输入缓冲器会一直耗电,几颗引脚的漏电流加起来比 RTC 还大。
唤醒用 RTC 闹钟,设定到下一个采集周期。我在两版固件里分别试了 1 秒周期和 10 秒周期。1 秒周期平均电流不到 20μA,10 秒周期可以压到 5μA 左右。HVAC 温控器只需要每分钟更新一次温度显示,每 10 秒采样绰绰有余。
4.2 唤醒源与测量周期设计
单总线设备有个特点:温度转换期间传感器自己也在耗电。如果主控和传感器同时工作,峰值电流会叠加。我在设计测量周期时,让主控唤醒后先发转换命令,然后立刻回去睡,等转换窗口结束再用 RTC 二次唤醒读结果。这样把 750ms 的转换时间用在休眠里,平均电流更低。
PJ85718DM 在不同分辨率下转换时间不一样。9 位分辨率大约 94ms,12 位分辨率大约 750ms。HVAC 监测不需要 12 位,我用 11 位分辨率,转换时间 375ms,精度 0.125℃ 足够。对风管温度来说,0.125℃ 的分辨率是肉眼感知不到的变化,但换来了更短的窗口和更低的功耗。
整个测量周期的代码逻辑是:唤醒、拉低总线发转换命令、进入 Stop、RTC 定时唤醒、拉低总线读寄存器、换算滤波、更新显示或上报、再次进入 Stop。一轮下来 MCU 真正 active 的时间只有几毫秒,传感器工作几百毫秒。这个设计的收益很直接:用一个纽扣电池跑半年没问题。
4.3 长线运行和死锁恢复
单总线是半双工协议,最怕主机发到一半,传感器那边忽然把总线锁死。比如线缆短路、远端芯片被干扰后误进入异常状态,总线可能被外部设备强制拉低。这时候主机发任何时序都无效,系统像死了一样。
我在驱动里加了一个看门狗式的恢复机制:每次操作总线之前做一次超时检测,如果在指定时间内读不到预期的电平变化,就对数据线做一次 480μs 的复位脉冲,强制总线上的所有传感器复位。如果连续几次复位仍失败,就标记该通道故障,跳过本轮采集,让系统继续跑其他任务。
看门狗本身也要配置。STM32L031C6 的内部独立看门狗用 LSI 时钟,超时间隔设成 2 秒,哪怕 main loop 卡死在单总线通信里,也能把整个系统拽回来。实际现场遇到过风机启动瞬间把总线打死的案例,就是因为有这层保护,设备没有死机,重启后自动恢复,售后电话少了好几个。
5. 实测数据与误差校准
5.1 测试方法与环境
样机出来后,我在实验室和现场分别做了两轮验证。实验室用恒温槽和一块精度 0.1℃ 的参考温度计,把本地 NTC、内部传感器、远程 PJ85718DM 放在同一温度场里,从 0℃ 到 70℃ 每 5℃ 记录一组数据。现场则选了一个带风管的空调箱,在回风口、送风口、表冷器后、室外四个位置各装一颗传感器,连续跑 48 小时。
测试时要特别注意传感器探头的导热安装方式。PJ85718DM 这类芯片本身测的是封装温度,封装和待测介质之间的热阻全靠现场处理。如果只是把传感器悬在风管里,测到的更多是空气流动的瞬时温度,波动大得没法看。正确做法是给传感器探头加导热硅脂,并让它和风管壁或散热片紧密接触,让热传导路径尽量短。
实验室数据采集用串口每 1 秒打印一次,加上时间戳,连续记录 2 小时。恒温槽稳定后,数据波动范围在 ±0.2℃ 以内,符合预期。现场数据波动稍大,主要是气流扰动带来的真实温度变化,不是传感器不准。
5.2 数据表格与误差分析
我整理了一组典型数据,在 25℃ 恒温槽里,同一总线上挂了 6 颗 PJ85718DM,连续读取 100 次,统计每颗传感器的均值、最大值、最小值和标准差。
| 节点 | 平均值(℃) | 最大偏差(℃) | 标准差(℃) |
|---|---|---|---|
| 节点 1 | 25.01 | +0.18 | 0.03 |
| 节点 2 | 24.98 | -0.22 | 0.04 |
| 节点 3 | 25.03 | +0.15 | 0.03 |
| 节点 4 | 25.00 | +0.20 | 0.05 |
| 节点 5 | 24.97 | -0.25 | 0.04 |
| 节点 6 | 25.02 | +0.16 | 0.03 |
从数据看,单颗传感器内部重复性很好,标准差都在 0.05℃ 以内,偏差主要来自传感器出厂精度和安装热阻差异。工程上用这类传感器做 HVAC 控制,±0.5℃ 的精度完全满足需求。如果客户要求更高精度,就要在产线上做单点校准,把每颗传感器的偏移值写进 Flash。
NTC 本地测温在恒温槽里的表现也能做到 ±0.3℃,但需要两点校准。第一点用 25℃ 基准,第二点用 60℃ 基准,校准后在整个工作范围内最大误差能压到 ±0.3℃。不做校准直接用公式拟合,误差可能飘到 ±1.5℃ 甚至更大。所以我给本地 NTC 也加了校准参数存储,和远程传感器一视同仁。
5.3 校准补偿在工程上的取舍
很多工程师会把校准做得特别复杂,试图把传感器残余误差全消掉。我的经验是,对 HVAC 应用来说,校准到 ±0.2℃ 以内纯属浪费成本。控制系统的执行机构是风机盘管、电动阀、加热器,它们的动作精度本来就不高,你费劲把测温校准到 0.1℃,末端设备也响应不了那么细。
工程上需要抓的是四点:第一,传感器自身偏移是否在规格范围内;第二,安装热阻是否一致;第三,线缆电阻是否引入明显误差;第四,ADC 参考电压是否稳定。前三点校准一次就能保持很久,第四点属于硬件设计问题,必须在原理图阶段解决。
如果产品要做批量一致性,我的做法是生产工装自动记录每台设备的室温偏差值。设备在常温下上电,对比工装的高精度温度计,把差值写进设备 Flash。后续所有温度显示统一加上这个 offset。这样一台设备一条校准数据,操作成本极低,现场又看不到明显个体差异。远程传感器同样处理:每颗 PJ85718DM 在产线测试时读取 25℃ 偏移值,写入传感器旁边的 EEPROM 或者主控的校准表里,程序运行时自动补偿。
6. 踩坑记录与排查速查
6.1 高频问题速查表
调试和现场经常碰到的问题,我整理成一张表,基本覆盖了这类系统的常见故障。
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 总线检测不到设备 | 上拉电阻失效或漏接 | 用万用表测数据线空闲电压,应为 3.3V 左右 |
| 温度读数跳变 | 线缆接触不良或接地环路 | 检查接线端子,重新处理远端地线汇接 |
| 首颗传感器正常,后续全失败 | 总线寄生电容过大 | 降低上拉电阻到 1kΩ,或缩短支线长度 |
| 读数一直偏高 | 传感器和热源靠太近或热阻差异 | 检查安装位置和导热硅脂涂抹情况 |
| 远程节点偶发掉线 | 电源跌落或 TVS 钳位电压偏低 | 增加就地滤波电容,换更高功率 TVS |
| 本地 NTC 读数错乱 | ADC 参考电压不稳 | 确认 VDDA 滤波电容,统一参考电压源 |
| 低功耗模式下电流偏高 | GPIO 浮空输入漏电 | 逐个检查未用 GPIO,配置成固定输出 |
表里每一项都是实际踩出来的。比如“首颗正常后续全失败”,第一次遇到时我以为是程序 bug,查了三天才发现是总线加了太长支线,等效电容把所有读时隙都拖烂了,换成 1kΩ 上拉立刻恢复。这类问题表放在手边,现场排查效率能高一倍。
6.2 总线上挂 8 个传感器后的稳定性
最多一次我在一条 25 米的单总线上挂了 8 颗 PJ85718DM,这是测试极限。正常轮询周期从 1 秒拉长到 3 秒,因为总线搜索和顺序读取的时间随节点数线性增加。8 颗传感器全部采用外部供电,每颗单独去耦,远端在 15 米处额外并了一颗 1kΩ 上拉,整体运行了 72 小时没有出现一次通信失败。
节点增多之后,单总线搜索 ROM 的过程会占用不少时间。搜索算法要对每个 bit 做两次读时隙来区分 0、1 和冲突,64 位序列号搜一遍下来就是 128 个读时隙。如果每次上电都重新搜索,开机过程要几十毫秒,这在电池供电系统里是肉眼可见的耗电。所以我把搜索 ROM 得到的序列号表存进 Flash,上电后直接读取,只有重新搜索命令发出时才重建表。
总线上的传感器型号如果混用,还要特别注意上拉阻值的匹配。不同芯片的输入阈值和拉低驱动能力有差异,上拉电阻过小会让弱驱动传感器拉不下去,过大又让上升沿太慢。我建议每批传感器小批量做一次波形验证,确认上升时间小于 1μs 再量产。
6.3 我在项目里最后改掉的三处设计
这块讲三处返工,都是工程上最常见的坑。
第一处是单总线数据线没有加串联电阻,直接连到 MCU。实验室没问题,现场一打雷或者风机启停,浪涌直接打坏 MCU 引脚。后来在数据线上加了 220Ω 串联电阻和 TVS,整批板子再没坏过。这个改动成本不到五毛钱,保护效果立竿见影。
第二处是远程传感器供电没做独立控制。为了进一步省电,我想通过一个 MOSFET 开关控制总线电源,只在采样时才给传感器供电。实际发现频繁上电会导致单总线初始化偶尔失败,传感器内部电容需要时间稳定,上电后立刻读会撞上电源爬坡。后来改成常供电,靠 MCU 休眠和传感器自身待机来省电,系统总功耗依然很低,但可靠性明显提高。
第三处是把本地 NTC 的分压电阻放在了一块发热量大的电源芯片旁边。电源芯片工作时把电阻烤热,NTC 测到的温度跟着偏高 2℃。最后把分压电阻移到 PCB 边缘,远离发热源,并把 NTC 放在独立小岛上,周围铺地挖空,读数立刻恢复正常。热设计对温度测量系统的影响,比很多人想象的大得多。
我个人在实际操作中最大的体会是:这套方案的难点从来不是传感器怎么读,而是总线怎么布置、地线怎么接、供电怎么滤、校准怎么做。把硬件底子打扎实,软件只是按部就班地填逻辑而已。如果让我再做一个类似项目,我还会沿用 STM32L031C6 加 PJ85718DM 的组合,先把本地和远程温度分开处理,再考虑联网上报和远程运维,循序推进,比一上来就追求花哨功能稳妥得多。