DS18B20 单总线(1-Wire)协议:UART 模拟篇
·
在嵌入式开发中,DS18B20 是单总线协议的典型代表。由于单总线对时序要求极严(微秒级),传统 GPIO 翻转易受中断干扰。本文重点介绍如何通过 UART(串口)硬件 完美模拟 1-Wire 时序。
一、什么是单总线(1-Wire)?
单总线(1-Wire)是由 Dallas(现 Maxim)开发的异步半双工通信协议。
- 极简连线: 最少连线仅需 一根数据线(DQ)+ GND。
- 物理特性: 采用 开漏输出(Open Drain),通过外部上拉电阻(通常 4.7kΩ)保证空闲时总线为高电平。
- 主从结构: 主机发起,从机响应。
- 唯一标识: 每个从机出厂自带全球唯一的 64 位 ROM ID,支持一根线上挂载多个传感器。
- 时序敏感: 通信时序要求严格,主机必须精确控制拉低和释放总线的时间。
二、通信基本流程
一次完整通信包含以下步骤:
1. 初始化(Reset + Presence)
- 主机拉低 ≥ 480µs(Reset)
- 释放总线
- 从机等待 15~60µs 后拉低 60~240µs(Presence)
表示设备在线并准备通信
2. 发送 ROM 命令
用于设备寻址:
| 命令 | 功能 |
|---|---|
| Read ROM (33h) | 读取唯一ID(单设备) |
| Match ROM (55h) | 选择指定设备 |
| Skip ROM (CCh) | 跳过寻址(单设备) |
| Search ROM (F0h) | 搜索所有设备 |
| Alarm Search (ECh) | 搜索报警设备 |
3. 发送功能命令
针对设备内部功能操作:
| 命令 | 功能 |
|---|---|
| Convert T (44h) | 启动温度转换 |
| Read Scratchpad (BEh) | 读取数据 |
| Write Scratchpad (4Eh) | 写配置 |
| Copy Scratchpad (48h) | 写入 EEPROM |
| Recall E2 (B8h) | 读取 EEPROM |
| Read Power Supply (B4h) | 查询供电方式 |
4. 数据读写
写时序:
- 写 0:拉低时间长(60 ~ 120µs)
- 写 1:拉低时间短(1~15µs)
读时序:
- 主机拉低后释放
- 从机决定电平:
- 低 → 0
- 高 → 1
三、UART模拟单总线通信原理
利用 UART 的起始位(Start Bit)始终为低电平,以及数据位可控的特性,通过切换波特率来模拟 1-Wire 的各种脉冲。
硬件连接: UART 的 TX 和 RX 必须短接(Half-Duplex 模式或物理短接),并连接一个上拉电阻 到 3.3V。
1. 复位与存在检测(波特率:9600)
- 原理: 9600 下 1 个 Bit 约为 104µs。
- 发送 0xF0:
- 时序:1 个起始位(0) + 4 个数据位(0) + 4 个数据位(1) + 1 个停止位(1)。
- 低电平时长:(满足 Reset 要求的 >480µs)。
- 高电平释放期:后 5 个 bit 为高,DS18B20 会在此期间拉低总线产生 Presence(存在信号)。
- 判断逻辑: 若 Receive != 0xF0,说明有从机拉低了总线,传感器在线。
2. 位读写(波特率:115200)
- 原理: 115200 下 1 个 Bit 约为 8.68µs。
| 操作 | UART 发送字节 | 信号成分 | 低电平总时长 | 1-Wire 标准要求 |
|---|---|---|---|---|
| 写 1 / 读请求 | 0xFF | 仅起始位为 0 | 8.68µs | 1 ~ 15µs |
| 写 0 | 0x00 | 起始位 + 8位数据全 0 | 78.12µs | 60 ~ 120µs |
四、代码示例
1. 单个设备代码示例:
// 初始化 UART 波特率的辅助函数
static void DS18B20_UART_ReInit(uint32_t baudrate) {
huart2.Init.BaudRate = baudrate;
HAL_UART_Init(&huart2);
}
// 发送并接收一个位 (115200bps)
static uint8_t DS18B20_WriteRead_Bit(uint8_t bit_val) {
uint8_t tx_data = bit_val ? 0xFF : 0x00; // 发送 0xFF 代表释放总线读 1,0x00 代表拉低读 0
uint8_t rx_data = 0;
HAL_UART_Transmit(&huart2, &tx_data, 1, 10);
HAL_UART_Receive(&huart2, &rx_data, 1, 10);
return (rx_data == 0xFF) ? 1 : 0; // 如果收回也是 0xFF,说明总线被拉高,读到 1
}
// 发送一个字节 (8位)
static void DS18B20_WriteByte(uint8_t data) {
for (int i = 0; i < 8; i++) {
DS18B20_WriteRead_Bit(data & 0x01);
data >>= 1;
}
}
// 读取一个字节 (8位)
static uint8_t DS18B20_ReadByte(void) {
uint8_t data = 0;
for (int i = 0; i < 8; i++) {
if (DS18B20_WriteRead_Bit(1)) { // 发送 1 释放总线,读取回复
data |= (1 << i);
}
}
return data;
}
// 3. 读取完整温度
float DS18B20_GetTemp(void) {
uint8_t data[2];
uint8_t presence_echo = 0;
uint8_t reset_cmd = 0xF0;
// --- 1. 启动转换 ---
DS18B20_UART_ReInit(9600);
HAL_UART_Transmit(&huart2, &reset_cmd, 1, 10);
HAL_UART_Receive(&huart2, &presence_echo, 1, 10);
if (presence_echo == 0xF0) return -101.0; // 没检测到传感器
DS18B20_UART_ReInit(115200);
DS18B20_WriteByte(0xCC); // Skip ROM
DS18B20_WriteByte(0x44); // Convert T
HAL_Delay(750); // 等待转换
// --- 2. 读取结果 (必须重新 Reset) ---
DS18B20_UART_ReInit(9600);
HAL_UART_Transmit(&huart2, &reset_cmd, 1, 10);
HAL_UART_Receive(&huart2, &presence_echo, 1, 10);
if (presence_echo == 0xF0) return -102.0;
DS18B20_UART_ReInit(115200);
DS18B20_WriteByte(0xCC); // Skip ROM
DS18B20_WriteByte(0xBE); // Read Scratchpad
data[0] = DS18B20_ReadByte(); // LSB
data[1] = DS18B20_ReadByte(); // MSB
int16_t temp_raw = (data[1] << 8) | data[0];
return temp_raw * 0.0625f;
}
2. 多设备支持
如果总线上有多个设备,必须使用 Match ROM 或 Search ROM 来区分不同设备。
- 匹配ID读取:
DS18B20_UART_ReInit(115200);
DS18B20_WriteByte(0x55); // Match ROM 指令
DS18B20_WriteROM(ROM_IDs[rom_id]); // 发送指定的 8 字节 ID
DS18B20_WriteByte(0xBE); // Read Scratchpad
在正式布线先,可以逐个接入传感器以确认 ID,或使用 Search ROM 命令扫描总线上的所有设备。
- 读取单个设备的 ROM ID 示例:
void DS18B20_Read_Single_ROM(void) {
// 1. 复位 (9600 bps)
DS18B20_UART_ReInit(9600);
uint8_t reset_cmd = 0xF0;
uint8_t echo;
HAL_UART_Transmit(&huart2, &reset_cmd, 1, 10);
HAL_UART_Receive(&huart2, &echo, 1, 10);
uint8_t current_id[8];
if (echo != 0xF0) { // 检测到设备
// 2. 发送 Read ROM 命令 (0x33h) @ 115200 bps
DS18B20_UART_ReInit(115200);
DS18B20_WriteByte(0x33);
// 3. 连续读取 8 个字节
for (int i = 0; i < 8; i++) {
current_id[i] = DS18B20_ReadByte();
}
// 4. 打印出来 (假设你已经配置好了 printf)
printf("ROM ID: ");
for (int i = 0; i < 8; i++) {
printf("%02X ", current_id[i]);
}
printf("\r\n");
} else {
printf("No Sensor Found!\r\n");
}
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)