☰
基于PJ85718DM与STM32的多点单总线温度监测系统设计
2026/10/22 6:42:21 网站建设 项目流程

1. 为什么盯上这块板:先把应用场景和选型逻辑说清楚

先别急着看代码,温度监测这活儿,看似简单,真正落到嵌入式设备和暖通空调系统里,坑比想象中多。我最近做了一个小项目,需求很明确:既要监测设备主板附近的本地温度,又要测量安装在几米甚至十几米外的风管、回风口的远程温度,而且成本要压得足够低,通讯要足够皮实。

选芯片的时候,我第一次看到PJ85718DM这颗料,第一反应是,这不就是常见的单总线数字温度传感器换了个丝印吗?但仔细翻了手册之后发现,事情没那么简单。它和主控之间只用一根数据线就能通信,一条总线上可以挂多个传感器,每个传感器有唯一的64位序列号,主机可以精确寻址到每一个。这一点对于“本地+远程多点测温”来说简直是量身定做的——不需要额外铺电源线、信号线、地线三根线到处跑,一根双绞线串过去就完事了。

主控这边选了STM32F042K6,核心是Cortex-M0内核,主频48MHz,Flash 32KB,RAM 6KB。你可能觉得性能很一般,但做温度采集这种任务,它绰绰有余。选它而不是更大容量的G0系列或F1系列,理由很现实:首先,F042的性价比高,做产品BOM核算有优势;其次,M0内核在极低主频下跑温度采集协议,功耗表现比F1那种M3核心更友好;最后,这颗料引脚少、封装面积小,适合放进HVAC控制器那种紧凑的PCB里。

设备要采集的点位大概是这样的:本地传感器放在主控板上,测的是控制板自身的工作温度,用来做降额保护和过热告警;远程传感器放在空调箱的送风口、回风口、冷凝器盘管这些关键测点,用来判断制冷制热效果、防结冰、辅助除霜逻辑。距离最近的大概0.5米,最远的将近12米。这就引出了整个项目的核心矛盾:一根线跑十几米,信号还能不能可靠?驱动代码怎么写才能在不同的线缆长度、不同的供电方式下稳定工作?接下来我把整个设计和调试过程拆开讲。这套思路不仅适合HVAC,你如果做储能电池包的电芯温度采集、冷库物联网终端、机房动环监控,同样能用上。

2. 硬件拓扑设计:本地就近挂载,远程长线传输,细节都在布线里

2.1 先画一下整体架构

主控板放在控制器外壳里,板上焊一个PJ85718DM,这就是本地测点;外部温度传感器通过一个两线制接插件引出,线的另一端是防水封装的PJ85718DM探头,这就是远程测点。主机的GPIO端口接单总线,总线上挂两个传感器。

这里有必要说一句:单总线协议在设计上是支持多点挂载的,理论上一条总线挂几十个器件也没问题,但实际工程中要克制。挂得越多,总线的寄生电容越大,时序裕量越差。我这几个测点只有在空调箱的送风口和回风口才需要分开两个远程探头,所以总线上最多就三颗器件:一颗本地板载,两颗远程探头。这个规模对时序来说毫无压力。

2.2 供电方式的选择:寄生供电与外部供电之争

很多刚接触单总线传感器的人在供电上会迷路。这类传感器支持两种供电模式:

  • 寄生供电:只有两根线,一根数据线一根地线,传感器从数据线上偷电,内部电容在总线上拉高电平时充电。这个模式好处是省一根线,坏处是如果线太长、电容太大,转换温度时的浪涌电流会把总线电压拉垮,导致读数异常。
  • 外部供电:三根线,VDD、DQ、GND,传感器转换温度时由外部电源直接供电,总线只负责通信,稳定得多。

我这个项目里,本地板载传感器用外部供电,因为PCB上电源到处都是,顺路引一个3.3V就行。远程探头我也选择了外部供电,但注意,我没让远程探头直接吃主控板上的3.3V,而是用一对接线端子把VDD和GND也引出去了,总共三根线。很多HVAC控制器为了省事,远程探头只引两根线,让传感器寄生供电。这种做法在5米内通常没事,但在10米以上、线径又细的场景,温度转换时容易出现偶发性的85℃误读或者CRC校验失败。

要说明的是,选外部供电并不代表三根线一定要走很粗的线。我用的是一般的屏蔽双绞线,其中一组双绞线走DQ和GND,另外单独一根线走VDD,而且VDD在远程探头端又加了一个10μF的电解电容和一个100nF的瓷片电容,用来在转换瞬间给传感器“撑腰”。这样试下来,12米的线缆也能稳定运行。

2.3 上拉电阻怎么选:3.3V系统里别照抄4.7kΩ

单总线是漏极开路结构,主机和传感器通过拉低总线来发送信号,不发送时靠上拉电阻把总线拉回高电平。官方推荐的上拉电阻典型值是4.7kΩ。但在实际项目中,这个值要结合供电电压和线缆长度重新算。

我的系统供电是3.3V,主控GPIO也是3.3V电平。如果照抄5V系统里的4.7kΩ,在3.3V下上拉电流只有0.7mA左右,而这颗传感器在通信时需要约1.5mA的电流驱动总线电容和内部电路,结果就是信号上升沿变得很缓,时序余量不足,单片机在高速读取时容易采到错误的电平。

我实际试过几组电阻值,用示波器看波形之后确认,3.3V系统、10米左右线缆最佳的匹配是2.2kΩ上拉。如果线缆进一步延长到20米以上,甚至可以用1kΩ。电阻值太小也不行,会加大总线寄生电容的充放电损耗,更重要的是,当传感器拉低总线时,上拉电阻越小,灌入传感器的电流越大,长期高温环境下可能会导致器件老化加速。

这里还要提醒一下,有些开发板上的上拉电阻是焊死的,我一开始在某款现成的最小系统板上做实验,板上默认就是4.7kΩ,跟远程探头配合就有偶发失败。后面打样自己画板子,才把上拉改成2.2kΩ。如果你是在现有板卡上调试,不想动板子,也可以把GPIO配置成开漏输出并内部使能上拉,但内部上拉一般只有几十kΩ级别,效果远不如外部电阻,所以最好的方案还是在PCB设计阶段就把上拉电阻的选型定好。

2.4 远程线的防护:HVAC环境里别忘了TVS和滤波

空调箱附近有压缩机、风机电机,这些东西启停瞬间会产生很强的电磁干扰,远程探头线缆相当于一根天线,会把干扰信号带进控制板。有些项目在实验室里调试一切正常,一到现场就频繁读到-55℃,或者传感器搜索不到,多半就是干扰问题。

我对远程线的做法是:接插件入口处,在DQ线上串一个100Ω的电阻,这个电阻要和上拉电阻的位置配合好——上拉在主机端,串阻在传感器端;同时DQ对地加一个TVS二极管,选反向关断电压在5V附近的规格,用来吸收静电和浪涌;在PCB布局上,远程端子、TVS、上拉电阻尽量靠近主控GPIO引脚,避免“先长走线再防护”的低级错误。这三板斧下来,现场干扰的影响基本可以忽略。

3. 驱动协议拆解:一次复位、一次ROM匹配、一次温度转换背后都在干什么

3.1 单总线时序的几个关键窗口

单总线协议的基本时序分三种:复位脉冲、写时隙、读时隙。任何一个时序的宽度不在合法范围内,通信就会断。我先把这几个关键窗口按PJ85718DM手册数值列出来,后面写代码时再把这些数值揉进延时逻辑里。

复位脉冲:主机先将总线拉低至少480μs,然后释放总线,传感器在15~60μs之后拉低总线约60~240μs作为存在脉冲,主机在发出复位脉冲之后,需要在一个窗口内采样总线电平,如果读到低电平,说明有传感器在线。

写时隙:主机拉低总线开始一个时隙,然后决定在时隙的前段释放还是继续拉低。如果主机想写0,就把总线拉低整个时隙的大约60μs;如果写1,就只拉低约6μs然后释放,靠上拉电阻把总线拉高。传感器在写时隙开始后15~60μs窗口内采样总线。注意,写0时隙的持续时间必须在60~120μs之间,写1时隙的释放后部分不能太短,否则传感器可能无法识别。

读时隙:主机拉低总线至少1μs,然后释放,紧接着在15μs内采样总线电平。传感器如果发0,会在主机释放总线后把总线拉低;如果发1,就保持高电平。所有读时隙必须在一个60μs左右的周期内完成,而两次读时隙之间至少要间隔1μs的恢复时间。

对于48MHz主频的STM32F042K6,如果你用HAL库的延时函数来凑这些时序,往往会因为函数调用开销导致时序偏差,尤其是读时隙,往往需要在采样前后框定精确的时间窗。我在实际代码里没有用HAL_Delay,而是直接用SysTick或者简单的循环计数来做微秒级延时,这一点在驱动效率上差别很大。

3.2 从零写一套驱动:初始化、ROM搜索与温度转换流程

整个驱动流程可以拆成四步:初始化总线、枚举总线上所有传感器序列号、对指定传感器发起温度转换、读取温度结果。前两步不是每次测温都要做,尤其是枚举,只有上电时做一次,把序列号存起来,之后直接通过ROM匹配命令来寻址目标传感器。

初始化很简单,GPIO配置成开漏输出,拉低480μs,释放,然后等传感器回复存在脉冲。代码类似这样:

#define DQ_PIN_PORT GPIOA #define DQ_PIN GPIO_PIN_8 static uint8_t ow_reset(void) { uint8_t presence = 1; GPIO_InitTypeDef gpio = {0}; gpio.Mode = GPIO_MODE_OUTPUT_OD; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_LOW; gpio.Pin = DQ_PIN; HAL_GPIO_Init(DQ_PIN_PORT, &gpio); HAL_GPIO_WritePin(DQ_PIN_PORT, DQ_PIN, GPIO_PIN_RESET); delay_us(500); HAL_GPIO_WritePin(DQ_PIN_PORT, DQ_PIN, GPIO_PIN_SET); delay_us(30); gpio.Mode = GPIO_MODE_INPUT; gpio.Pull = GPIO_PULLUP; HAL_GPIO_Init(DQ_PIN_PORT, &gpio); delay_us(50); if (HAL_GPIO_ReadPin(DQ_PIN_PORT, DQ_PIN) == GPIO_PIN_RESET) { presence = 0; } delay_us(450); return presence; }

注意,GPIO先配置成开漏输出,再切到输入模式。开漏模式下写入高电平相当于释放总线,这是单总线操作的基本功。有些新手把GPIO配成推挽输出,直接写1来释放总线,这在总线上同时有多个开漏器件时会出问题——推挽高电平会拖着其他器件拉低的总线电位,造成通信紊乱。

ROM搜索本身有点绕,它本质上是二叉树遍历:主机依次读取每一位的两位码,第一位表示传感器在该位上的值,第二位表示相反值,通过比对这两个电平可以判断当前节点是分支还是终点。对于只有两三个传感器的系统,还有一个更粗暴但有效的办法:直接用读取ROM命令(0x33)在有且仅有一个传感器时读取64位序列号。如果总线上挂了多颗,则必须用搜索算法。我写的搜索函数每次可以返回一个序列号,反复调用直到返回0表示全部枚举完成。

温度读取这块,我建议流程固定为:先向目标发送ROM匹配命令(0x55),随后发送它的64位序列号,再发送温度转换命令(0x44);等待转换完成,至少750ms——PJ85718DM在12位分辨率下最大转换时间是750ms;然后再发一次ROM匹配,发读取暂存器命令(0xBE),连续读9个字节。第0、1字节是温度值,第8字节是CRC校验。

3.3 CRC校验不能省,场景越恶劣越要较真

刚才提到读回来9个字节,最后一个是CRC。很多示例代码读温度时光顾着拼字节,不看校验,这在实验室环境也许能跑一整天不出错,到了现场就是灾难。单总线的CRC算法是多项式x^8+x^5+x^4+1,也就是0x8C,初始种子是0。我实测下来,远程线缆在受到干扰时,出现的错误形态有两种:一种是读回来的数据直接是0xFF或0x00,一眼就能看出来有问题;另一种比较坑,温度值看起来完全合理,比如从23.5℃变成了27.3℃,但CRC校验是错的,这种情况如果没有校验逻辑,系统就会把假温度当成真温度用,轻则控制逻辑误判,重则可能触发不该有的告警。

我在驱动里每次读完暂存器都会跑一遍CRC计算,如果失败就重新发送读取命令重试,连续重试3次都失败就丢弃这次数据,标记该通道为故障。在长时间运行测试中,12米线上CRC失败的偶发率大概在千分之一量级,重试基本都能成功。如果你发现重试也救不回来,那基本不是时序问题,而是要检查硬件了,比如上拉电阻、线缆屏蔽层接地这类。

4. 软件架构怎么搭:把温度采集做成一个可靠的状态机

4.1 用状态机管理测温流程,不要把延时函数糊在main里

如果你之前是从某个示例代码复制粘贴开始做的,可能会习惯这么写:main循环里先发转换命令,延时750ms,然后读温度。这个写法在单测点场景没毛病,但在多传感器、多测温周期,还要兼顾HVAC控制逻辑的产品里,基本没法用。原因很简单:750ms的阻塞延时意味着整个控制主循环被卡死,按键扫描、通讯轮询全部停摆,这在一个实时控制设备里是不可接受的。

我的做法是把测温流程拆成一个状态机,放在10ms周期的主循环里调度。状态定义如下:

  • 空闲:什么都不做,等待下一次测温周期到来
  • 启动转换:对指定传感器发送温度转换命令
  • 等待转换:记录启动时间,不断查询是否已到750ms,到点后跳到读取状态
  • 读取暂存器:发送读取命令并接收9个字节

每个状态只做自己该做的事,一旦遇到耗时操作,比如发送复位脉冲的500μs延时,就用阻塞方式原地等待,因为500μs对于10ms的调度周期来说不算什么。真正需要跨周期等待的是750ms转换时间,用时间戳判断,不阻塞。

主机始终只和当前选中的传感器通信。先用ROM匹配命令锁定目标,之后再发转换命令或读取命令。这样多个传感器轮询时不会乱套。

4.2 测温周期与滤波策略:HVAC控制不能直接拿原始值用

测温周期我设的是1秒。空调系统的热惯性很大,1秒采一次完全够用,温度控制环路用的是每5秒的均值,每秒的瞬时值只用于故障诊断和界面显示。

软件滤波我采用了两个手段配合:一是滑动平均,取最近5次有效读数的平均数;二是变化率限幅,如果新读数跟上一次有效值的差超过3℃——远程探头安装在风管里,正常工况下1秒内的温度变化不可能这么大——就认为是一次干扰,丢弃该数据,连续丢弃5次则把实际温度强制更新为新值,防止在真实温度突变时滤波过于迟钝。

这里有一个容易踩的坑:滑动平均窗口里的历史值如果包含了非法数据,会拖慢真实变化的响应速度。所以我只在全部数据都通过CRC校验和范围检查之后才入窗。范围检查很简单,传感器本身只支持-55℃到+125℃,如果读出来是-55℃,大概率不是真实温度,而是传感器供电丢失或者总线时序完全错乱。我设置的有效范围是-40℃到+80℃,超出即判无效。

4.3 多传感器管理:序列号映射与故障标志位

两个传感器用数组管理,每个节点结构体里保存64位序列号、当前温度值、滤波值、最后一次成功通信时间戳、连续故障次数。上电时先尝试枚举,如果总线上只有两颗传感器,而实际枚举出来也是两颗,就把它们按序列号分别映射到本地和远程两个逻辑测点。

比较麻烦一个场景是产品量产时序列号各不相同,不能通过固件硬编码。我的做法是在出厂校准环节把枚举出来的两颗序列号按顺序写入Flash,实际使用时如果枚举结果顺序变化,就通过一个简单的排序:序列号小的当本地测点,大的当远程测点。这个映射规则有点无厘头,但对于固定两颗传感器的产品是稳定可靠的,不会因为上电时序不同导致本地和远程数据互换。

故障处理上,连续读取失败超过10次,就把该测点标记为故障,温度显示为“--”,控制逻辑切到保守模式:如果是冷凝器盘管温度传感器故障,就不再启动制冷,防止盘管结冰;如果是送风温度故障,风机仍然运行,但停止调节阀门,保持在上一次安全开度。这样即使传感器彻底挂了,设备也不会出安全事故。

5. 实测过程中的几个经典翻车现场

5.1 翻车一:第一版PCB温度读回来全是85℃

这个问题我在网上看到很多人遇到过,也花了不少时间排查。85℃这个值很特殊,它其实是传感器上电后的默认温度寄存器的复位值。如果主机读到的温度一直是85℃,说明传感器根本没有成功执行温度转换命令,寄存器只停留在复位状态。换了个传感器还是85℃,基本就排除了传感器本身的故障。

最后定位到根因:我在发送温度转换命令之后,切换GPIO模式的时候把关断输出配错了,导致总线在传感器转换期间没有被可靠上拉,传感器压根没收到有效的转换指令。解决方法是把总线释放的操作统一封装成一个函数,确保GPIO永远是开漏输出高电平,而不是在输入模式间来回切换——开漏输出高电平和输入模式虽然电气效果一样,但在模式切换的瞬间可能产生毛刺,直接把命令时序破坏掉。

5.2 翻车二:远程探头低温读数在0℃附近频繁跳动

当时的现象是,温度在0~5℃区间来回跳,而且只有距离最远的那颗探头这样。我用示波器抓DQ线上的波形,发现远端传感器发出的读时隙信号上升沿非常缓,在主机采样时电平还处于0.8V左右的模糊区,读到的比特位不稳定。

这个问题的根源是线缆分布电容太大。双绞线每米大约有50pF电容,12米就是600pF,再加上传感器的输入电容,总线总电容接近800pF。上拉电阻2.2kΩ给800pF充电的时间常数是1.76μs,在标准读时隙的采样窗口内,电压刚好爬升到阈值附近,非常危险。

我把上拉电阻从2.2kΩ降到1kΩ,实测波形上升沿改善明显,再没出现跳动。注意此时上拉电流会接近3.3mA,传感器拉低总线时的灌电流也增大了,发热会略有增加,但仍在器件允许范围内。如果线缆更长,比如超过20米,我建议直接把传感器换成电流源方式驱动总线,或者改走RS485组网,别硬撑着单总线长距离传输。

5.3 翻车三:示波器探头一接上去就好,一拿下来就坏

这个现象特别迷惑,但它真的发生在调试过程中。问题的本质是示波器探头本身有大约10MΩ的输入阻抗和十几pF的电容,探头接上去相当于给总线增加了一个很小的负载,但同时它改变了地回路——确切地说,把设备地和示波器的地连在了一起,无意中给远程线缆的屏蔽层提供了更好的接地路径。

去掉探头后,屏蔽层悬空,干扰信号重新进来,故障复现。所以最终解决方案不是依赖示波器,而是给远程线的屏蔽层单独做接地处理:屏蔽层在控制器端接地,传感器端悬空,避免形成地环路。现场设备的地线质量很重要,HVAC这类设备经常被安装在没有独立地线的环境里,屏蔽层接了大地,干扰问题才彻底解决。

6. 低功耗与量产细节:从能跑到能卖之间还差几步

6.1 在电池供电设备里压低功耗

如果你的场景是电池供电的冷柜温度记录仪,那功耗控制就要更激进。STM32F042K6支持多种低功耗模式,在两次测温之间完全可以从Stop模式唤醒。我的做法是:把传感器和主控的单总线GPIO都配置成外部中断模式,传感器没有通信时不活动,主控睡在Stop模式下,用RTC定时器每10秒唤醒一次,整个过程耗时不到30ms,剩余99.7%时间都在睡觉。

这10秒一次的测温周期下,实测平均功耗能压到几十微安级别,对于一节18650电池来说,理论续航可以到一年以上。但要注意,传感器本身在等待期间会有一点静态功耗,如果整机要求极低功耗,还可以用一颗MOS管控制传感器的VDD,只有测温时才通电,这样等于把传感器的静态功耗也掐掉了。代价是每次测温前要给传感器留出至少100ms上电稳定时间,否则第一读永远是失败的。

6.2 量产测试与校准:每颗传感器的误差不要轻信规格书

这类单总线传感器的精度一般在±0.5℃范围内,但这是在理想条件下的数值。实际上,传感器封装内部的引脚焊接应力、PCB走线的热传导、探头外壳的导热介质,都会引入额外误差。我见过一块板子上两颗传感器测同一个点,读数相差1.2℃的情况。所以在产品出厂前,我建议做一次单测点校准:在一个恒温槽里,以0℃和50℃两个标准温度点做校准,把每颗传感器的偏差存进Flash,在实际显示温度时做线性纠偏。

校准的线性纠偏也就是做一个两点拟合,求出偏移量和增益系数。量产后发现,绝大多数器件的增益偏差很小,问题都在偏移上,所以大多数厂家只做零点校准就够了。具体的实现逻辑是:

typedef struct { int16_t offset; // ADC/计数偏移量,单位0.1℃ int16_t gain; // 增益修正系数,默认1000,表示1.000 } temp_cal_t;

如果25℃时读书偏高0.4℃,offset存-4(单位0.1℃)即可。如果50℃时偏高0.2℃,25℃时偏高0.4℃,那么gain可以调整成1002左右,用一个简单的直线方程去修正全量程。

这个校准动作对于工业级设备来说很重要,因为HVAC系统里温度测点的偏差会直接影响压缩机启停判断,偏差大要么导致频繁启停浪费电,要么导致房间温度永远达不到设定值。

7. 最后再分享一点现场经验

调试这类单总线多点测温系统,我最大的体会是,硬件上的问题往往伪装成软件问题,而软件上的问题又常常通过硬件现象暴露出来。比如读温度偶发失败,你可能会拼命调延时参数、改滤波逻辑,但实际上就是某个GPIO在模式切换时产生了微秒级毛刺;反过来,你以为上拉电阻换个阻值就能搞定,结果发现是远程线的屏蔽层没有接地。所以排查问题的时候,示波器要跟代码一起看,时序波形和寄存器数据要互相对照。

如果你现在要做的项目和我类似,我先给你的建议是:只要传感器总线上挂超过两颗,或者线缆长度超过5米,就果断用外部供电,别省那一根线;上拉电阻按线缆长度实测调整,不要拘泥于规格书上的典型值;驱动代码里CRC校验是底线,绝不能省。这套方案做冷库、做机房、做风管测温都适用,把时序和硬件搞扎实了,剩下的事情就只是换壳子。

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

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

立即咨询