0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 程式開發
  3. 正文

OneWire 通訊協定完整教學:DS18B20 溫度感測器 ESP32/STM32 實作

2026 年 6 月 8 日 35點熱度 0人點贊 0條評論

什麼是 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(外接或寄生供電)

DS18B20 內部方塊圖

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

接線方式

DS18B20 接線方式

OneWire 匯流排需要一顆 4.7kΩ 上拉電阻(Pull-Up)將 DQ 線拉到 VDD。建議使用外接電源模式(VDD 接 3.3V),寄生供電模式在溫度轉換時需要 Master 強拉 High,不建議多顆並用。

OneWire 通訊協定時序

OneWire 的時序非常精準,所有操作都由 Master(ESP32/STM32)發起:

1. 初始化(Reset + Presence Pulse)

OneWire 初始化時序

每次通訊開始前,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

OneWire Write 時序

寫入 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

OneWire Read 時序

讀取 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 完整通訊流程

以 DS18B20 讀取溫度為例,流程如下:

  1. Reset + Presence:確認裝置存在
  2. 發送 ROM 指令:0xCC(Skip ROM,跳過位址比對,單顆時用)
  3. 發送功能指令:0x44(Convert T,啟動溫度轉換)
  4. 等待:12-bit 解析度下約 750 ms 轉換時間
  5. Reset + Presence:再次初始化
  6. 發送 ROM 指令:0xCC
  7. 發送功能指令:0xBE(Read Scratchpad,讀取暫存器)
  8. 讀取 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 的底層時序有助於理解嵌入式系統中低速通訊協定的設計哲學——用最少的線,做最多的事。

標籤: 教學
最後更新:2026 年 6 月 8 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇
下一篇 >

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang