znlgis 博客

GIS开发与技术分享

第18章:单总线协议与 DS18B20

本章详细讲解单总线(1-Wire)协议的时序特点、DS18B20 温度传感器的通信过程与数据解码。


18.1 单总线协议概述

18.1.1 协议特点

1-Wire 协议是 Dallas/Maxim 公司的专有协议:

  • 单线通信:数据和时钟共用一根线
  • 主从结构:主机控制时序
  • 寄生供电:可通过数据线供电
  • 唯一地址:每个设备有 64 位唯一 ROM

18.1.2 硬件连接

         VCC
          │
        [4.7K]  上拉电阻
          │
主机 ─────┼───── DS18B20 (DQ)
          │
         GND

18.2 1-Wire 时序详解

18.2.1 复位与存在脉冲

复位时序:
主机: ────┐                        ┌─────────
          │      480μs+            │
          └────────────────────────┘
                     │
设备: ──────────────────┐     ┌─────────
                        │     │
                        └─────┘
                          60-240μs
                      存在脉冲

18.2.2 写时隙

写 "0":
主机: ──┐                    ┌────
        │    60-120μs        │
        └────────────────────┘
          设备采样窗口
          (15μs 附近)

写 "1":
主机: ──┐  ┌─────────────────────
        │  │
        └──┘
        <15μs  设备采样窗口

18.2.3 读时隙

主机: ──┐  ┌──────────────────────
        │  │ ← 主机在此时读取
        └──┘
        <15μs

设备返回 "0":
设备: ──────────────────────────
                 │ 拉低
                 └──────────

设备返回 "1":
设备: 不驱动,保持高电平

18.3 DS18B20 ROM 与功能命令

18.3.1 64 位 ROM 结构

┌──────────┬──────────────────────────────┬──────────┐
│ 家族码  │        序列号(48位)         │   CRC    │
│  (8位)  │    唯一标识每个设备            │  (8位)   │
│  0x28   │   例:0x123456789ABC          │          │
└──────────┴──────────────────────────────┴──────────┘

18.3.2 ROM 命令

命令 代码 功能
搜索 ROM 0xF0 枚举总线上所有设备
读 ROM 0x33 读取单设备的 ROM(仅单设备时)
匹配 ROM 0x55 选择特定 ROM 的设备
跳过 ROM 0xCC 跳过 ROM 匹配(单设备或广播)
报警搜索 0xEC 搜索有报警标志的设备

18.3.3 功能命令

命令 代码 功能
温度转换 0x44 启动温度测量
读暂存器 0xBE 读取 9 字节暂存器
写暂存器 0x4E 写入报警阈值和配置
复制暂存器 0x48 将暂存器复制到 EEPROM
回调 EEPROM 0xB8 从 EEPROM 恢复设置
读电源供电 0xB4 检测供电模式

18.4 暂存器数据格式

18.4.1 9 字节结构

字节 0: 温度 LSB
字节 1: 温度 MSB
字节 2: TH 寄存器(高温报警阈值)
字节 3: TL 寄存器(低温报警阈值)
字节 4: 配置寄存器
字节 5: 保留 (0xFF)
字节 6: 保留
字节 7: 保留 (0x10)
字节 8: CRC

18.4.2 温度数据编码

12 位分辨率时:

MSB:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ S │ S │ S │ S │ S │2^6│2^5│2^4│
└───┴───┴───┴───┴───┴───┴───┴───┘
  S = 符号位(0=正,1=负)

LSB:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│2^3│2^2│2^1│2^0│2^-1│2^-2│2^-3│2^-4│
└───┴───┴───┴───┴───┴───┴───┴───┘
     整数部分      小数部分

温度计算

# 正温度 (MSB 高 5 位为 0):
temp = raw_value / 16  # 或 raw_value * 0.0625

# 负温度 (MSB 高 5 位为 1):
# 使用二进制补码
if raw_value & 0x8000:  # 检查符号位
    raw_value = raw_value - 65536
temp = raw_value / 16

18.4.3 温度转换示例

原始值 (Hex) 二进制 温度 (°C)
0x0191 0000 0001 1001 0001 +25.0625
0x00A2 0000 0000 1010 0010 +10.125
0x0008 0000 0000 0000 1000 +0.5
0x0000 0000 0000 0000 0000 0
0xFFF8 1111 1111 1111 1000 -0.5
0xFF5E 1111 1111 0101 1110 -10.125
0xFE6F 1111 1110 0110 1111 -25.0625

18.5 MicroPython 实现

18.5.1 使用内置库

'''
实验:DS18B20 温度读取
'''
import machine
import onewire
import ds18x20
import time

# 初始化单总线
ow = onewire.OneWire(machine.Pin(3))
ds = ds18x20.DS18X20(ow)

# 扫描设备
roms = ds.scan()
print(f"发现 {len(roms)} 个 DS18B20")

for rom in roms:
    print(f"  ROM: {rom.hex()}")

# 读取温度
while True:
    ds.convert_temp()
    time.sleep_ms(750)  # 12 位需要 750ms
    
    for rom in roms:
        temp = ds.read_temp(rom)
        print(f"温度: {temp:.2f}°C")
    
    time.sleep(1)

18.5.2 手动实现协议

'''
实验:手动实现 1-Wire 协议
深入理解协议细节
'''
import machine
import time

class OneWireManual:
    def __init__(self, pin_num):
        self.pin = machine.Pin(pin_num)
    
    def _write_bit(self, bit):
        """写一个位"""
        self.pin.init(machine.Pin.OUT)
        
        if bit:
            # 写 1: 短脉冲
            self.pin.value(0)
            time.sleep_us(6)
            self.pin.value(1)
            time.sleep_us(64)
        else:
            # 写 0: 长脉冲
            self.pin.value(0)
            time.sleep_us(60)
            self.pin.value(1)
            time.sleep_us(10)
    
    def _read_bit(self):
        """读一个位"""
        self.pin.init(machine.Pin.OUT)
        self.pin.value(0)
        time.sleep_us(6)
        
        self.pin.init(machine.Pin.IN)
        time.sleep_us(9)
        
        bit = self.pin.value()
        time.sleep_us(55)
        
        return bit
    
    def reset(self):
        """发送复位脉冲,返回是否有设备响应"""
        self.pin.init(machine.Pin.OUT)
        self.pin.value(0)
        time.sleep_us(480)
        
        self.pin.init(machine.Pin.IN)
        time.sleep_us(70)
        
        presence = not self.pin.value()
        time.sleep_us(410)
        
        return presence
    
    def write_byte(self, byte):
        """写一个字节"""
        for i in range(8):
            self._write_bit(byte & 1)
            byte >>= 1
    
    def read_byte(self):
        """读一个字节"""
        byte = 0
        for i in range(8):
            byte |= self._read_bit() << i
        return byte

# 使用手动实现
ow = OneWireManual(3)

if ow.reset():
    print("设备存在")
    
    # 跳过 ROM + 温度转换
    ow.write_byte(0xCC)
    ow.write_byte(0x44)
    
    time.sleep_ms(750)
    
    # 跳过 ROM + 读暂存器
    ow.reset()
    ow.write_byte(0xCC)
    ow.write_byte(0xBE)
    
    # 读取 9 字节
    data = [ow.read_byte() for _ in range(9)]
    print(f"暂存器: {[hex(b) for b in data]}")
    
    # 解析温度
    raw = data[0] | (data[1] << 8)
    if raw & 0x8000:
        raw -= 65536
    temp = raw / 16
    print(f"温度: {temp:.2f}°C")

18.6 CRC 校验

18.6.1 CRC-8 算法

DS18B20 使用 CRC-8 校验,多项式为 X^8 + X^5 + X^4 + 1 (0x31):

def crc8(data):
    """计算 CRC-8"""
    crc = 0
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x01:
                crc = (crc >> 1) ^ 0x8C
            else:
                crc >>= 1
    return crc

# 验证暂存器数据
data = [0x91, 0x01, 0x4B, 0x46, 0x7F, 0xFF, 0x0F, 0x10, 0x4A]
calculated_crc = crc8(data[:-1])
received_crc = data[-1]

if calculated_crc == received_crc:
    print("CRC 校验通过")
else:
    print(f"CRC 错误: 计算={calculated_crc:02X}, 接收={received_crc:02X}")

18.7 多传感器管理

18.7.1 ROM 搜索算法

'''
实验:搜索所有 DS18B20
'''
import machine
import onewire

ow = onewire.OneWire(machine.Pin(3))

def search_all():
    """搜索所有设备"""
    devices = []
    ow.reset()
    
    # 使用内置 scan 方法
    for rom in ow.scan():
        if rom[0] == 0x28:  # DS18B20 家族码
            devices.append(rom)
    
    return devices

devices = search_all()
print(f"找到 {len(devices)} 个 DS18B20:")
for i, rom in enumerate(devices):
    print(f"  [{i}] ROM: {rom.hex()}")

18.7.2 并行温度转换

'''
实验:同时读取多个传感器
'''
import ds18x20
import time

ds = ds18x20.DS18X20(ow)
roms = ds.scan()

# 广播温度转换命令(所有设备同时转换)
ds.convert_temp()
time.sleep_ms(750)

# 逐个读取
for rom in roms:
    temp = ds.read_temp(rom)
    print(f"ROM {rom.hex()}: {temp:.2f}°C")

18.8 分辨率配置

18.8.1 分辨率与转换时间

分辨率 配置字节 增量 转换时间
9 位 0x1F 0.5°C 93.75 ms
10 位 0x3F 0.25°C 187.5 ms
11 位 0x5F 0.125°C 375 ms
12 位 0x7F 0.0625°C 750 ms

18.8.2 设置分辨率

def set_resolution(ds, rom, resolution):
    """
    设置分辨率
    resolution: 9, 10, 11, 12
    """
    config_bytes = {9: 0x1F, 10: 0x3F, 11: 0x5F, 12: 0x7F}
    config = config_bytes.get(resolution, 0x7F)
    
    ow.reset()
    ow.select_rom(rom)
    ow.writebyte(0x4E)  # 写暂存器
    ow.writebyte(0x00)  # TH
    ow.writebyte(0x00)  # TL
    ow.writebyte(config)  # 配置

# 设置为 9 位分辨率(快速但精度低)
# set_resolution(ds, roms[0], 9)

18.9 本章小结

本章深入讲解了单总线协议与 DS18B20:

  1. 单总线时序
    • 复位/存在脉冲
    • 读写时隙的时序要求
  2. DS18B20 通信
    • 64 位 ROM 结构
    • ROM 命令与功能命令
    • 暂存器数据格式
  3. 温度数据解码
    • 12 位分辨率,0.0625°C 精度
    • 二进制补码表示负温度
  4. 可靠性保障
    • CRC-8 校验
    • 多传感器管理

下一章将学习红外遥控编解码。