---
title: "Practical Application of 1‑Wire Protocol: Low‑Level Drivers, Timing Diagrams and Complete Implementa"
url: "https://maker.wiznet.io/ruilixin6/projects/practical-application-of-1wire-protocol-lowlevel-drivers-timing-diagrams-and-complete-implementa/"
markdown_url: "https://maker.wiznet.io/ruilixin6/projects/practical-application-of-1wire-protocol-lowlevel-drivers-timing-diagrams-and-complete-implementa/md"
type: "UCC: User Created Content"
author: "FreakStudio"
author_url: "https://maker.wiznet.io/ruilixin6/"
original_author: "Freakstudio"
original_url: "https://openwrite.cn/articles/63390/edit"
published: "2026-08-14"
language: "en"
tags: ["pico-onewire-gpio"]
likes: 0
views: 119
comments: 0
source: "WIZnet Makers (https://maker.wiznet.io/)"
---

# Practical Application of 1‑Wire Protocol: Low‑Level Drivers, Timing Diagrams and Complete Implementa

> This article introduces two 1-Wire implementation schemes on Pico, focusing on GPIO simulation. It presents a complete OneWire MicroPython class with reset, rea

Original author: Freakstudio (source: https://openwrite.cn/articles/63390/edit)

## Article

【Preliminary Note】The original hardware example in this article was written based on the RP2040. The actual hardware used in this hands-on demonstration features the W55RP20 as the main controller chip. The circuit logic and UF2 flashing operation principles are universally applicable, with only the main controller model differing. The original chip model mentioned in the circuit descriptions below is provided for reference purposes only.

![](https://mcnszeyqf1h6.feishu.cn/space/api/box/stream/download/asynccode/?code=NTZiOTcyZmFkY2FiYmRiNDdkZTA1MzM5ZDExOTFiMDhfU0xBa2lGZVUwdGN2QlVUcFVzNDlSMnJoaWt6eFFnQlZfVG9rZW46TllOb2JZSks0bzc2aUt4QjI0ZmNGUjNYbnplXzE3ODY3MDQwNTA6MTc4NjcwNzY1MF9WNA&add_watermark=true&scene_type=CCM)

**1. 1-Wire Overview on the Raspberry Pi Pico**

There are two ways to implement 1-Wire communication using the Raspberry Pi Pico:

![ScreenShot_2026-06-09_205633_921.png](http://openwrite.cn/uploads/21597/63390/f58c3c05-ccd7-4f1d-92c2-3852daea48f7.png) ![ScreenShot_2026-06-09_205709_831.png](http://openwrite.cn/uploads/21597/63390/2320a83a-9653-4cb5-9b3c-40c06a8d2a01.png)

Here, we use GPIO software emulation of the 1-Wire communication protocol so that everyone can better understand the overall flow of 1-Wire communication.

## **2. Software Control Method**

Here, we use the GPIO software emulation method to implement 1-Wire communication, referring to the following code for implementation:

https://github.com/robert-hh/Onewire_DS18X20/blob/master/onewire.py

First, we define the 1-Wire communication class. Its main functions are as follows:

**Initialize 1-Wire communication:** includes configuring the pin as open-drain with pull-up, and defining methods to disable and enable interrupts.

**Implement the 1-Wire reset operation:** includes the master sending a reset pulse and determining whether a slave responds.

**Implement single-bit and single-byte read/write operations:** satisfy the timing requirements by controlling the bus state and delays.

**Implement multi-byte read/write operations:** implemented by calling the single-byte read/write methods in a loop.

**Implement the function of selecting a device with a specified ROM ID:** used to select a specific slave device.

**Implement a CRC8 check algorithm based on a lookup table:** used to verify whether the data read from the slave device is complete and correct.

**Implement the scan function:** scan all devices on the bus and return all matching ROM IDs.

First, we define several commonly used command code constants for 1-Wire communication, such as the search command and the read ROM ID command:

```
# Define the 1-Wire communication class
class OneWire:
    """
    OneWire class, used to communicate through the OneWire protocol, supporting interaction with OneWire devices (such as DS18B20 temperature sensors).
    This class encapsulates the basic operations of OneWire communication, such as resetting the bus and sending/receiving data, and provides a device search function.
​
    Attributes:
        pin (Pin): the GPIO pin instance used for OneWire communication.
        crctab1 (bytes): the first byte table required by the CRC lookup table method.
        crctab2 (bytes): the second byte table required by the CRC lookup table method.
        disable_irq (function): the function to disable interrupts.
        enable_irq (function): the function to enable interrupts.
​
    Methods:
        reset(required: bool = False) -> bool: reset the 1-Wire bus.
        readbit() -> int: read one data bit from the 1-Wire bus.
        readbyte() -> int: read one byte from the 1-Wire bus.
        readbytes(count: int) -> bytearray: read multiple bytes from the 1-Wire bus.
        readinto(buf: bytearray) -> None: read multiple bytes from the 1-Wire bus into a buf array.
        writebit(value: int, powerpin: machine.Pin = None) -> None: write one data bit to the 1-Wire bus.
        writebyte(value: int, powerpin: machine.Pin = None) -> None: write one byte to the 1-Wire bus.
        write(buf: bytearray) -> None: write the data in the buf array to the 1-Wire bus.
        select_rom(rom: bytearray) -> None: send the Match ROM command.
        crc8(data: bytearray) -> int: CRC check, implemented with the lookup table method.
        scan() -> list[bytearray]: scan all devices on the 1-Wire bus and return all matching ROM IDs.
        _search_rom(l_rom: bytearray, diff: int) -> tuple[bytearray, int]: search for the device with the corresponding ROM ID on the 1-Wire bus.
    """
    # ROM commands for 1-Wire communication
    CMD_SEARCHROM   = const(0xf0)  # Search command
    CMD_READROM     = const(0x33)  # Read ROM ID command
    CMD_MATCHROM    = const(0x55)  # Match ROM ID command
    CMD_SKIPROM     = const(0xcc)  # Address all devices command
    # High level value
    PULLUP_ON       = 1
```

In the initialization method, we define the configuration of the pin as open-drain with pull-up, the methods to disable and enable interrupts, and the two bytes tables used for CRC checking:

```
def __init__(self, pin: machine.Pin) -> None:
    """
    Initialize the OneWire class, passing in the data pin object to use.
​
    Args:
        pin (machine.Pin): the data pin object.
​
    Returns:
        None
    """
    self.pin = pin
    # Initialize the pin as pull-up, open-drain mode
    self.pin.init(pin.OPEN_DRAIN, pin.PULL_UP)
    # Define the methods to disable and enable interrupts
    self.disable_irq = machine.disable_irq
    self.enable_irq = machine.enable_irq
    # Two bytes tables required by the CRC lookup table method
    self.crctab1 = (b"\x00\x5E\xBC\xE2\x61\x3F\xDD\x83"
                    b"\xC2\x9C\x7E\x20\xA3\xFD\x1F\x41")
    self.crctab2 = (b"\x00\x9D\x23\xBE\x46\xDB\x65\xF8"
                    b"\x8C\x11\xAF\x32\xCA\x57\xE9\x74")
```

Then, we define the method to reset the bus, mainly implemented by controlling the high/low level of the bus and delays. For details, you can refer to the 1-Wire communication protocol layer content we discussed above:

```
def reset(self, required: bool = False) -> bool:
    """
    Reset the 1-Wire bus.
​
    Args:
        required (bool): whether an assertion needs to be manually triggered, defaults to False.
​
    Returns:
        bool: returns True if the device sent a presence pulse; otherwise returns False, indicating failure.
​
    Raises:
        AssertionError: if required is True and the device did not respond to the reset pulse.
    """
    sleep_us = time.sleep_us
    pin = self.pin
    # The master sends a reset pulse by pulling the bus low for 480us
    pin(0)
    sleep_us(480)
    # Disable interrupts to prevent interrupt service routines from interrupting communication
    i = self.disable_irq()
    # Pull the bus high for 60us
    pin(1)
    sleep_us(60)
    # Wait for the slave to send a presence pulse; the presence pulse pulls the bus low for 60~240us
    # Read the bus state
    status = not pin()
    self.enable_irq(i)
    # In the idle state, the pull-up resistor pulls the bus high; the master receives the presence pulse for at least 480us
    # 480us - 60us = 420us
    sleep_us(420)
    # If status on the bus is True, it means a device responded to the reset pulse
    # The program continues executing only when the assert condition is met
    assert status is True or required is False, "Onewire device response"
    return status
```

Its timing diagram is shown below:

![1e028fc8-b212-44a2-9ea4-0d07b3234346.png](http://openwrite.cn/uploads/21597/63390/757143b6-2fab-453c-b9b6-de9334f62697.png)

Then we define the methods used by the master to read a single data bit, read a single byte, and read multiple bytes:

```
def readbit(self) -> int:
    """
    Read one data bit from the 1-Wire bus.
​
    Args:
        None
​
    Returns:
        int: the read data bit (0 or 1).
    """
    sleep_us = time.sleep_us
    pin = self.pin
​
    # For some devices, the bus needs to be pulled high before reading data to match CRC checking
    pin(1)
    # Disable interrupts
    i = self.disable_irq()
    # Pull the 1-Wire bus low to start reading a data bit
    # The master read signal is generated by pulling the bus low for at least 1us and then releasing it
    pin(0)
    # Skip sleep_us(1), for compatibility with some devices that do not require strict timing
    pin(1)
    # The master only needs to complete sampling within 15us
    sleep_us(5)
    value = pin()
    # Enable interrupts
    self.enable_irq(i)
    # The master pulls the bus high for 40us, indicating that the data bit read is complete
    sleep_us(40)
    return value
​
def readbyte(self) -> int:
    """
    Read one byte from the 1-Wire bus, implemented by calling readbit 8 times.
​
    Args:
        None
​
    Returns:
        int: the read byte (0~255).
    """
    value = 0
    for i in range(8):
        # Shift the return value of self.readbit() left by i bits and OR it with value
        # to obtain a byte composed of 8 data bits
        value |= self.readbit() << i
    return value
​
def readbytes(self, count: int) -> bytearray:
    """
    Read multiple bytes from the 1-Wire bus, implemented by calling readbyte multiple times.
​
    Args:
        count (int): the number of bytes to read.
​
    Returns:
        bytearray: the read binary byte array.
    """
    buf = bytearray(count)
    for i in range(count):
        buf[i] = self.readbyte()
    return buf
​
def readinto(self, buf: bytearray) -> None:
    """
    Read multiple bytes from the 1-Wire bus into a buf array.
​
    Args:
        buf (bytearray): the binary array to place the data to read.
​
    Returns:
        None
    """
    for i in range(len(buf)):
        buf[i] = self.readbyte()
```

You can see that the `readinto()` method, `readbytes()` method, and `readbyte()` method are all implemented by calling the `readbit()` method:

![e5d02ee1-4e2a-4bf4-b98d-fd7dc2503949.png](http://openwrite.cn/uploads/21597/63390/30cc9ba1-4518-4d2c-b15b-42c748f92e50.png)

The internal working timing of the `readbit()` method is shown below:

![image.png](http://openwrite.cn/uploads/21597/63390/77f08a6e-1bbf-4ab9-bbdb-a16b6b047f89.png)

Similarly, we define the methods for the master to write data to the sensor:

```
def writebit(self, value: int, powerpin: machine.Pin = None) -> None:
    """
    Write one data bit to the 1-Wire bus.
​
    Args:
        value (int): the data bit to write, 0 or 1.
        powerpin (machine.Pin): the power supply pin; pass this object when using parasitic power supply.
                                The default is independent power supply, so this pin is not needed.
​
    Returns:
        None
    """
    sleep_us = time.sleep_us
    pin = self.pin
​
    # Disable interrupts
    i = self.disable_irq()
    # First pull the bus low for at least 1us (at most 15us); this is omitted here because MicroPython executes slowly
    # In fact, there is already a 1us delay between the pin(0) and pin(value) statements
    pin(0)
    # If the data bit to send is 0, pull the bus low
    # If the data bit to send is 1, pull the bus high
    pin(value)
    # The write signal lasts at least 60us; the slave starts sampling 15us after the master pulls the bus low
    sleep_us(60)
​
    # If parasitic power supply is used and a power pin is defined
    if powerpin:
        # The 1-Wire bus can intermittently provide high level to charge the slave
        pin(1)
        powerpin(self.PULLUP_ON)
    else:
        pin(1)
​
    # Enable interrupts
    self.enable_irq(i)
​
def writebyte(self, value: int, powerpin: machine.Pin = None) -> None:
    """
    Write one byte to the 1-Wire bus, implemented by calling writebit 8 times in succession.
​
    Args:
        value (int): the value to write, 0~255.
        powerpin (machine.Pin): the power supply pin; pass this object when using parasitic power supply.
​
    Returns:
        None
    """
    for i in range(7):
        self.writebit(value & 1)
        value >>= 1
    self.writebit(value & 1, powerpin)
​
def write(self, buf: bytearray) -> None:
    """
    Write the data in the buf array to the 1-Wire bus, implemented by calling writebyte multiple times.
​
    Args:
        buf (bytearray): the binary array containing the data to send.
​
    Returns:
        None
    """
    for b in buf:
        self.writebyte(b)
```

Similarly, the `writebyte()` method and `write()` method are also implemented by calling the `writebit()` method:

![c13285d0-be1b-416a-8405-257e99c031ee.png](http://openwrite.cn/uploads/21597/63390/e1622ef1-4fcb-4131-9157-25d96e4917d0.png)

The specific working process of the `writebit()` method is shown below:

![fe446dd6-a5b0-4057-a49a-fd14b9a7c598.png](http://openwrite.cn/uploads/21597/63390/4dfe89f1-7eb9-49d9-9640-5c5087a809ee.png)

Then we define the method used to select the slave device with the specified `ROM ID` on the 1-Wire bus, mainly implemented by sending the Match `ROM ID` command:

```
def select_rom(self, rom: bytearray) -> None:
    """
    Send the Match ROM ID command.
​
    Args:
        rom (bytearray): ROM ID, a bytearray of 8 bytes, 8 bytes x 8 bits = 64 bits.
​
    Returns:
        None
    """
    # Initialize the bus
    self.reset()
    # Send the Match ROM ID command
    self.writebyte(OneWire.CMD_MATCHROM)
    # The master sends the ROM ID of the device to match
    self.write(rom)
```

We define the scan method used to find all connected devices on the 1-Wire bus, mainly implemented by calling the `_search_rom` method:

```
def scan(self) -> list[bytearray]:
    """
    Scan all devices on the 1-Wire bus and return all matching ROM IDs.
​
    Args:
        None
​
    Returns:
        list[bytearray]: returns the ROM list of all connected devices.
                          Each ROM is returned as an 8-byte byte object.
    """
    # List to store device ROM IDs
    devices = []
    diff = 65
    rom = False
​
    # Search all ROM IDs from 0 to 255
    for i in range(0xff):
        rom, diff = self._search_rom(rom, diff)
        # If the search is successful, add the ROM ID to the list
        if rom:
            devices += [rom]
        # If diff is 0, all devices have been searched; exit the loop
        if diff == 0:
            break
    return devices
​
def _search_rom(self, l_rom: bytearray, diff: int) -> tuple[bytearray, int]:
    """
    Search for the device with the corresponding ROM ID on the 1-Wire bus.
​
    Args:
        l_rom (bytearray): the ROM ID found in the last search.
        diff (int): the difference position of the last search.
​
    Returns:
        tuple[bytearray, int]: if the search is successful, returns the ROM ID and the updated diff value.
    """
​
    # Reset the bus and check whether a slave responds
    if not self.reset():
        return None, 0
    # Send the OneWire.CMD_SEARCHROM command
    self.writebyte(OneWire.CMD_SEARCHROM)
    # If no ROM ID is passed in, initialize an empty ROM ID
    if not l_rom:
        l_rom = bytearray(8)
    # Initialize the rom variable to store the finally searched ROM address
    rom = bytearray(8)
    # Initialize the next_diff variable to record the next difference position
    next_diff = 0
    i = 64
​
    # Traverse the ROM ID from low to high, 8 bytes
    for byte in range(8):
        r_b = 0
        # Read the 8 data bits of each byte of the ROM ID sent by the slave
        for bit in range(8):
            b = self.readbit()
            # Read the data bit again
            if self.readbit():
                # If both reads are 1, i.e., both are high, the read was not successful
                if b:
                    return None, 0
            # If the two read results are different, the read was successful
            # The slave sends the original code and complement of each bit
            else:
                if not b:
                    # If there is a conflict and the current position is less than the last difference position, or the current position differs from the last difference position
                    if diff > i or ((l_rom[byte] & (1 << bit)) and diff != i):
                        b = 1
                        # Determine the next search direction by comparing the current search position with the last search position
                        next_diff = i
            # Write the b value back to the bus; the master needs to send each bit of the ID read
            # The slave compares the data bit read by the master with the data bit sent by the slave to determine whether they match
            # Then decides whether to send the next data bit
            self.writebit(b)
            if b:
                r_b |= 1 << bit
            i -= 1
        rom[byte] = r_b
    # Return the searched rom value and the next difference position next_diff
    return rom, next_diff
```

The following figure shows the specific flow and calling relationship:

![59f90b79-19ff-4ba7-a64d-1477cc93d196.png](http://openwrite.cn/uploads/21597/63390/86cf5f91-1fdb-4859-a617-043c5c478de0.png)

Here, we also define the commonly used CRC-8 check method, mainly using XOR operations and a lookup table to quickly check the CRC value:

```
def crc8(self, data: bytearray) -> int:
    """
    CRC check, implemented with the lookup table method.
​
    Args:
        data (bytearray): the data to check.
​
    Returns:
        int: the CRC check value; 0 means the check passed.
    """
​
    # Initialize the crc variable to 0
    crc = 0
    # Use a for loop to iterate over each byte of the input data
    for i in range(len(data)):
       # XOR the current crc value with the current byte value and assign the result back to crc
       crc ^= data[i]
       # Use the low 4 bits of the crc value as the index to look up the corresponding value in self.crctab1
       # Use the high 4 bits of the crc value as the index to look up the corresponding value in self.crctab2
       # XOR the two lookup results to get the new crc value
       crc = (self.crctab1[crc & 0x0f] ^
              self.crctab2[(crc >> 4) & 0x0f])
    return crc
```

---

Source: https://maker.wiznet.io/ruilixin6/projects/practical-application-of-1wire-protocol-lowlevel-drivers-timing-diagrams-and-complete-implementa/
