单总线数字温度传感器MY18E20解析与MicroPython驱动实战
发布时间:2026/9/9 7:42:43 作者:尧图编辑部 阅读量:1,286

搞嵌入式这些年测温方案我折腾过不少热敏电阻、NTC、PT100都用过但要说最省事的还得是单总线数字温度传感器。今天要拆的这颗MY18E20名字看着陌生其实就是DS18B20协议体系下的国产兼容器件一颗TO-92封装的小管子一根数据线既能传数据又能供电管理温度直接给出数字量不需要ADC不需要运算放大器一根IO口就能搞定。这篇文章我打算把单总线协议从头到尾撕开揉碎讲一遍然后给出一份可以直接搬进项目的MicroPython驱动代码顺便把我在实际调试过程中踩过的坑、总结出的排查方法全部摆出来。如果你正在做温控项目、环境监测或者任何需要低成本测温的场景又不想被NTC查表、标定搞到头疼这篇文章应该能帮你省下不少时间。1. MY18E20这个传感器到底特殊在哪1.1 一颗传感器搞定测温全链路MY18E20的基本定位很简单单总线数字温度传感器测量范围-55°C到125°C在-10°C到85°C范围内能保证不错的精度。按DS18B20兼容体系的默认配置它出厂时是12位温度分辨率也就是0.0625°C的量化精度这个精度对绝大多数环境监测、设备状态监控场景已经完全够了。它的神奇之处在于内部集成了温度传感单元、ADC转换、寄存器存储、ROM寻址逻辑和单总线通信控制电路。主机单片机只需要一根GPIO口按照单总线协议发出复位脉冲和功能命令传感器就会把温度值写进自己的暂存器然后主机再按比特位把数据读出来。整个过程不需要外部时钟、不需要片选引脚连供电都可以跟数据线共用一根。每一个MY18E20在出厂时都会被激光写入一个64位的ROM序列号这个序列号全球唯一包含1字节家族码、6字节序列号和1字节CRC校验。这个特性决定了我们可以把多个传感器并联挂到同一根总线上各自通过ROM码区分地址这在物理上把一根线扩展成了一条可挂载多设备的微型总线。1.2 硬件连接上拉电阻真的不能省虽然单总线听起来很美妙但硬件连接上有一个关键点数据线DQ必须接一个4.7kΩ左右的上拉电阻到电源正极。这不是可选项而是协议工作的物理基础。单总线的所有设备包括主机都是开漏输出默认状态下谁都不驱动总线总线靠上拉电阻维持在逻辑高电平任何一方要发送数据时直接把总线拉低释放后总线自动恢复高电平。接线的常规做法是VDD接3.0V-5.5V电源GND接地DQ接主控GPIO然后在DQ和VDD之间接上拉电阻。如果你用MicroPython开发板供电一般3.3V逻辑电平就能正常通信。我用过的绝大多数MCU在3.3V供电下直接驱动DS18B20体系器件都没有问题关键是上拉电阻要接对位置很多第一次玩的朋友把上拉电阻漏掉结果总线永远读不到存在脉冲直接报错。还有一个常见问题是线长。板内短距离用杜邦线完全没有问题但如果你要把传感器拉到1米以上建议把上拉电阻降到2.2kΩ否则长线的寄生电容会拖慢信号上升沿导致读时序的采样点落在模糊区轻则CRC报错重则直接识别不到设备。关于这点后面的调试章节还会专门展开。1.3 和DS18B20的兼容性MY18E20这个名字看起来就是DS18B20的衍生型号实际使用中我把它当作DS18B20的兼容器件对待协议层面完全一致ROM家族码同样是0x28暂存器布局一样时序参数和命令字也完全兼容。这意味着你在MicroPython里既可以用内置的ds18x20模块驱动它也可以直接参考DS18B20的文档来逐寄存器操作。我这里先做一个重要说明这篇博客里所有关于MY18E20的寄存器、时序、命令描述都是基于它与DS18B20协议兼容的前提来讲解的。实际器件是否100%复刻DS18B20的全部行为不同批次、不同厂商可能存在微小差异。所以拿到样品后第一件事应该是先用逻辑分析仪抓一下波形确认复位时序和读写时序可行再进入代码开发。别嫌麻烦这个习惯能帮你避免很多后期折腾。2. 单总线协议的两段式通信机制2.1 物理层设计为什么一根线能完成双向数据交换单总线之所以叫单总线核心在于物理层的开漏结构。你可以把开漏输出想象成一条小巷默认状态下巷子是空的所有人可以自由通行这是高电平如果某个设备想占用巷子就把入口堵住这是低电平堵住的人松开之后巷子又恢复畅通。总线上的所有设备共享这条小巷同一时刻只允许一个设备驱动低电平谁想说话谁就把总线拉低然后释放其他设备通过采样电平高低来解码信息。这个设计带来的一个直接约束就是总线上的设备只能主动拉低不能主动拉高。高电平完全依赖外部上拉电阻实现。理解了这一点你就能明白为什么上拉电阻的阻值会影响通信质量阻值太大上升沿变缓阻值太小拉高时灌电流过大可能损坏IO口。还有一个关键概念叫时隙slot。单总线上每一位数据的传输都占用一个时隙时隙由主机发起以主机拉低总线为起始标志。无论是写还是读一个完整的通信过程就是一系列时隙的组合而设备之间的同步完全靠这些拉低动作和电平持续时间来对齐。2.2 ROM命令与功能命令的两段式结构单总线通信在命令层面上分两步走这是理解协议最重要的框架。第一次总线复位之后主机先发一个ROM命令用来选择总线上哪一台设备参与后面的对话第二次总线复位之后主机再发一个功能命令让选中的设备执行具体的操作比如启动温度转换、读取暂存器。ROM命令有好几条最常用的几个是0x33读ROM直接读取总线上唯一设备的64位ROM码适用于总线上只有1个传感器的场景0xCC跳过ROM不指定地址让总线上所有设备都响应后续功能命令0x55匹配ROM后面跟随64位ROM码只有ROM码匹配的设备才会响应该功能命令0xF0搜索ROM用于系统上电时识别总线上挂了多少个设备并读取它们的ROM码所以如果你总线上只挂了一个传感器最简单的流程就是复位之后发0xCC跳过ROM然后直接发功能命令。如果挂了多个传感器就需要先用0xF0把所有设备的ROM码扫出来再用0x55指定某一个进行操作。内置的ds18x20模块里提供的scan()方法就是干这个事的。2.3 功能命令的典型调用流程以一次完整读取温度为例单总线设备的标准流程是主机发送复位脉冲等待从机响应存在脉冲主机发送ROM命令0xCC或者0x55ROM码主机发送功能命令0x44启动温度转换等待转换完成12位分辨率下典型时间是750ms主机再次发送复位脉冲主机再次发送ROM命令主机发送功能命令0xBE读取暂存器内容主机从总线上读回9个字节的数据解析温度值这个流程在驱动代码里就是一组固定的状态机步骤。很多第一次自己写驱动的人会漏掉第二次复位或者忘记在读取前重新发送ROM命令结果读到的永远是0xFF或者乱码。记住这个两段式结构每次发功能命令前都要先复位一次再发ROM命令。3. 三个基础时序深度拆解3.1 复位时序一高一低的握手任何一个完整的单总线通信都必须从复位时序开始。复位时序的目的是让主机和从机完成一次握手确认总线上确实存在设备并且让所有从机恢复到初始状态。过程是这样的主机先把总线拉低持续480us以上然后释放总线。从机检测到低电平持续一段时间后又变为高电平就知道主机在呼叫自己。从机不会马上应答而是等待15us到60us后主动把总线拉低持续60us到240us这个低电平就是存在脉冲。主机释放总线后在60us到240us的窗口内采样总线电平如果读到低电平说明总线上有设备在线。这里有一个细节容易被忽略主机读取存在脉冲时不能在自己释放总线后立即采样也不能等到太晚。采样太早从机还没开始应答采样太晚从机的存在脉冲已经结束。合理的做法是在释放总线后延时70us左右再采样这样能保证落在从机的应答窗口内。我在后面的MicroPython驱动中也是这样设计的。3.2 写时隙用低电平持续时间区分0和1复位完成后主机向从机发送数据每一位都要占据一个写时隙。写时隙的标准结构是主机拉低总线作为起始信号然后根据要发送的数据决定总线释放的时机。写1时隙主机拉低总线持续1us到15us后释放剩余时间总线保持高电平整个时隙不少于60us。 写0时隙主机拉低总线持续60us到120us后释放。从机在主机拉低总线之后的15us到60us窗口内采样总线电平。如果采样到高电平代表收到一个1采样到低电平代表收到一个0。所以写1和写0的关键差别在于低电平持续的时间长短而不是电平极性这也是单总线时序比较容易混淆的点。在实际驱动代码里我通常写1时拉低2us到6us然后释放写0时拉低60us然后释放最后都要保证整个时隙总时长在60us以上。很多驱动代码会用sleep_us来制造延时但我必须提醒你MicroPython的sleep_us在不同平台、不同固件版本上的实际误差可以相差很大这部分会在驱动章节具体说明。3.3 读时隙从机告诉主机数据位读时隙由主机发起但数据由从机提供。主机先拉低总线持续1us到15us之后释放然后从机接管总线。如果从机要传0它会主动把总线拉低并保持到时隙结束如果从机要传1它不驱动总线总线由上拉电阻维持在高电平。主机必须在一个读时隙开始后的15us附近采样总线电平不能在时隙末尾才去读。因为一旦超过15us从机可能已经释放了总线电平会被上拉电阻拉高主机就会把0误判成1。换句话说读时序的采样窗口非常短这是整个单总线协议里对时序要求最严格的地方。这也是许多MicroPython纯Python驱动不稳定的根源。解释型语言执行每条语句的耗时本身就有波动pin.value()这个函数调用一次可能就要花好几us再加上sleep_us的误差采样点很容易漂移出15us窗口。3.4 时序参数速查表为了写驱动方便我把关键时间参数整理成一张表代码里设计延时参数的时候直接对着看时序名称参数推荐时间允许范围复位脉冲低电平主机拉低500us480us以上存在脉冲等待主机释放后等待70us15us-60us后从机应答存在脉冲从机拉低约120us采样60us-240us写1拉低主机拉低2us-6us1us-15us写0拉低主机拉低60us60us-120us写时隙总时长从时隙开始到下一次70us60us-120us读时隙拉低主机拉低2us-6us1us-15us读采样点主机采样时隙开始后15us附近约15us读时隙总时长从时隙开始到下一次65us60us以上4. 驱动架构设计与核心方法实现MicroPython4.1 驱动架构设计把协议层和业务层分开写驱动之前先把架构想清楚。我习惯把驱动拆成两层底层叫协议层负责处理复位、写位、读位、写字节、读字节这些最基本的单总线操作上层叫业务层负责组合这些底层操作实现扫描ROM、启动温度转换、读取温度、解析数据这些面向用户的功能。这样拆的好处是显而易见的。如果你以后要从MY18E20换到其他单总线器件比如开关、湿度传感器之类只需要重写业务层协议层基本不用动。反过来如果你要换平台、换固件也只需要针对协议层做时序校准业务层代码保持稳定。在MicroPython里我习惯把传感器对象作为构造参数传入驱动类而不是在类内部硬编码引脚号。这样写出来的驱动可移植性更强测试也更方便。后面给的完整代码就是按照这个思路设计的。4.2 方案A直接用固件内置模块生产环境推荐MicroPython官方固件里自带onewire和ds18x20两个模块这两个模块底层是C语言实现的时序精度比纯Python可靠得多。如果项目是用来生产或者长期运行的我的建议非常明确不要自己去重复造轮子直接用内置模块。理由很简单单总线协议最关键的时序窗口是微秒级别的而MicroPython解释器执行一条Python语句的开销本身就可能超过这个量级。内置onewire模块的时序在固件层由C函数完成底层操作和精确延时都是编译后的机器码稳定性完全不在同一个量级。最基础的应用层代码如下from machine import Pin import onewire import ds18x20 import time ow onewire.OneWire(Pin(14)) sensor ds18x20.DS18X20(ow) roms sensor.scan() print(发现传感器: , roms) if roms: sensor.convert_temp() time.sleep_ms(750) # 12位分辨率转换时间 for rom in roms: print(rom.hex(), - , sensor.read_temp(rom), °C)这段代码已经把扫描、转换、读取、打印的全流程覆盖了非常简单。但如果你要的是理解协议背后的原理或者在特殊平台上需要自己控制时序那就需要看方案B。4.3 方案B从底层时序写起的教学版驱动接下来说说自己从时序层写驱动。先给出核心协议层代码这部分实现了单总线最基本的三个操作复位、写位、读位以及在此基础上组合出的读写字节。from machine import Pin import time class MY18E20Protocol: def __init__(self, pin): self.pin pin self.pin.value(1) def reset(self): p self.pin p.init(Pin.OUT, Pin.PULL_UP) p.value(0) time.sleep_us(500) p.init(Pin.IN, Pin.PULL_UP) time.sleep_us(70) presence p.value() 0 time.sleep_us(410) p.init(Pin.OUT, Pin.PULL_UP) p.value(1) return presence def write_bit(self, bit): p self.pin p.init(Pin.OUT, Pin.PULL_UP) p.value(0) if bit: time.sleep_us(6) p.value(1) time.sleep_us(64) else: time.sleep_us(60) p.value(1) time.sleep_us(10) def read_bit(self): p self.pin p.init(Pin.OUT, Pin.PULL_UP) p.value(0) time.sleep_us(6) p.init(Pin.IN, Pin.PULL_UP) time.sleep_us(8) value p.value() time.sleep_us(50) return value def write_byte(self, data): for i in range(8): self.write_bit((data i) 1) def read_byte(self): data 0 for i in range(8): data | self.read_bit() i return data这个协议层的每一个方法都对应着前面第三章节讲过的时序。写数据时低位先发这一点和很多串行协议不一样是单总线的惯例一定要记住。需要特别说明的是这个纯Python版本的时序参数是我在树莓派Pico上针对MicroPython固件校准之后的经验值换了平台不能直接照抄。ESP32上MicroPython的Pin操作和sleep_us的行为就和Pico有明显差异我在这两种板上跑同一个驱动采样时序要改的参数完全不一样。生产环境还是那句话请优先使用固件内置onewire模块。4.4 业务层方法温度转换与暂存器读取有了底层协议层接着写业务层。温度转换和读取暂存器是核心功能对应功能命令0x44和0xBE。class MY18E20(MY18E20Protocol): def convert_temp(self, romNone): self.reset() if rom is None: self.write_byte(0xCC) # 跳过ROM else: self.write_byte(0x55) # 匹配ROM for b in rom: self.write_byte(b) self.write_byte(0x44) def read_scratchpad(self, romNone): self.reset() if rom is None: self.write_byte(0xCC) else: self.write_byte(0x55) for b in rom: self.write_byte(b) self.write_byte(0xBE) data [self.read_byte() for _ in range(9)] if self.crc8(data) ! 0: raise ValueError(CRC check failed) return data def read_temp(self, romNone): self.convert_temp(rom) time.sleep_ms(750) data self.read_scratchpad(rom) raw data[0] | (data[1] 8) if raw 0x8000: raw - 0x10000 return raw * 0.0625 def crc8(self, data): crc 0 for byte in data: for _ in range(8): fb (crc ^ byte) 0x01 crc 1 if fb: crc ^ 0x8C byte 1 return crcread_temp的流程和前面2.3节的标准流程完全一致先复位发跳过ROM命令再发0x44启动转换等待750ms后再次复位发跳过ROM命令然后发0xBE读取9字节暂存器最后做CRC校验和温度解析。温度解析那三行值得细讲一下。暂存器的0字节是温度低字节1字节是温度高字节两个拼起来是一个16位的有符号数低12位有效。如果最高位是1说明温度是负数需要对这个16位数做补码转换也就是先减去0x10000再乘以0.0625。比如-10.125°C对应raw -162 0xFF5E直接转成无符号数是65374最高位是1减去65536得到-162再乘0.0625就是-10.125°C。 ## 5. 一版可以直接运行的完整驱动与示例 ### 5.1 完整代码扫描、匹配、单设备多设备都能用 下面给一份完整的驱动文件可以直接保存成my18e20.py放到开发板上使用。这份代码同时支持单设备和多设备两种模式单设备可以省略ROM码参数多设备则先扫描再按地址读取。 python from machine import Pin import time class MY18E20: def __init__(self, pin): self.pin pin self.pin.init(Pin.OUT, Pin.PULL_UP) self.pin.value(1) self.roms [] def _reset(self): p self.pin p.init(Pin.OUT, Pin.PULL_UP) p.value(0) time.sleep_us(500) p.init(Pin.IN, Pin.PULL_UP) time.sleep_us(70) ok p.value() 0 time.sleep_us(410) p.init(Pin.OUT, Pin.PULL_UP) p.value(1) return ok def _write_bit(self, bit): p self.pin p.init(Pin.OUT, Pin.PULL_UP) p.value(0) if bit: time.sleep_us(6) p.value(1) time.sleep_us(64) else: time.sleep_us(60) p.value(1) time.sleep_us(10) def _read_bit(self): p self.pin p.init(Pin.OUT, Pin.PULL_UP) p.value(0) time.sleep_us(6) p.init(Pin.IN, Pin.PULL_UP) time.sleep_us(8) v p.value() time.sleep_us(50) return v def _write_byte(self, data): for i in range(8): self._write_bit((data i) 1) def _read_byte(self): data 0 for i in range(8): data | self._read_bit() i return data def _crc8(self, data): crc 0 for byte in data: for _ in range(8): fb (crc ^ byte) 0x01 crc 1 if fb: crc ^ 0x8C byte 1 return crc def scan(self): self.roms [] if not self._reset(): raise RuntimeError(MY18E20 not found) self._write_byte(0xF0) for _ in range(64): bit_a self._read_bit() bit_b self._read_bit() if bit_a and bit_b: # 总线上没有设备或数据线异常 self.roms [] return self.roms if not bit_a and not bit_b: # 冲突位教学驱动里简化处理按0继续搜 bit 0 else: bit bit_a # 两个值不同说明所有设备在此位相同 self._write_bit(bit) return self.roms然后业务层代码def convert_temp(self, romNone): if not self._reset(): raise RuntimeError(MY18E20 not found) if rom is None: self._write_byte(0xCC) else: self._write_byte(0x55) for b in rom: self._write_byte(b) self._write_byte(0x44) def read_temp(self, romNone): self.convert_temp(rom) time.sleep_ms(750) if not self._reset(): raise RuntimeError(MY18E20 not found) if rom is None: self._write_byte(0xCC) else: self._write_byte(0x55) for b in rom: self._write_byte(b) self._write_byte(0xBE) data [self._read_byte() for _ in range(9)] if self._crc8(data) ! 0: raise ValueError(CRC error) raw data[0] | (data[1] 8) if raw 0x8000: raw - 0x10000 return raw * 0.0625调用示例# 单设备快速读取 s MY18E20(Pin(14)) if s.read_temp() is not None: print(温度: {:.2f} C.format(s.read_temp()))注意scan()里我把冲突位简化处理了写完0之后没有做真正的回溯遍历这个教学版能扫出单设备但多设备搜索可能不完整。如果你总线上超过一个设备更稳妥的办法是用固件内置的ds18x20模块的scan()它会正确处理所有分支。5.2 温度解析与CRC校验别忽略那两个细节温度解析看起来简单但有两个细节经常出问题。第一读取暂存器返回的9个字节里前两个是温度第三个和第四个是用户自定义报警阈值TH和TL第五个是配置寄存器第六到第八个是保留字节第九个是前八个字节的CRC。CRC校验时要把前8个字节都算进去不是只算温度两字节。CRC8的多项式是x^8x^5x^41对应二进制0x8C低位移位版本。这段代码用的是按位计算效率不高但逻辑直观。如果你在极限性能场景下跑可以考虑查表法优化但这个环节一般不是瓶颈750ms的转换等待已经足够消耗了。第二读取到的原始值默认是12位分辨率也就是小数点后4位的表达。如果你的应用场景不需要这么高精度可以通过写配置寄存器把分辨率降到9位、10位或11位以换取更短的转换时间。9位分辨率转换时间约为93.75ms10位约187.5ms11位约375ms。不过大多数时候我不建议动这个配置12位的750ms在温度采集中完全够用。5.3 多传感器挂载ROM搜索与匹配原理单总线上挂多个传感器是它最有意思的场景。每个传感器的ROM码都是出厂激光刻写的全球唯一值主机的任务就是在上电时把这些ROM码全部扫出来。搜索ROM命令0xF0会触发一个特殊的位级交互过程主机在每一位上连续读两次。第一次读到的是该位在当前所有设备中的或值第二次读到的是反相或值。如果两个读数都是0说明这一位上既有设备是0又有设备是1也就是出现了冲突如果读数是0和1说明所有设备这一位都是1如果读数是1和0说明所有设备这一位都是0如果两次都是1则说明总线上没有设备在线。出现冲突时主机需要选择一个分支继续走然后记住另一个分支没有探索之后回溯继续搜索。这个过程说穿了就相当于在一棵二叉树里做深度优先遍历。理解了原理再看固件内置ds18x20模块的scan()实现就明白它在干什么了。实际项目中如果你不想深入这个算法可以直接用内置模块的scan()拿到全部ROM码然后再配合自己的业务逻辑处理。在学习阶段动手把搜索算法写一遍对理解单总线协议非常有帮助我建议至少读一遍源码。6. 实战中的坑与调试经验6.1 常见问题速查表我把自己和周围朋友在项目里遇到最多的故障现象整理成一张表排查的时候可以对照着看故障现象可能原因处理方法一直显示85°C温度转换命令未执行成功读到的是上电默认值0x0550检查上拉电阻、供电、GPIO初始化确认convert_temp发出后等待足够时间找不到设备接线错误、上拉电阻缺失、GPIO切错重新检查DQ引脚连接确认上拉4.7k用万用表测DQ高电平是否接近VDDCRC频繁报错读时序采样点偏移、线路过长导致波形畸变调大概率延时参数逻辑分析仪抓波形确认采样窗口长线换2.2k上拉温度跳变不稳定供电纹波、VDD不稳、寄生供电模式供电不足改用外部供电VDD和GND之间加100nF去耦电容多设备只有一个能被识别ROM搜索不完整、总线驱动能力不足用内置scan()替代教学版搜索或调整上拉电阻并检查总线电容程序卡死在等待存在脉冲从机没有响应主机死等给reset操作加超时保护比如循环采样超过一定时间就报错返回说到85°C这个问题多说一句。DS18B20兼容器件上电时暂存器里的默认温度值不是0而是0x0550换算出来正好是85°C。如果你读出来一直是85说明你的读取命令执行了但温度转换命令没有真正跑通。这种情况优先查时序和供电不要怀疑芯片坏了。6.2 用逻辑分析仪校准时序在我做过的所有单总线调试里逻辑分析仪是投入产出比最高的工具。哪怕是最便宜的几十块钱的8通道逻辑分析仪只要能支持8MHz以上的采样率就足够用来抓单总线时序了。接法很简单把逻辑分析仪的通道接到DQ线上地线接公共地然后跑一段读取程序抓到的波形就能直观地看出复位脉冲的宽度、写时隙的高低电平比例、读时隙的采样点位置。抓波形之后重点看三个地方。第一是复位脉冲有没有达到480us以上很多驱动写60us甚至更短从机根本来不及识别。第二是写1时隙里低电平的持续时间有些实现为了迁就解释器速度把拉低时间拉得太长超过了15us从机就会把1误判成0。第三是读时序中你实际采样电平的点如果把读时序区间放大可以看到电平翻转的瞬间你要确保代码里pin.value()调用的时刻落在从机驱动总线的稳定区间内。基于实测波形来校准驱动里的sleep_us参数是我最推荐的做法。不要凭感觉猜参数不要照抄别人固件里的延时每个平台、每块板的实际情况都不一样。6.3 上拉电阻、线缆长度与寄生供电最后再说几个容易踩的隐形坑。上拉电阻的取值要结合供电电压和总线长度综合考虑。3.3V供电、短线小于30cm用4.7k没问题5V供电也常能用4.7k但要确认GPIO耐压线缆超过1米建议降到2.2k但要注意总线上挂的设备越多总线上所有设备释放总线时的高电平驱动能力就越重要阻值太小会让低电平的灌电流偏大影响设备识别。传感器可以工作在寄生供电模式也就是VDD和GND短接完全靠DQ线上的高电平给内部电容充电供电。但这种模式对主机读时序要求更苛刻因为读时隙里从机释放总线后主机还要继续维持高电平给传感器供电稍有不慎就会因为供电不足导致转换失败。我做项目的时候一律采用外部供电模式省心太多。线缆方面如果传感器要装在设备外侧建议使用双绞线或者屏蔽线数据线走单独一根不要跟电机电源线、继电器控制线扎在一起。工业现场如果干扰严重I2C电平转换器加单总线也会有不小的坑最稳妥的方案是在传感器附近加一个缓冲器或者用隔离芯片不过那是另外一个话题了。我个人在实际操作中的体会是单总线协议能不能跑通七分在硬件三分在软件。你花一晚上调代码里的us参数不如花十分钟用万用表量一下DQ电平是否正常、上拉电阻是否焊上。等波形这关过了驱动代码反而是一马平川的事。最后再分享一个小技巧调试的时候先在总线上只挂一个传感器把单设备的全流程跑通再去考虑多设备搜索。一步一步来比什么问题都堆在一起排查要快得多。