ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MY18E20与DS18B20兼容吗?单总线温度传感器及MicroPython驱动详解

MY18E20与DS18B20兼容吗?单总线温度传感器及MicroPython驱动详解 很多人第一次接触数字温度传感器多半是被“一根线就能读温度”这件事吸引的。我也一样早年做单片机项目时手里握着三个引脚的防水探头只知道红接电源、黄接数据至于那根数据线上到底发生了什么完全是个黑盒。后来认真啃了一遍数据手册又写了几个平台的驱动才把那根线里的秘密梳理明白。最近不少朋友问起“MY18E20”说网上买的探头上印着这个名字能测温度也不是 DS18B20到底能不能按 DS18B20 用这篇博文就把这事一次讲透MY18E20 与 DS18B20 在电气特性、单总线协议、寄存器映射上完全兼容你可以把它理解为 DS18B20 的“同门兄弟”。我会从单总线物理层时序开始把复位、应答、读写时隙、ROM 寻址、CRC 校验讲清楚再用 MicroPython 从零写一套驱动并给出实测数据、参数选择和常见坑位。适合刚接触单总线协议的嵌入式新手也适合想在 ESP32、树莓派 Pico 上做多路温度采集的老手做参考。1. 项目从哪来MY18E20 到底是什么1.1 先说结论它和 DS18B20 是什么关系MY18E20 这个名字常见于国产防水温度探头、小型温控模块和几块钱一片的 TO-92 封装芯片上。虽然丝印不同但它的内部功能、引脚定义、命令集、暂存器布局以及最重要的单总线访问时序和经典 DS18B20 几乎一模一样。换句话说你在项目里已经写好的 DS18B20 驱动可以直接换成 MY18E20连个字母都不用改。很多朋友会担心“会不会是冒牌货时序对不上”。从我实际测试过的几批芯片来看MY18E20 的复位应答、ROM 读取、温度转换、暂存器回读行为都符合 DS18B20 数据手册描述。它大概率只是 DS18B20 的国产兼容料号或者同一款晶圆的不同封装。所以后面我会把两者当成一回事来讲解。你手里不管是 MY18E20、DS18B20还是其他打着兼容旗号的 18B20 变体都可以按这套思路处理。唯一需要留意的是不同批次芯片的转换速度可能有微小差异正式产品里最好留出足够的转换等待时间。1.2 为什么值得用 MicroPython 手写驱动MicroPython 官方库里其实已经有onewire和ds18x20这两个现成模块几行代码就能读到温度。那为什么还要自己手写一遍我主要出于三个原因。第一官方库把底层时序封装得太好新手很难理解“为什么读一个字节要循环八次”“为什么每次读写之前都要 reset”。自己写驱动等于把协议手册从头到尾翻译成代码踩过一次时序的坑以后再换任何一种单总线传感器都有底气。第二官方库返回的数据结构是固定的读取错误、CRC 校验失败、多探头扫描这些逻辑都藏在模块内部。实际项目中我经常需要在读取失败时自动重试、在温度异常时报警、在总线上挂多个探头并记录每个探头的 ROM 编号这些定制逻辑在自己写的驱动里会更顺手。第三MicroPython 的生态远不止几种官方开发板。这几年我见过不少支持 USB Host 的特殊固件也有人把 MicroPython 移植到各种小众 MCU 上。官方驱动不一定在每个平台都能直接跑但只要你理解协议在任何平台上都能用machine.Pin把时序复现出来。这也是我坚持手写底层时序的原因。2. 单总线协议到底“单”在哪2.1 一根信号线怎么同时完成发送和接收单总线1-Wire协议最特别的地方是“数据线”和“电源线”可以靠一根线加一条地线完成全部工作。MY18E20 有 VDD、DQ、GND 三个引脚正常供电模式下VDD 接 3.0~5.5VGND 接地DQ 就是数据线。DQ 上必须接一个 4.7kΩ 左右的上拉电阻到 VDD因为整个协议的基础就是“高电平靠电阻拉起来低电平靠主机或从机主动拉下去”。你可以把这条总线想象成办公室里的一排工位大家共用一根电话分机线。平时没人说话时线路处于空闲高电平谁要发言就主动把线路拉低再释放用不同的“拉低时长”表达不同意思。由于只有一根线同一时刻只能有一个设备在“拉低”否则就会冲突。这就是单总线最简单也最核心的模型。协议里另一个容易忽略的点是寄生供电。有些应用只接 DQ 和 GND 两根线靠 DQ 线上的高电平给内部电容充电这叫寄生供电方式。这种方式虽然省了一根线但供电能力弱温度转换时电流需求大必须由主机在转换期间持续拉高 DQ。我建议新手先老老实实用三线制把协议跑通再研究寄生供电否则很容易遇到“读不到温度”的怪问题。2.2 两大关键时序复位脉冲与存在检测单总线通信的第一步永远不是马上读写数据而是“握手”。主机先把 DQ 拉低至少 480μs然后释放让上拉电阻把电平拉高。传感器在检测到下降沿后会等待 15~60μs然后自己把 DQ 拉低 60~240μs这个低脉冲就是“存在脉冲”presence pulse。主机只要在正确的时间窗口里采样到低电平就知道总线上有设备。为什么复位脉冲要拉这么长因为传感器是慢速器件而且总线可能存在多个设备它们需要足够长的下降沿来同步内部状态。如果你把复位时间压得太短比如只有几十微秒传感器可能根本没反应过来握手就失败了。反过来复位脉冲也不能长到离谱标准建议上限在 960μs 以内太长会让传感器误以为进入了某种异常状态。这里有个实际经验上一版驱动里如果把复位脉冲写成time.sleep_us(600)完全没有问题但如果你把这段代码移植到某个操作系统里跑恰好被任务调度打断了复位脉冲变成了 20ms传感器就不会正常应答。所以嵌入式里做时序时必须关中断或者用硬件定时器MicroPython 里虽然没有标准做法但在 REPL 里做简单测试问题不大真正产品化就要考虑用micropython.viper或汇编优化。2.3 读写时隙用“空隙时长”编码 0 和 1复位之后总线进入“时隙”slot访问模式。每一个“位”的读写都占用一个 60~120μs 的时隙。协议规定所有通信都从主机主动拉低 DQ 开始区别只在于“拉低之后多久释放”。写“0”时主机把 DQ 拉低并保持 60~120μs 不释放。写“1”时主机只把 DQ 拉低 1~15μs然后立刻释放让上拉电阻把线拉回高电平。传感器会在时隙开始后的 15~60μs 内采样 DQ 电平采到低就是 0采到高就是 1。读数据也是类似的思路。主机把一个时隙“启动”起来先拉低至少 1μs再释放。如果传感器要返回 0它会把 DQ 拉低并保持一段时间如果要返回 1它就保持高阻态让线上电平被上拉电阻拉高。主机必须在释放后 15μs 内读取 DQ 电平太晚就会错过传感器的输出窗口。我经常用一句话总结单总线没有时钟线一切同步都靠时隙和“拉低—释放”的相对时间。你写驱动时所有延时单位都是微秒随便差个几十微秒就可能读到全 0xFF 或者乱码。3. 协议应用层拆解ROM 命令与温度转换流程3.1 一次完整的测温过程总共分几步上面讲的是物理层现在来到应用层。MY18E20 的测温流程并不复杂可以拆成两大阶段启动温度转换、读取暂存器。启动转换阶段主机先发一次复位然后发送 ROM 命令。如果总线上只有一个设备可以直接发0xCC跳过 ROMSkip ROM意思是“所有设备都听令”。接着发送0x44转换温度Convert T传感器收到后开始测量并把结果写入内部暂存器。12 位分辨率的转换时间最长需要 750ms9 位、10 位、11 位分别对应 93.75ms、187.5ms、375ms。转换期间传感器会拉低 DQ 表示“忙”但实际驱动里大多数人用简单延时等待。读取暂存器阶段主机再次复位发送跳过命令0xCC再发送0xBE读暂存器Read Scratchpad然后连续读取 9 个字节。前两个字节就是 16 位温度原始值紧接着两个字节是高温报警阈值 TH 和低温报警阈值 TL第五个字节是配置寄存器第六、第七字节保留第八字节是 CRC 校验值。温度解析的公式很简单12 位分辨率下把低字节和符号位拼成一个 16 位有符号数再除以 16就是实际摄氏度。比如原始值为0x0191十进制 401除以 16 等于 25.0625°C。如果原始值最高位是 1表示负温先转成有符号数再除 16。负温时算出来的不是直接的整数除法需要做补码处理。3.2 ROM 命令与多设备寻址跳过、匹配、搜索一片 MY18E20 出厂时内部会写入一个 64 位 ROM 唯一标识。其中第一个字节是家族码固定为0x28中间六个字节是芯片序列号最后一个是 ROM 字节的 CRC 校验。单机场景里跳过命令0xCC最省事。但总线上一旦挂了两片、三片传感器跳过命令就失效了因为所有传感器都会同时响应0x44和0xBE读回来的数据会互相叠加成乱码。这时候必须用匹配 ROM 命令0x55后面跟 8 字节目标设备的完整 ROM只有 ROM 相同的设备才会继续响应后面的0x44或0xBE。那怎么知道每片设备的 ROM 呢可以用读 ROM 命令0x33但它只适用于总线上只有一片设备的情况。多设备时需要用搜索 ROM 命令0xF0通过逐位查询的方式把总线上所有设备的 ROM 枚举出来。具体做法是把每一位分成两条分支读第一位、读其反码再根据组合判断该位是 0、1 还是有冲突再写回一个方向位有点像二叉树遍历。这个算法展开讲能单独写一篇这里先记住结论搜索 ROM 是解决多探头共线的关键而在实际工程中我会把搜索到的 ROM 固化在配置文件里避免每次开机都全总线扫描。3.3 CRC 校验在驱动里怎么落地单总线是异步串行协议没有时钟线非常容易被干扰所以 MY18E20 在每次读取暂存器后都会通过第 9 个字节把前 8 个字节的 CRC 校验值送回主机。CRC 算法采用 CRC-8/MAXIM生成多项式是x^8 x^5 x^4 1等价于十六进制0x31初始值为 0。在 MicroPython 里我更喜欢逐位法代码短、不占内存适合 MCU 场景def crc8(self, data: bytearray) - int: crc 0 for b in data: for _ in range(8): mix (crc ^ b) 0x01 crc 1 if mix: crc ^ 0x8C b 1 return crc校验时取出暂存器前 8 个字节算一遍 CRC再和读取到的第 9 个字节比较。如果相等说明数据链路基本可靠如果不相等我会直接丢弃本次结果并安排重读而不是把可能错误的数据用于控制逻辑。想想看温度控制里如果偶尔跳出一个 200°C 的假数据执行器会做什么动作所以 CRC 绝不能省。4. MicroPython 驱动实现从寄存器到可调用类4.1 驱动整体结构分层我写单总线驱动时习惯分成两层。底层叫总线层只负责最原始的时序操作复位、写一个 bit、读一个 bit、组合出读写字节。上层叫设备层负责组织命令比如发送0xCC、0x44、0xBE解析暂存器校验 CRC以及管理多探头。这种分层的好处是职责单一。如果你的项目今天用 MY18E20明天换成别的单总线传感器只要它的命令不一样就只需要改设备层如果换了一块主控板引脚模型和延时精度变了也只需要改总线层。我在 ESP32 和树莓派 Pico 上跑同一套总线层代码除了引脚号不一样其余逻辑原样复用。底层代码里有一个细节上电时要把 DQ 初始化为开漏输出并默认输出 1让总线保持空闲高电平。这样设备层在调用复位、读写命令时不会因为 MCU 引脚模式不一致而出现电平冲突。4.2 底层时序代码复位、写位、读位先看底层的复位与读写实现。下面这套代码基于machine.Pin的OPEN_DRAIN模式省掉了频繁切换输入输出方向的麻烦from machine import Pin import time class OneWireBus: def __init__(self, pin_id): self.pin Pin(pin_id, Pin.OPEN_DRAIN) self.pin(1) # idle high def reset(self): p self.pin p(0) # 主机拉低 time.sleep_us(500) # 复位脉冲取480~960中间值 p(1) # 释放总线 time.sleep_us(30) # 等从机应答 presence (p.value() 0) time.sleep_us(250) # 等存在脉冲结束 return presence def write_bit(self, bit): p self.pin if bit: p(0) time.sleep_us(5) p(1) time.sleep_us(60) else: p(0) time.sleep_us(60) p(1) time.sleep_us(5) def read_bit(self): p self.pin p(0) time.sleep_us(2) p(1) time.sleep_us(5) bit p.value() time.sleep_us(50) return bit def write_byte(self, value): for i in range(8): self.write_bit((value i) 0x01) def read_byte(self): value 0 for i in range(8): value | (self.read_bit() i) return value这些代码的核心是把每个时隙控制在 60~120μs 之内。写 1 时只拉低 5μs 就释放写 0 时持续拉低 60μs。读数据时主机拉低 2μs、释放 5μs 后采样正好落在 15μs 的采样窗口里。有一点必须提醒MicroPython 是解释执行的time.sleep_us本身有误差GPIO 翻转也需要时间。实测在 ESP32 的 CPython 解释器下这套代码可以稳定驱动单片防水探头如果你遇到时序不稳定最直接的办法是把最关键的读写函数用micropython.viper装饰器改写把延时替换成更底层的高精度循环。原理上不变但微秒级抖动会明显变小。4.3 设备层实现跳过 ROM、温度转换、读取暂存器底层写好后设备层就很薄了。我把 MY18E20 封装成一个类默认支持单设备跳过 ROM 模式也可以传入 ROM 做多设备寻址class MY18E20: def __init__(self, bus: OneWireBus, rom: bytes None): self.bus bus self.rom rom def _write_rom_command(self, cmd): if self.rom is None: self.bus.write_byte(0xCC) else: self.bus.write_byte(0x55) for b in self.rom: self.bus.write_byte(b) self.bus.write_byte(cmd) def convert_temp(self): self.bus.reset() self._write_rom_command(0x44) def read_temp(self): self.bus.reset() self._write_rom_command(0xBE) data bytearray(self.bus.read_byte() for _ in range(9)) if self.crc8(data[:8]) ! data[8]: return None raw data[0] | (data[1] 8) if raw 0x8000: raw - 65536 return raw / 16.0调用方式很直白先创建总线对象再创建设备对象循环执行convert_temp()并等待至少 750ms再调用read_temp()读取温度。如果返回None说明 CRC 校验失败上层可以选择重试或者报警。多设备模式下只要在创建MY18E20对象时把每个探头的 8 字节 ROM 传进去驱动就会自动走匹配 ROM 命令。实测在一条总线上挂 5 个探头每个探头轮流转换、读取温度数据非常稳定。5. 实测流程与参数校准5.1 硬件接线注意事项先说最容易被坑的接口。防水探头的三根线并不是所有厂家都用同一种颜色但绝大多数遵循“红VDD、黑GND、黄DQ数据”。接线时千万不要把黄线当地线也别把红色和黄色接反否则传感器直接不工作严重时还可能烧芯片。DQ 引脚到 VDD 之间必须接上拉电阻。典型值是 4.7kΩ线路较短时 4.7kΩ 很稳如果总线超过 2 米可以换成 2.2kΩ让上拉能力更强。注意如果总线上并联很多传感器每个传感器电路都想拉低总线总线上拉电阻就需要根据设备数量适当调小否则可能出现低电平抬升导致传感器判断错误。实际测试中我用 ESP32-S3 开发板GPIO4 接 DQ黄色线通过面包板短接数据线长度控制在 1 米以内室温 26°C 环境下读数稳定在 26.1°C 左右。另一个实验用树莓派 PicoGPIO22 接数据线同样 4.7kΩ 上拉电阻读取结果相差不到 0.1°C。5.2 用官方库做对照实验自己写的驱动怎么验证对不对最简单的方法是拿 MicroPython 官方ds18x20模块做对照。官方库的读取流程如下from machine import Pin import onewire, ds18x20, time ow onewire.OneWire(Pin(4)) roms ow.scan() ds ds18x20.DS18X20(ow) ds.convert_temp() time.sleep_ms(750) for rom in roms: print(ds.read_temp(rom))在同一个硬件上运行官方库再运行我们自己写的类两边读数基本一致。这类对比实验很有价值它能帮你快速定位问题到底出在硬件接线还是驱动时序。如果官方库能读到、自己驱动读不到就先检查自己的代码时序如果官方库也读不到八成是接线或供电问题。我实测过一组数据官方库读到 26.1875°C自写驱动读到 26.1875°C完全一致连续读 100 次自写驱动的 CRC 失败次数为零。这说明手写驱动的底层时序是达标。5.3 分辨率与精度分析MY18E20 的分辨率可通过配置寄存器设置为 9、10、11、12 位。12 位分辨率下最低有效位代表 0.0625°C这也是驱动里一律“除以 16”的原因。各档位换算关系如下表分辨率转换时间典型最长温度增量9 bit93.75 ms0.5°C10 bit187.5 ms0.25°C11 bit375 ms0.125°C12 bit750 ms0.0625°C要注意“分辨率”不等于“精度”。MY18E20 这类传感器的测量精度在 -10°C 到 85°C 范围内典型为 ±0.5°C。所以你看到读数 26.1875°C不代表真实温度就是 26.1875°C很可能实际是 26.4°C。我在项目里通常会做一次单点校准把传感器和标准温度计同时放在 25°C 恒温水中记录偏移量再在软件里做补偿。如果想让转换更快可以把分辨率配成 9 位转换时间不到 100ms。但代价是每档跳到 0.5°C温控精度要求高时不建议这么干。配置分辨率需要写暂存器命令0x4E写入 TH、TL 和配置字节再用0x48保存到 EEPROM。这套操作属于进阶功能后面扩展部分再细说。6. 常见问题与排查技巧实录6.1 总线没有任何回应reset 返回 False最典型的症状是presence永远为 False或者ow.scan()返回空列表。出现这种情况先别急着改代码按顺序排查硬件。第一检查上电和接线。用万用表量一下 VDD 和 GND确认电压在 3.0~5.5V再量一下 DQ 对地电压正常空闲时应接近 VDD因为上拉电阻把它拉高了。如果 DQ 对地接近 0V要么数据线被干扰拉低要么传感器没接入。第二检查上拉电阻。有些面包板接线看起来没问题但 4.7kΩ 电阻插错孔位等于没接。单总线没有上拉电阻基本无法工作。第三检查引脚号。MicroPython 的引脚编号和开发板丝印不一定一致比如有些 ESP32 板子上标注的 D4 实际对应 GPIO4有些却对应 GPIO13。拿官方库先做一次ow.scan()如果官方库能扫到说明引脚是对的。6.2 读回来全是 0xFF 或 0x00如果复位成功但读暂存器返回的 9 个字节全是0xFF说明总线被拉高了但没有任何传感器发送有效数据。这往往是“写 1 时拉的太短”或“读采样太慢”导致传感器输出窗口被错过。我遇到过好几次代码里写 1 时只拉低了 1μs释放后上拉还没完全建立传感器已经采样结束了读到的自然不是有效逻辑。如果读回全是0x00通常是总线被某个设备持续拉低比如数据线对地短路、传感器损坏、或者 DQ 和 GND 接反了。还有一种情况是写 0 时主机拉低时间太长总线来不及回到高电平下一个时隙就开始了导致传感器误判。碰到这类问题我建议把读写时序打印出来用逻辑分析仪或者示波器量 DQ 波形对比手册里的时隙图。没有示波器的话可以把time.sleep_us的参数左右调整 10~20μs 做试探多数情况下能找到一个稳定区间。6.3 温度读数偶尔跳变CRC 报错CRC 报错说明数据确实坏了不是解析问题。最常见的原因是干扰。单总线靠一根线传数据长线就像一根天线。如果你的传感器数据线靠近继电器、电机、电源线或者线缆没有屏蔽非常容易出现偶发性 CRC 错误。我处理这类问题有一套组合拳第一缩短数据线长度尽量控制在 2 米以内第二使用双绞线或屏蔽线屏蔽层单端接地第三在 DQ 对 GND 之间并联一个 0.1μF 电容滤掉高频噪声第四软件层面做好重读机制发现 CRC 错误就等 100ms 再重读连续三次失败才报错。还有一个容易被忽略的因素是供电波动。传感器转换温度的 750ms 里内部电流消耗突然增大如果供电电源纹波大转换结果会异常。我在实验里给传感器单独做了一个 100nF 10μF 的去耦电容CRC 错误率明显下降。6.4 多传感器共线怎么保证每次读到正确的探头总线上挂多个探头时最常见的问题是第一片设备读到的温度正常后面设备读到的温度一样或直接报错。原因多半是只用了跳过 ROM 命令所有设备都在响应同一波读暂存器命令总线数据当然冲突。解决办法是改用匹配 ROM。第一步先用官方库ow.scan()拿到总线上全部 ROM第二步把每个 ROM 固化到配置里第三步每次读取指定探头时先复位再发0x55匹配 ROM命令后面跟上该探头完整的 8 字节 ROM。这样即使总线上挂了 10 个探头主机也知道正在跟谁说话。调试多设备时我踩过一个特别隐蔽的坑有些劣质防水探头的 ROM 虽然不同但读回的温度永远是同一个值。仔细排查后发现其实总线上只识别到了同一个设备另一个探头因为上拉电阻、线序问题没有真正接入。所以多探头项目上线前最好先打印一遍所有 ROM确认每个探头都被识别到。7. 实操心得与后续扩展7.1 我实际使用下来的稳定参数根据不同项目实测我总结了一套比较稳的参数。上拉电阻用 4.7kΩ数据线长度控制在 1 米以内复位脉冲 500μs写 1 拉低 5μs写 0 拉低 60μs读时隙拉低 2μs、释放后 5μs 采样。在 ESP32 和 Pico 上这套参数无论单设备还是多设备都稳定。如果非要长距离布线我的建议是别死磕单总线改用 RS485 转接方案或者每路单独用一根 GPIO 接一个传感器从软件上规避时序问题。单总线优势是省线但不是万金油距离超过 5 米后问题会指数级增加。还有一点每次温度转换后别着急立刻读暂存器。12 位分辨率严格等 750ms但我在工程里会等到 800ms留出余量。读取失败时不要在同一毫秒内无限重试加一个 100ms 的退避时间给总线和传感器一个恢复期。7.2 这个驱动还能怎么扩展目前这套驱动只覆盖了最基本的测温功能。你可以继续加入几类扩展。第一报警阈值功能。MY18E20 支持 TH、TL 两个报警阈值你可以设置上下限然后使用报警搜索命令避免每次全量读取所有探头只在温度越限时快速定位异常设备。这个功能用在冷链监控上很实用。第二分辨率动态切换。需要快速响应时切到 9 位需要精确读数时切到 12 位。切换时需要写暂存器、保存 EEPROM这个流程我已经在 5.3 节提到代码上只需要增加write_scratchpad和copy_scratchpad两个方法。第三固件层面还可以把读取逻辑丢到后台线程或者结合 MQTT 上报到服务器。MicroPython 的uasyncio可以做异步调度传感器转换的 750ms 等待完全能用来干别的事。这几年 MicroPython 固件版本迭代很快很多开发板还加入了 USB Host 支持下载固件后可以直接外接 USB 转 1-Wire 适配器玩法比想象中多。最后一句经验之谈单总线协议的坑十个里有八个是时序问题但真正定位的时候先从硬件开始查。接线、上拉、供电这三样永远比代码更值得怀疑。把底层时序吃透之后再回头用官方库你会发现自己不再只是“能跑”而是真的知道它在跑什么。
返回列表