什麼是 OneWire 通訊協定?
OneWire(1-Wire) 是 Maxim(現 Analog Devices)開發的專有通訊協定,如同其名——只需要一條資料線(DQ)就能完成雙向通訊。它同時也是供電線(寄生供電模式),讓裝置只需要 2 條線(DQ + GND)就能運作。
最廣泛使用的 OneWire 裝置就是 DS18B20 數位溫度感測器:
- 測量範圍:-55°C ~ +125°C
- 精度:±0.5°C(-10°C ~ +85°C)
- 解析度:9~12 bit(可程式,預設 12 bit = 0.0625°C)
- 獨一無二的 64-bit ROM ID:一條匯流排上可掛多顆
- 供電:3.0V ~ 5.5V(外接或寄生供電)

Scratchpad(暫存器)是 DS18B20 的核心:Byte0~1 儲存溫度值、Byte2~3 為警報閾值、Byte4 為設定暫存器、Byte8 為 CRC 校驗。
接線方式

OneWire 匯流排需要一顆 4.7kΩ 上拉電阻(Pull-Up)將 DQ 線拉到 VDD。建議使用外接電源模式(VDD 接 3.3V),寄生供電模式在溫度轉換時需要 Master 強拉 High,不建議多顆並用。
OneWire 通訊協定時序
OneWire 的時序非常精準,所有操作都由 Master(ESP32/STM32)發起:
1. 初始化(Reset + Presence Pulse)
每次通訊開始前,Master 必須發送 Reset 信號:
- Master 將 DQ 拉 Low 至少 480 μs
- Master 釋放 DQ(Pull-Up 拉回 High)
- Slave(DS18B20)在 15~60 μs 後將 DQ 拉 Low 60~240 μs
- 此 Presence Pulse 告訴 Master「裝置存在」
2. Write-0 / Write-1
寫入 1 bit 需要 60~120 μs:
- Write-1:Master 拉 Low 1~15 μs → 釋放 DQ(Pull-Up 拉 High)→ Slave 讀到 1
- Write-0:Master 拉 Low 60~120 μs → Slave 讀到 0
- 兩個 Write 之間需要至少 1 μs 的恢復時間(Recovery Time)
傳送 1 byte 就是連續 8 次 Write,LSB(最低位元)先送。
3. Read-Data
讀取 1 bit 的流程:
- Master 拉 Low 1~15 μs(啟動讀取時槽)
- Master 釋放 DQ
- Slave 在 15 μs 內驅動 DQ(若資料為 0 則拉 Low,若為 1 則 Release)
- Master 在 ~15 μs 時採樣 DQ 電平
- 整個時槽需 60 μs
4. 完整通訊流程
以 DS18B20 讀取溫度為例,流程如下:
- Reset + Presence:確認裝置存在
- 發送 ROM 指令:0xCC(Skip ROM,跳過位址比對,單顆時用)
- 發送功能指令:0x44(Convert T,啟動溫度轉換)
- 等待:12-bit 解析度下約 750 ms 轉換時間
- Reset + Presence:再次初始化
- 發送 ROM 指令:0xCC
- 發送功能指令:0xBE(Read Scratchpad,讀取暫存器)
- 讀取 9 bytes:包含溫度值、警報、CRC
常用 ROM 指令
| 指令 | 代碼 | 說明 |
|---|---|---|
| Search ROM | 0xF0 | 搜尋匯流排上所有 DS18B20 的 ROM ID |
| Read ROM | 0x33 | 讀取單顆裝置的 ROM ID(僅單顆時可用) |
| Match ROM | 0x55 | 指定特定 ROM ID 進行通訊 |
| Skip ROM | 0xCC | 跳過 ROM 比對(單顆或多顆一起控制時用) |
| Alarm Search | 0xEC | 搜尋觸發警報條件的裝置 |
常用功能指令
| 指令 | 代碼 | 說明 |
|---|---|---|
| Convert T | 0x44 | 啟動溫度轉換 |
| Write Scratchpad | 0x4E | 寫入 Byte2~4(TH, TL, 設定) |
| Read Scratchpad | 0xBE | 讀取 9 bytes 暫存器 |
| Copy Scratchpad | 0x48 | 將暫存器複製到 EEPROM |
| Recall EEPROM | 0xB8 | 從 EEPROM 復原暫存器 |
| Read Power Supply | 0xB4 | 讀取供電模式(0=寄生, 1=外接) |
ESP32 實作(OneWire + DallasTemperature)
// ESP32 Arduino - DS18B20 溫度讀取
#include
#include
#define ONE_WIRE_BUS 4 // GPIO 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
void setup() {
Serial.begin(115200);
sensors.begin(); // 初始化匯流排
// 顯示匯流排上 DS18B20 的數量
int count = sensors.getDeviceCount();
Serial.printf("發現 %d 顆 DS18B20\n", count);
// 設定解析度(可選,預設 12-bit)
sensors.setResolution(12);
}
void loop() {
// 請求溫度轉換(非阻塞版本)
sensors.requestTemperatures();
// 讀取溫度(°C)
float tempC = sensors.getTempCByIndex(0);
if (tempC == DEVICE_DISCONNECTED_C) {
Serial.println("❌ DS18B20 連線中斷");
} else {
float tempF = tempC * 9.0 / 5.0 + 32.0;
Serial.printf("溫度: %.2f°C (%.2f°F)\n", tempC, tempF);
}
delay(1000);
}
ESP32 多顆 DS18B20 實作(使用 ROM ID)
// ESP32 - 多顆 DS18B20 + ROM ID 定址
#include
#include
#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
// 儲存所有發現的 ROM ID
DeviceAddress sensorList[10];
int sensorCount = 0;
void setup() {
Serial.begin(115200);
sensors.begin();
// 搜尋所有 DS18B20 的 ROM ID
DeviceAddress addr;
DallasTemperature::DeviceAddress devices[10];
sensorCount = sensors.getDeviceCount();
Serial.printf("發現 %d 顆感測器\n", sensorCount);
// 列印每顆的 ROM ID
for (int i = 0; i < sensorCount; i++) {
if (sensors.getAddress(devices[i], i)) {
Serial.printf("感測器 #%d: ", i);
for (int b = 0; b < 8; b++) {
Serial.printf("%02X", devices[i][b]);
if (b < 7) Serial.print(":");
}
Serial.println();
}
}
sensors.setResolution(12); // 所有感測器設為 12-bit
}
void loop() {
sensors.requestTemperatures();
for (int i = 0; i < sensorCount; i++) {
float temp = sensors.getTempCByIndex(i);
Serial.printf("Sensor #%d: %.2f°C\n", i, temp);
}
delay(2000);
}
STM32 實作(HAL + 軟體模擬 OneWire)
// STM32 HAL - 軟體模擬 OneWire(DS18B20 讀取)
// 使用 PA0 作為 DQ 腳位,4.7kΩ 上拉到 3.3V
#define DQ_PORT GPIOA
#define DQ_PIN GPIO_PIN_0
// 微秒延遲(需 TIM 或 DWT 支援)
void delay_us(uint32_t us) {
// 使用 DWT 精確延遲,或 HAL_Delay 近似
for (uint32_t i = 0; i < us * 8; i++) __NOP();
}
// 匯流排操作
void DQ_Low() { HAL_GPIO_WritePin(DQ_PORT, DQ_PIN, GPIO_PIN_RESET); }
void DQ_High() { HAL_GPIO_WritePin(DQ_PORT, DQ_PIN, GPIO_PIN_SET); }
uint8_t DQ_Read(){ return HAL_GPIO_ReadPin(DQ_PORT, DQ_PIN); }
void DQ_Output() {
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = DQ_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // Open-Drain
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(DQ_PORT, &GPIO_InitStruct);
DQ_High();
}
void DQ_Input() {
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = DQ_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(DQ_PORT, &GPIO_InitStruct);
}
// OneWire Reset
uint8_t onewire_reset() {
DQ_Output();
DQ_Low();
delay_us(480);
DQ_High();
delay_us(70);
DQ_Input();
uint8_t presence = !DQ_Read(); // 0 = Presence
delay_us(410);
return presence;
}
// OneWire Write Bit
void onewire_write_bit(uint8_t bit) {
DQ_Output();
DQ_Low();
if (bit) {
delay_us(6);
DQ_High();
delay_us(64);
} else {
delay_us(60);
DQ_High();
delay_us(10);
}
}
// OneWire Read Bit
uint8_t onewire_read_bit() {
DQ_Output();
DQ_Low();
delay_us(2);
DQ_Input();
delay_us(8);
uint8_t bit = DQ_Read();
delay_us(50);
return bit;
}
// OneWire Write Byte
void onewire_write_byte(uint8_t data) {
for (int i = 0; i < 8; i++) { onewire_write_bit(data & 0x01); data >>= 1;
}
}
// OneWire Read Byte
uint8_t onewire_read_byte() {
uint8_t data = 0;
for (int i = 0; i < 8; i++) { data >>= 1;
if (onewire_read_bit()) data |= 0x80;
}
return data;
}
// 讀取 DS18B20 溫度
float read_ds18b20() {
if (!onewire_reset()) return -999.0; // 無裝置
onewire_write_byte(0xCC); // Skip ROM
onewire_write_byte(0x44); // Convert T
HAL_Delay(750); // 等待轉換
if (!onewire_reset()) return -999.0;
onewire_write_byte(0xCC); // Skip ROM
onewire_write_byte(0xBE); // Read Scratchpad
uint8_t tempL = onewire_read_byte();
uint8_t tempH = onewire_read_byte();
// 組合成 16-bit 溫度值
int16_t raw = (tempH << 8) | tempL;
return raw * 0.0625f; // 12-bit 解析度
}
// 使用範例
void main() {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
float temp = read_ds18b20();
printf("溫度: %.2f°C\n", temp);
}
CRC 校驗
DS18B20 回傳的第 9 個 byte 是 CRC 校驗值,用於確保資料完整性:
// OneWire CRC-8 計算
uint8_t onewire_crc8(const uint8_t *data, int len) {
uint8_t crc = 0;
for (int i = 0; i < len; i++) {
uint8_t byte = data[i];
for (int b = 0; b < 8; b++) { uint8_t fb = (crc ^ byte) & 0x01; crc >>= 1;
if (fb) crc ^= 0x8C; // 多項式: x^8 + x^5 + x^4 + 1
byte >>= 1;
}
}
return crc;
}
// 驗證 DS18B20 資料
uint8_t scratchpad[9];
// ... 讀取 9 bytes ...
if (onewire_crc8(scratchpad, 8) == scratchpad[8]) {
// CRC 正確,資料有效
float temp = ((scratchpad[1] << 8) | scratchpad[0]) * 0.0625f;
}
多顆 DS18B20 匯流排注意事項
| 注意事項 | 說明 |
|---|---|
| Pull-Up 電阻 | 一顆 4.7kΩ 即可,多顆時可降為 2.2kΩ |
| 最大數量 | 理論上無限,但電容限制建議 < 20 顆 |
| 接線長度 | 建議 < 30m,過長需加強 Pull-Up |
| 寄生供電 | 多顆時不建議,轉換時電流不足 |
| ROM ID 衝突 | 每顆 DS18B20 有唯一 64-bit ID,不會衝突 |
| 轉換時間 | 9-bit: 93.75ms, 10-bit: 187.5ms, 11-bit: 375ms, 12-bit: 750ms |
總結
OneWire 通訊協定雖然看似老派(Maxim 在 1990 年代推出),但 DS18B20 至今仍是嵌入式領域最受歡迎的溫度感測器之一。它只需要一條 GPIO 和一個電阻就能精確測量溫度,並且可以輕鬆掛載多顆在同一匯流排上。
從 ESP32 的 DallasTemperature 函式庫到 STM32 的純軟體模擬,掌握 OneWire 的底層時序有助於理解嵌入式系統中低速通訊協定的設計哲學——用最少的線,做最多的事。
文章評論