深度干货向:MY18E20 单总线协议深度解析与MicroPython驱动编写

MY18E20 温度读取

MY18E20 是一款高精度可编程的数字单总线温度测量芯片:

  • 其测温范围为-55°C 到 +125°C、最高分辨率 0.015°C
  • 在-10°C 到 +85°C 范围内最大差为 ±0.5°C,在极端工作温度的最大误差小于 ±1.5°C
  • 芯片内置 80bit 非易失性存储单元,用于保存用户自定义信息, 如高低温报警阈值、传感器节点编号、位置信息、温度校准信息等

微处理器可以通过一个 GPIO 端口,依据单总线协议的软件指令,读写芯片的控制寄存器设置测温精度、报警阈值等配置,并周期采集数字温度信息。

1280X1280 (5).PNG

1280X1280.PNG

1.MY18E20 传感器通信协议解析

需要注意的是,数据手册中关于单总线通信的一些名词与我们使用的名词有所不同,但指代对象相同:

  • ​主机/设备/控制器:​指在单总线通信中发起通信的单片机。
  • ​从机/芯片/传感器:​指在单总线通信中响应通信的传感器。
  • ​读时隙:​指主机发送读信号的通信流程。
  • ​写时隙:​指主机发送写信号的通信流程。
  • ​复位信号/呼叫信号:​主机发起,拉低总线一定时间后释放等待从机响应。
  • ​存在信号/响应信号:​主机发起呼叫信号后,从机检测到总线上升沿拉低总线一段时间。

1.1 MY18E20 传感器的结构和工作原理

1280X1280 (1).PNG

MY18E20 传感器主要由以下几个部分组成:

  • ​EEPROM:​主要存储系统参数、64 位 ROM ID 和 10 字节用户存储,64 位 ROM 存储了器件的唯一 ID 序列码。
    单片机 (主机)通过每个器件的唯一 64 位编码识别并寻址总线上的器件。因为每个器件都有唯一的编码,理论上挂在总线上并可以被寻址的设备数量是无限的。

1280X1280 (2).PNG

  • ​8 位循环冗余校验产生器:​循环冗余校验字节是 MY18E20 的 64 位 ROM 编码的一部分,位于暂存器的第 9 个字节。ROM 编码循环冗余校验是从 ROM 编码的前 56 位计算得来,存储在 ROM 的最高位。暂存器的循环冗余校验码是在暂存器内计算得来,因此随暂存器内数据改变而改变。循环冗余校验码给主机从暂存器读取数据提供了一个数据校验方法。要验证数据读取是否正确,主机必须自己通过接收到的数据进行计算并将此数据与 ROM 里的循环冗余校验码比较(来自于 ROM 读操作)或者与暂存器里的循环冗余校验码比较(来自于暂存器读操作)。如果计算的循环冗余校验码与读取的吻合,数据就是正确无误地接收了。

  • ​温度传感器和 ADC 单元:​进行温度测量和数据转换,产生的温度数据被存储在暂存器的温度寄存器中。

  • ​数字控制逻辑:​根据接收的 ROM 指令和功能指令进行对应操作。

  • ​暂存器:​暂存器包含了两个字节的温度寄存器存储来自于温度传感器的数字输出。
    另外,暂存器提供了一高一低两个报警触发阈值寄存器(TH 和 TL)。配置寄存器允许用户设定温度数字转换的分辨率为 9、10、11、12 位(分别对应着温度的分辨率为:0.5°C,0.25°C,0.125°C,0.0625°C,默认为 12 位分辨率)。提供另外的数据空间供用户使用。

    **暂存器中关于配置的相关数据可存入非易失性存储 EEPROM,芯片掉电时数据不会丢失,在下一次设备上电时,EEPROM 的配置相关数据会自动写入暂存器。**
    
  • ​单总线接口:​将单总线接口接收的命令数据进行转换发送到数字控制逻辑单元。

  • ​电源管理:​将输入电压进行转换和分流给各个系统进行供电。

MY18E20 的工作步骤可以分为三步:

  1. ​初始化 MY18E20:​MY18E20 芯片上电初始化,主机发送呼叫信号,从机 MY18E20 响应
  2. ​主机发送 ROM 指令(跟随任意必需的数据交换):​访问每个 DS18B20,搜索 64 位序列号,读取匹配的序列号值,然后匹配对应的 DS18B20。如果我们仅仅使用单个 DS18B20,可以直接跳过 ROM 指令,而跳过 ROM 指令的字节是 0xCC。

这里,跟随任意必需的数据交换包括主机复位总线、发送功能指令、读取数据等操作。

  1. ​执行 MY18E20 功能指令(跟随任意必需的数据交换):​包括读取温度值、写入报警触发阈值、配置温度测量精度等命令

当总线主机检测到从机响应信号时,就可以发送 ROM 指令。ROM 指令共有 5 条,每个 8 位长。需要注意的是,在主机发送 MY18E20 的功能指令之前必须发送一个 ROM 指令:

ScreenShot_2026-06-09_213235_624.png

当总线主机使用一个 ROM 指令寻址一个它希望与之通讯的 MY18E20 之后,主机可以发布 MY18E20 的功能指令之一。这些指令允许主机进行数据读写:包括读取温度值、写入报警触发阈值、配置温度测量精度等命令。

ScreenShot_2026-06-09_213252_608.png

129c0c01-8591-4b58-8d11-93c0a6c4feb2.png

这里,Copy Scratchpad[48h] 指令和 Recall E2[B8h]指令主要用于以下场景:

当我们设定 TH 报警阈值上限、TL 报警阈值下限和温度转换精度到暂存器时,我们会希望当设备停机掉电后保存我们设定的数据,并在下一次设备上电后读取上一次配置数据到暂存器中。此时,要把 TH、TL 和配置数据从暂存器传输到 E2PROM,主机必须发出 CopyScratchpad[48h] 指令。E2PROM 寄存器中的数据掉电会保持,上电时会自动加载到相应的暂存器位置。数据也可以通过 RecallE2[B8h]指令随时重新加载。主机可以在 RecallE2[B8h]指令随后发送读时隙,然后芯片会返回状态,如果重新加载还在进行中会返回 0,如果已经完成会返回 1。

ROM 指令流程图如下:

ec114ef9-d76d-4d18-a429-2980d0b96e2b.png

功能指令流程图如下:

3bb93ca0-bf31-4717-b534-04ffef8490cb.png

1.2 MY18E20 的存储器组织

MY18E20 的存储器包含一个 SRAM 暂存器以及非易失性存储 E2PROM 寄存器,MY18E20 的存储组织如下图所示:

008f744d-f351-41c4-9301-5c4c09b2e21f.png

其中:

  • ​字节 0 和字节 1 是只读的:​用于存储转换后的温度数据
  • ​字节 2 和字节 3 用于访问 TH 和 TL 寄存器:​存储报警的温度上下限
  • ​字节 4 包含配置寄存器数据:​用于配置温度转换精度
  • ​字节 5、6 和 7 可以读写:​提供数据空间供用户使用
  • ​字节 8 为只读寄存器:​字节 0 到 7 产生的循环冗余校验码

数据可以通过 WriteScratchpad[4Eh]WriteUser [66h] 指令写入到字节 2,3,4,5,6 和 7 中。数据传输必须始于字节 2 的最低位(即主机发送数据从 LSB 到 MSB)。为了验证数据完整性,可以在数据写操作之后读取暂存器(通过 Read Scratchpad [BEh] 指令)。读取暂存器的时候,单总线的数据传输始于字节 0 的最低位(即从机发送数据从 LSB 到 MSB)。

1.3 MY18E20 传感器应用电路

MY18E20 通过 VDD 引脚由外部电源供电,如下图所示。这种模式的优点是不需要 MOSFET 上拉,而且单总线在温度转换过程中可以任意进行其他操作。

81eef14d-205d-4e76-a65a-8949c61ecd9d.png

691665dc-0bf4-4fce-839e-6aa4df81c53c.png

ecfcaffc-d7f7-42bf-8e80-82aa8976d0b6.png

1.4 单总线通信时序和电气特性

1.4.1 主机发送呼叫信号和从机应答

d464d869-42a4-478f-ae20-21e033b92ba7.png

3235232d-a1a2-4890-aecf-211dbf2ce30d.png

1.4.2 主机读写数据位

fe9310de-1142-4419-a107-c96d9e254d7f.png

2e419d98-e2a5-431b-bd5c-905fe670e905.png

9564a366-6cd9-483a-9a37-de8239695db1.png

85cb15e8-51a3-434a-8225-2ce7978accf9.png

e13e098a-44fe-46af-bb79-7cad5478b017.png

37a89479-3cb8-4c1b-ab7b-4783b6c14719.png

96397625-c3ea-4f8a-a75d-47352e819e8a.png

1.5 直接温度测量功能

MY18E20采用 16 位补码的形式来存储温度数据,温度是摄氏度。当温度转换命令发布后,经转换所得的温度值以二字节补码形式存放在暂存器的第 0 和第 1 个字节。

高字节的五个 S 为符号位,温度为正值时 S=1,温度为负值时 S=0。

剩下的 11 位为温度数据位:

  • ​对于 12 位分辨率:​所有位全部有效
  • ​对于 11 位分辨率:​位 0(bit0)无定义
  • ​对于 10 位分辨率:​位 0 和位 1 无定义
  • ​对于 9 位分辨率:​位 0、位 1 和位 2 无定义

da8ac65b-f92b-4db8-ab64-a3e9a9255268.png

049ab077-ee89-4fcf-be70-79685dd01f8d.png

需要注意的是,温度以大端序进行发送,即高位 MS 字节在前而低位 LS 字节在后。

4289ac7f-797c-40aa-a455-d60fc0854c20.png

温度计算方法如下:

  • ​当五个符号位 S=0 时,温度为正值:​直接将后面的 11 位二进制转换为十进制,再乘以 0.0625(12 位分辨率),就可以得到温度值
  • ​当五个符号位 S=1 时,温度为负值:​先将后面的 11 位二进制补码变为原码(符号位不变,数值位取反后加 1),再计算十进制值。再乘以 0.0625(12 位分辨率),就可以得到温度值

2432dc72-c953-4c98-b4d0-29e8e7fb6079.png

以上表为例,讲解温度计算:

  • +125℃ 的数字输出 07D0(00000111 11010000),转换成 10 进制是 2000,对应摄氏度:0.0625x2000=125°C
  • -55℃ 的数字输出为 FC90,首先取反,然后 +1,转换成原码为:11111011 01101111,数值位转换成 10 进制是 870,对应摄氏度:-0.0625x870=-55°C

1.6 温度采集分辨率配置

在配置寄存器中,我们可以通过 R0 和 R1 设置 ME18E20 的转换分辨率,ME18E20 在上电后默认 R0=1 和 R1=1(12 分辨率),寄存器中的第 7 位和第 0 位到 4 位保留给设备内部使用。

bc5727ef-127d-4962-80fd-a3c2f97dc89f.png

1.7 报警功能

MY18E20 完成一次温度转换后,将该温度值和用户定义的二进制补码报警触发值比较,该值存储在 TH 和 TL 寄存器中,TH 和 TL 寄存器是非易失性的(E2PROM),所以当设备掉电时数据会保持。TH 和 TL 可以通过暂存器的第 2 和第 3 字节访问。

e31e0679-c602-44a0-b74f-d457d9076942.png

由于 THTL 是 8 位寄存器,因此在温度比较中只用到 11 到 4 位。如果测量到的温度小于等于 TL 或大于等于 TH,则报警条件成立,芯片内部会设立一个标志位。该标志位每次温度测量都会被更新,所以如果报警条件不再满足,标志位就会在下次温度转换之后被关闭。

主机可以通过发布一个 AlarmSearch[ECh] 指令来检查所有挂在总线上的温度芯片的标志位状况。任何设立了标志位的芯片都会响应该指令,这样主机可以知道具体哪一个芯片进入了报警条件。如果报警条件成立并且 TH 或者 TL 设置被更改,则应该再进行一次温度转换来验证报警条件。

1.8 循环冗余校验码生成

循环冗余校验字节是 MY18E20 的 64 位 ROM 编码的一部分,位于暂存器的第 9 个字节。ROM 编码循环冗余校验是从 ROM 编码的前 56 位计算得来,存储在 ROM 的最高位。​暂存器的循环冗余校验码是在暂存器内计算得来,因此随暂存器内数据改变而改变。​循环冗余校验码给主机从暂存器读取数据提供了一个数据校验方法。要验证数据读取是否正确,主机必须自己通过接收到的数据进行计算并将此数据与 ROM 里的循环冗余校验码比较(来自于 ROM 读操作)或者与暂存器里的循环冗余校验码比较(来自于暂存器读操作)。如果计算的循环冗余校验码与读取的吻合,数据就是正确无误地接收了。循环冗余校验的比较以及是否继续操作都完全取决于主机。如果芯片的循环冗余校验(ROM 或暂存器)与总线主机计算的值不匹配,芯片内没有电路阻止指令进一步执行。

CRC 校验码计算方式如下:

06c2e36f-d241-4086-9008-0e18479d95a7.png

41f348ca-c3fa-4e70-9026-9279d5c5c829.png

ScreenShot_2026-06-09_213445_536.png

2. MY18E20 传感器驱动代码

这里,由于 MY18E20 传感器工作流程与 DS18B20 传感器相兼容,我们直接使用 DS18B20 传感器驱动代码即可。我们首先定义一个 DS18X20 类(DS18 系列温度传感器有许多不同型号,通信流程大体相似),这个类的主要功能如下:

  • ​初始化:​初始化 DS18X20 类时,需要传入一个 OneWire 类的实例;通过组合关系,DS18X20 类可以使用 OneWire 类的方法进行单总线通信。
  • ​设置供电模式:​支持两种供电模式,包括独立供电和寄生供电,寄生供电模式下,需要通过 powerpin 引脚为传感器供电。
  • ​扫描传感器:​扫描单总线上的所有 DS18X20 传感器,并返回它们的 ROM 地址列表。
  • ​启动温度转换:​启动温度转换操作,通过发送温度转换命令 CMD_CONVERT 实现。
  • ​​ 读取和写入暂存器:​读取和写入传感器的暂存器数据,包括温度值、分辨率等配置信息。
  • ​计算和返回温度值:​读取传感器的温度值,并支持摄氏度、华氏度和开氏度三种单位,根据传感器的型号(DS18B20DS18S20 等)进行不同的温度计算。
  • ​设置温度分辨率:​设置温度转换的分辨率,支持 9 位到 12 位之间的设置。

这里,DS18X20 类和 OneWire 类采用组合关系,而非继承关系:

19c2b752-8917-418c-a29b-60bff9f4b4e1.png

主要是基于以下几点考虑:

  1. 单一职责原则:
    1. OneWire 类专注于单总线通信的底层实现,提供了一系列读写数据、复位等基本操作。
    2. DS18X20 类则专注于针对 DS18X20 温度传感器的上层逻辑,如温度转换、读取温度等,并且封装了 DS18X20 传感器的特定命令(如温度转换命令 CMD_CONVERT、读取暂存器命令 CMD_RDSCRATCH 等)。
    3. 这样的设计遵循了单一职责原则,使得两个类分工明确,责任清晰。
  2. 可复用性:
    1. OneWire 类是一个通用的单总线通信类,可以被多种设备类复用,比如其他类型的 1-Wire 设备。
    2. 如果采用继承关系,DS18X20 类就会与 OneWire 类过于耦合,降低了 OneWire 类的可复用性
  3. 灵活性:
    1. 在组合关系中,DS18X20 类可以自由选择使用哪个 OneWire 类的实例,根据实际需求进行配置。
    2. 而如果采用继承关系,DS18X20 类就必须继承 OneWire 类,无法自由选择使用哪个单总线通信类。
  4. 可测试性:
    1. 在组合关系中,DS18X20 类可以独立于 OneWire 类进行测试,有利于提高测试的覆盖率和可靠性。
    2. 如果采用继承关系,DS18X20 类的相关测试就会与 OneWire 类的实现细节耦合在一起,降低了测试的独立性。

这里,我们首先定义 DS18X20 传感器使用的功能指令常量和初始化方法:

# 定义DS18X20温度传感器类
class DS18X20:
    """
​    DS18X20 温度传感器类,用于与 DS18B20、DS18S20 等单总线温度传感器进行通信。
​    该类封装了温度传感器的功能,包括温度转换、读取温度、设置分辨率等。

​    Attributes:
​        ow (OneWire): 单总线通信类的实例。
​        buf (bytearray): 用于存储暂存器数据的缓冲区(9 字节)。
​        config (bytearray): 用于存储用户配置数据的缓冲区(3 字节)。
​        power (int): 电源模式,1 为独立供电,0 为寄生供电。
​        powerpin (machine.Pin): 供电引脚对象(用于寄生供电模式)。

​    Methods:
​        powermode(powerpin: machine.Pin) -> int: 设置 DS18X20 的供电模式。
​        scan() -> list[bytearray]: 扫描单总线上的 DS18X20 传感器,返回 ROM 地址列表。
​        convert_temp(rom: bytearray) -> None: 启动传感器温度转换。
​        read_scratch(rom: bytearray) -> bytearray: 读取传感器暂存器数据。
​        write_scratch(rom: bytearray, buf: bytearray) -> None: 写入传感器暂存器数据。
​        read_temp(rom: bytearray) -> float: 读取传感器温度。
​        resolution(rom: bytearray, bits: int) -> int: 设置或读取传感器温度转换分辨率。
​        fahrenheit(celsius: float) -> float: 将摄氏度转换为华氏度。
​        kelvin(celsius: float) -> float: 将摄氏度转换为开氏度。
​    """

​    ​# DS18X20 功能命令
    CMD_CONVERT     = const(0x44)   # 转换温度命令
    CMD_RDSCRATCH   = const(0xbe)   # 读暂存器命令
    CMD_WRSCRATCH   = const(0x4e)   # 写暂存器命令
    CMD_RDPOWER     = const(0xb4)   # 读电源命令
    CMD_COPYSCRATCH = const(0x48)   # 拷贝暂存器命令

    # 上拉电阻控制
    PULLUP_ON       = const(1)
    PULLUP_OFF      = const(0)

    def __init__(self, onewire: OneWire) -> None:
        """
​        初始化 DS18X20 类,传入使用的单总线通信类。

​        Args:
​            onewire (OneWire): 单总线通信类的实例。
​        """
​        ​self.ow = onewire
        # 存储暂存器的9个字节数据
        self.buf = bytearray(9)
        # 存储用户配置的3个字节数据
        self.config = bytearray(3)
        # 默认独立供电
        self.power = 1
        self.powerpin = None

然后定义了用于设置 DS18X20 温度传感器的供电模式的方法:

def powermode(self, powerpin: Pin = None) -> int:
    """
​    设置 DS18X20 的供电模式。

​    Args:
​        powerpin (machine.Pin): 供电引脚对象(用于寄生供电模式)。如果为 None,则使用默认模式。

​    Returns:
​        int: 电源模式,1 为独立供电,0 为寄生供电。
​    """
​    ​# 如果已经设置了 powerpin,则将其拉低,关闭上拉电阻
    if self.powerpin is not None:
        self.powerpin(DS18X20.PULLUP_OFF)

    # 发送CMD_SKIPROM命令
    self.ow.writebyte(OneWire.CMD_SKIPROM)
    # 发送读取电源模式命令
    self.ow.writebyte(DS18X20.CMD_RDPOWER)
    # 读取电源模式
    self.power = self.ow.readbit()

    # 读取完毕后,将powerpin设置为推挽输出模式,拉低
    if powerpin is not None:
        assert type(powerpin) is Pin, "powerpin must be a Pin object"
        self.powerpin = powerpin
        self.powerpin.init(mode=Pin.OUT, value=0)

    # 返回电源模式
    return self.power

dc08d2ad-89d3-4c82-a38e-998e0f55376e.png

可以看到,其主要实现就是靠 OneWire 实例进行指令的收发。

同时,定义了 scan() 方法用于扫描单总线上连接的 DS18X20 温度传感器,并返回它们的 ROM 地址列表,这个列表可以在后续的操作中使用,比如读取温度值或设置传感器参数。

17a46640-eee9-43bc-86bc-6aef0df8717e.png

通过检查 ROM 地址的前几个字节,可以识别出 DS18X20 系列的温度传感器,并将其与其他可能连接在总线上的设备区分开来。

def scan(self) -> list[bytearray]:
    """
​    扫描单总线上的 DS18X20 传感器,返回 ROM 地址列表。

​    Args:
​        None

​    Returns:
​        list[bytearray]: 传感器 ROM 列表,每个 ROM 为 8 字节的 bytearray。
​    """
​    ​if self.powerpin is not None:
        self.powerpin(DS18X20.PULLUP_OFF)

    # 通过单总线扫描得到的ROM列表中筛选出那些以0x10、0x22或0x28开头的ROM
    return [rom for rom in self.ow.scan() if rom[0] in (0x10, 0x22, 0x28)]

DS18X20 类的核心方法是 convert_temp() 方法,用于启动 DS18X20 温度传感器的温度转换操作。

需要注意的是,调用这个方法只是启动了温度转换操作,并不会直接返回温度值。要获取转换完成后的温度值,需要调用read_temp()方法。

def convert_temp(self, rom: bytearray = None) -> None:
    """
​    启动传感器温度转换。

​    Args:
​        rom (bytearray): 目标温度传感器的 ROM 地址。如果为 None,则对所有传感器广播。

​    Returns:
​        None
​    """
​    ​if self.powerpin is not None:
        self.powerpin(DS18X20.PULLUP_OFF)
    # 主机重启总线
    self.ow.reset()
    # 当rom不为空时,则发送CMD_SKIPROM命令
    # 在总线上只有一个温度传感器时,可以发送CMD_SKIPROM命令
    if rom is None:
        self.ow.writebyte(OneWire.CMD_SKIPROM)
    else:
        # 根据给定的ROM地址选择对应传感器
        self.ow.select_rom(rom)
    # 写入转换温度命令
    self.ow.writebyte(DS18X20.CMD_CONVERT, self.powerpin)

其具体流程如下图所示:

79af3a11-197a-44b8-ae65-5420e39ff086.png

然后,我们定义了读写暂存器的方法,暂存器中存储了温度数据、用户自定义数据和相关配置数据(温度转换分辨率、报警上下限阈值等):

def read_scratch(self, rom: bytearray) -> bytearray:
    """
​    读取传感器暂存器数据。

​    Args:
​        rom (bytearray): 目标温度传感器的 ROM 地址。

​    Returns:
​        bytearray: 暂存器数据(9 字节)。

​    Raises:
​        AssertionError: 如果 CRC 校验失败。
​    """
​    ​if self.powerpin is not None:
        self.powerpin(DS18X20.PULLUP_OFF)
    # 主机重启总线
    self.ow.reset()
    # 根据给定的ROM地址选择对应传感器
    self.ow.select_rom(rom)
    # 写入CMD_RDSCRATCH命令
    self.ow.writebyte(DS18X20.CMD_RDSCRATCH)
    # 读取暂存器数据,保存到buf中
    self.ow.readinto(self.buf)
    # 进行CRC校验
    assert self.ow.crc8(self.buf) == 0, 'CRC error'
    return self.buf

def write_scratch(self, rom: bytearray, buf: bytearray) -> None:
    """
​    写入传感器暂存器数据。

​    Args:
​        rom (bytearray): 目标温度传感器的 ROM 地址。
​        buf (bytearray): 写入暂存器的数据(9 字节)。

​    Returns:
​        None
​    """
​    ​if self.powerpin is not None:
        self.powerpin(DS18X20.PULLUP_OFF)
    # 主机重启总线
    self.ow.reset()
    # 根据给定的ROM地址选择对应传感器
    self.ow.select_rom(rom)
    # 写入CMD_WRSCRATCH命令
    self.ow.writebyte(DS18X20.CMD_WRSCRATCH)
    # 写入暂存器数据
    self.ow.write(buf)

需要注意的是,在读写暂存器数据之前,都会先重置单总线,并根据给定的 ROM 地址选择对应的传感器:

f47655c4-51ba-476c-acae-b0640e7f0e5c.png

然后,我们定义了读取温度数据的 read_temp() 方法,主要通过读写暂存器来实现,需要注意的是,该方法还会进行 CRC 校验,以确保读取的数据是完整可靠的,如果 CRC 校验失败,则会抛出 AssertionError 异常,并返回 None。

def read_temp(self, rom: bytearray) -> float:
    """
​    读取传感器温度。

​    Args:
​        rom (bytearray): 目标温度传感器的 ROM 地址。

​    Returns:
​        float: 温度值(摄氏度)。如果读取失败,返回 None。
​    """
​    ​try:
        # 读取暂存器数据
        buf = self.read_scratch(rom)
        # 当rom中第一个数据为0x10时,为另一个型号的DS18x20传感器
        if rom[0] == 0x10:
            # 如果温度为负值
            if buf[1]:
                t = buf[0] >> 1 | 0x80
                t = -((~t + 1) & 0xff)
            # 如果温度为正数
            else:
                t = buf[0] >> 1
            # 计算温度值,包括小数部分
            return t - 0.25 + (buf[7] - buf[6]) / buf[7]
        # 当rom中第一个数据为0x22、0x28时,为DS18B20传感器
        elif rom[0] in (0x22, 0x28):
            # 从缓冲区中读取两个字节,将它们组合成一个16位的无符号整数
            t = buf[1] << 8 | buf[0]
            # 检查这个整数是否大于0x7fff,如果是,那么就将其转换为负数
            if t & 0x8000:
                # 先将后面的11位二进制补码变为原码(符号位不变,数值位取反后加1),再计算十进制值
                t = -((t ^ 0xffff) + 1)
            # 除以16,即乘以系数0.0625
            return t / 16
        else:
            return None
    # 抛出断言异常
    except AssertionError:
        return None

这里,在温度值解码方面,代码通过对不同型号传感器的数据处理逻辑进行区分,可以适用于多种 DS18X20 系列传感器,具体流程如下图所示:

a0f2ec91-6df8-43cd-b05b-58f48b4edc00.png

我们还定义了用于设置和读取温度转换分辨率的 resolution() 方法,该方法主要是通过将更新后的配置数据写入到传感器的暂存器中来实现温度转换分辨率的设置:

def resolution(self, rom: bytearray, bits: int = None) -> int:
    """
​    设置或读取传感器温度转换分辨率。

​    Args:
​        rom (bytearray): 目标温度传感器的 ROM 地址。
​        bits (int): 温度转换分辨率(9~12 位)。如果为 None,则读取当前分辨率。

​    Returns:
​        int: 设置或读取的分辨率位数。
​    """
​    ​if bits is not None and 9 <= bits <= 12:
        # 将分辨率信息设置为bits变量的值
        # self.config的第3位用于设置分辨率
        self.config[2] = ((bits - 9) << 5) | 0x1f
        self.write_scratch(rom, self.config)
        return bits
    else:
        data = self.read_scratch(rom)
        return ((data[4] >> 5) & 0x03) + 9

具体流程如下图所示:

bfb6d0a4-c7e4-45ed-83de-3db0e4059548.png

需要注意的是,分辨率越高,温度值的精度越高,但相应的转换时间也会更长。通过调整分辨率,可以在精度和转换速度之间进行权衡,满足不同应用场景的需求。

对于 DS18 系列传感器来说,转换精度和时间如下:

ScreenShot_2026-06-09_213530_935.png

最后,我们定义了用于摄氏度、开氏度和华氏度的转换方法。其中,摄氏度是最广泛使用的温度单位,适用于大多数日常生活和工业应用场景。华氏度主要局限于北美地区。而开氏度则主要用于科学研究和一些高精度的工业应用。三者关系如下:

  • ​开氏度与摄氏度的转换公式为:​K = °C + 273.15
  • 华氏度与摄氏度的转换公式为:​°F = °C × 1.8 + 32
def fahrenheit(self, celsius: float) -> float:
    """
​    将摄氏度转换为华氏度。

​    Args:
​        celsius (float): 摄氏度。

​    Returns:
​        float: 华氏度。如果输入为 None,返回 None。
​    """
​    ​return celsius * 1.8 + 32 if celsius is not None else None

def kelvin(self, celsius: float) -> float:
    """
​    将摄氏度转换为开氏度。

​    Args:
​        celsius (float): 摄氏度。

​    Returns:
​        float: 开氏度。如果输入为 None,返回 None。
​    """
​    ​return celsius + 273.15 if celsius is not None else None

3.应用实验

在下面实验中,我们需要将风雅一号板-Grove接口扩展板插入到风雅一号板-通用兼容扩展板上,同时使用 HY2.0-4P 排线连接风雅一号板-通用兼容扩展板DIO0 接口和GraftSense-基于DS18B20的温度传感器模块上的 DIO0 数字接口:

6d27a62a03771e5388f37e241534d438.jpg

9e25f4dc-b782-44f4-b298-461fd7f55f5d.jfif

使用的引脚情况如下表所示:

ScreenShot_2026-06-09_213554_607.png

以下代码可以在我们提供的资料包中 elegance-devkit v1\Demo\38 OneWire_DS18B20 文件夹找到。

在软件代码中,我们导入相关模块:

# Python env   : MicroPython v1.23.0
# -*- coding: utf-8 -*-        
# @Time    : 2024/7/22 下午3:01   
# @Author  : 李清水            
# @File    : main.py       
# @Description : DS18B20温度类实验,使用单总线通信完成数据交互

# ======================================== 导入相关模块 ========================================

# 导入硬件相关的模块
from machine import Pin
# 导入时间相关的模块
import time
# 导入单总线通信相关的模块
from onewire import OneWire
# 导入温度传感器类
from ds18x20 import DS18X20

在初始化配置中,我们定义了单总线通信引脚 ow_pin,同时创建 DS18X20 对象并绑定到 ow_pin,用于与 DS18B20 传感器进行通信:

# ======================================== 全局变量 ============================================

# ======================================== 功能函数 ============================================

# ======================================== 自定义类 ============================================

# ======================================== 初始化配置 ==========================================

# 延时等待设备初始化
time.sleep(3)
# 打印调试信息
print('FreakStudio : Using OneWire to read DS18B20 temperature')

# 定义单总线通信引脚
ow_pin = OneWire(Pin(14))
# 定义温度传感器
ds18x20 = DS18X20(ow_pin)

在主程序中,我们进行以下操作:

image.png

  1. 准备工作:
    1. 延迟 3 秒钟以确保系统稳定;
    2. 扫描总线上的 DS18B20 传感器设备,获取设备地址列表 roms_list
    3. 打印 DS18B20 传感器设备的 ROM ID;
    4. 触发所有 DS18B20 传感器启动温度转换;
  2. 温度读取循环:
    1. 每 500 ms 读取一次温度;
    2. 遍历 roms_list 中的每个设备地址;
    3. 读取对应设备的温度值并打印;
    4. 再次触发所有 DS18B20 传感器启动温度转换;

示例代码如下:

# ========================================  主程序  ===========================================

# 扫描总线上的DS18B20,获取设备地址列表
roms_list = ds18x20.scan()
# 打印设备地址列表
for rom in roms_list:
    print('ds18b20 sensor devices rom id:', rom)
# 让所有挂载在总线上的DS18B20转换温度
ds18x20.convert_temp()

# 循环读取温度
while True:
    time.sleep_ms(500)
    for rom in roms_list:
        # 转换并打印温度
        temp = ds18x20.read_temp(rom)
        # 打印温度
        print('ds18b20 sensor {} devices temp {}'.format(rom, temp))
    # 启动温度转换
    ds18x20.convert_temp()

烧录代码,连接终端,用手轻捏传感器,可以看到终端输出如下:

2076f2fb-9ec3-457f-a190-534c90ad97a3.png

mmexport1781014259545.gif

posted @ 2026-09-01 18:38  FreakStudio  阅读(22)  评论(0)    收藏  举报