第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:
- 单总线时序:
- 复位/存在脉冲
- 读写时隙的时序要求
- DS18B20 通信:
- 64 位 ROM 结构
- ROM 命令与功能命令
- 暂存器数据格式
- 温度数据解码:
- 12 位分辨率,0.0625°C 精度
- 二进制补码表示负温度
- 可靠性保障:
- CRC-8 校验
- 多传感器管理
下一章将学习红外遥控编解码。