DS18B20 多顆 1‑Wire 並聯:Raspberry Pi Pico 2 W 實作指南(含 C 範例)

本文示範如何 同時連接與讀取多顆 Analog Devices DS18B20 溫度感測器。DS18B20 採用 1‑Wire 通訊協定,具 CRC 資料完整性檢查。這種 1-Wire 介面利用數據線本身的能量,省去外接 VDD 供電線。每顆裝置擁有 唯一的 64-Bit 雷射燒錄 ROM Code

image

因此 多顆感測器可共享同一條 1‑Wire 匯流排,非常適合大範圍的分散式溫度量測,例如:環境監控、樓宇控制、機台/製程監控與工業設備。

DS18B20 特色重點(Features)

  • 1‑Wire® 單埠通訊:只需一個 I/O 腳即可通訊
  • 整合感測器與 EEPROM:降低系統用料
  • 量測範圍:−55°C ~ +125°C(−67°F ~ +257°F)
  • 精度:±0.5°C(−10°C ~ +85°C)
  • 解析度可程式化:9/10/11/12 Bits(預設 12 Bits)
  • 寄生供電模式:只需 DQ 與 GND 兩腳即可運作,無需外接 VDD
  • 多點佈建:支援 Multidrop 並聯
  • 唯一序號:每顆具 64‑bit ROM(支援搜尋/配對)
  • 非揮發性(NV)警報設定,以及警報搜尋指令,可辨識溫度超出預設限值的設備
  • 封裝:8‑pin SO(150 mil)、8‑pin µSOP、3‑pin TO‑92

示範: 使用 TO‑92 封裝 的三顆 DS18B20

DS18B20 溫度感測器的框圖如下:

DS18B20 工作原理與資料格式

DS18B20 的核心功能是其直接數位式溫度感測器。

  • 解析度:9/10/11/12 bits(透過配置暫存器), 分別對應 0.5/0.25/0.125/0.0625°C上電預設 12 bits
  • 輸出單位:溫度以 攝氏(°C) 標定;若要 華氏(°F) 請自行轉換 (對於華氏溫度應用,必須使用查找表或轉換例程。)
  • 溫度資料格式:溫度資料以 16 bit 符號擴展的二進位補碼數形式儲存在溫度暫存器中,如下表所示。

如果需要,DS18B20 也可以採用傳統的供電方式,即將外部電源連接到 VDD 接腳,如下圖所示。

DS18B20 的內部記憶體佈局如下圖所示,

image

此記憶體由一個 SRAM 暫存區和一個非揮發性 EEPROM 組成,其中 EEPROM 儲存高低警報觸發暫存器 (TH) 和配置暫存器。請注意,如果未使用 DS18B20 警報功能,則 TH 和 TL 暫存器可用作通用記憶體。

匯流排主控器使用 ROM 指令 (0x55) 尋址要通訊的 DS18B20 後,即可發出 DS18B20 功能指令。這些命令允許主控器對 DS18B20 的暫存區記憶體進行讀寫操作、啟動溫度轉換以及確定電源模式。 DS18B20 功能指令如下表所示。

示範配置

以下示範中使用了一些配置,包括

  • Raspberry Pico 2 W 評估板
  • 透過 Pin 36 提供 3.3V 電壓,Pin 38 提供參考電壓
  • 三顆 DS18B20 溫度感測器
  • 在 GPIO 15(Pin 20)上連接 3.3V 上拉電阻,作為 1-Wire 資料匯流排

為了示範 DS18B20 溫度感測器的同作能力,我們在 1-Wire 匯流排上放置了三顆 DS18B20 溫度感測器。使用 Raspberry Pi Pico 2W 平台,並利用其內部 PIO 狀態機 onewire_library.pio 創建了自訂的 1-Wire 通訊協定接口,並透過 C SDK 中的 C 應用程式進行存取。本示範中使用:

onewire_library.pio PIO 狀態機定義檔

;
; Copyright (c) 2023 mjcross
;
; SPDX-License-Identifier: BSD-3-Clause
;

; Implements a Maxim 1-Wire bus with a GPIO pin.
;
; Place data words to be transmitted in the TX FIFO and read the results from the
; RX FIFO. To reset the bus execute a jump to 'reset_bus' using the opcode from
; the provided function.
;
; At 1us per cycle as initialised below the timings are those recommended by:
; https://www.analog.com/en/technical-articles/1wire-communication-through-software.html
;
; Notes:
;   (1) The code will stall with the bus in a safe state if the FIFOs are empty/full.
;   (2) The bus must be pulled up with an external pull-up resistor of about 4k.
;       The internal GPIO resistors are too high (~50k) to work reliably for this.
;   (3) Do not connect the GPIO pin directly to a bus powered at more than 3.3V.

.program onewire
.side_set 1 pindirs

PUBLIC reset_bus:
        set x, 28       side 1  [15]    ; pull bus low                          16
loop_a: jmp x-- loop_a  side 1  [15]    ;                                  29 x 16
        set x, 8        side 0  [6]     ; release bus                            7
loop_b: jmp x-- loop_b  side 0  [6]     ;                                    9 x 7

        mov isr, pins   side 0          ; read all pins to ISR (avoids autopush) 1
        push            side 0          ; push result manually                   1
        set x, 24       side 0  [7]     ;                                        8
loop_c: jmp x-- loop_c  side 0  [15]    ;                                  25 x 16

.wrap_target
PUBLIC fetch_bit:
        out x, 1        side 0          ; shift next bit from OSR (autopull)     1
        jmp !x  send_0  side 1  [5]     ; pull bus low, branch if sending '0'    6

send_1: ; send a '1' bit
        set x, 2        side 0  [8]     ; release bus, wait for slave response   9
        in pins, 1      side 0  [4]     ; read bus, shift bit to ISR (autopush)  5
loop_e: jmp x-- loop_e  side 0  [15]    ;                                   3 x 16
        jmp fetch_bit   side 0          ;                                        1

send_0: ; send a '0' bit
        set x, 2        side 1  [5]     ; continue pulling bus low               6
loop_d: jmp x-- loop_d  side 1  [15]    ;                                   3 x 16
        in null, 1      side 0  [8]     ; release bus, shift 0 to ISR (autopush) 9
.wrap
;; (17 instructions)


% c-sdk {
static inline void onewire_sm_init (PIO pio, uint sm, uint offset, uint pin_num, uint bits_per_word) {

    // create a new state machine configuration
    pio_sm_config c = onewire_program_get_default_config (offset);

    // Input Shift Register configuration settings
    sm_config_set_in_shift (
        &c,
        true,           // shift direction: right
        true,           // autopush: enabled
        bits_per_word   // autopush threshold
    );

    // Output Shift Register configuration settings
    sm_config_set_out_shift (
        &c,
        true,           // shift direction: right
        true,           // autopull: enabled
        bits_per_word   // autopull threshold
    );

    // configure the input and sideset pin groups to start at `pin_num`
    sm_config_set_in_pins (&c, pin_num);
    sm_config_set_sideset_pins (&c, pin_num);

    // configure the clock divider for 1 usec per instruction
    float div = clock_get_hz (clk_sys) * 1e-6;
    sm_config_set_clkdiv (&c, div);

    // apply the configuration and initialise the program counter
    pio_sm_init (pio, sm, offset + onewire_offset_fetch_bit, &c);

    // enable the state machine
    pio_sm_set_enabled (pio, sm, true);
}

static inline uint onewire_reset_instr (uint offset) {
    // encode a "jmp reset_bus side 0" instruction for the state machine
    return pio_encode_jmp (offset + onewire_offset_reset_bus) | pio_encode_sideset (1, 0);
}
%}

main.c 範例


/**
 * Copyright (c) 2023 mjcross
 *
 * SPDX-License-Identifier: BSD-3-Clause
**/

#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"

#include "onewire_library.h"    // onewire library functions
#include "ow_rom.h"             // onewire ROM command codes
#include "ds18b20.h"            // ds18b20 function codes

// Demonstrates the PIO onewire driver by taking readings from a set of
// ds18b20 1-wire temperature sensors.

int main() {
    stdio_init_all();

    PIO pio = pio0;
    uint gpio = 15;

    OW ow;
    uint offset;
    // add the program to the PIO shared address space
    if (pio_can_add_program (pio, &onewire_program)) {
        offset = pio_add_program (pio, &onewire_program);

        // claim a state machine and initialise a driver instance
        if (ow_init (&ow, pio, offset, gpio)) {

            // find and display 64-bit device addresses
            int maxdevs = 10;
            uint64_t romcode[maxdevs];
            int num_devs = ow_romsearch (&ow, romcode, maxdevs, OW_SEARCH_ROM);

            printf("Found %d devices\n", num_devs);      
            for (int i = 0; i < num_devs; i += 1) {
                printf("\t%d: 0x%llx\n", i, romcode[i]);
            }
            putchar ('\n');

            while (num_devs > 0) {
                // start temperature conversion in parallel on all devices
                // (see ds18b20 datasheet)
                ow_reset (&ow);
                ow_send (&ow, OW_SKIP_ROM);
                ow_send (&ow, DS18B20_CONVERT_T);

                // wait for the conversions to finish
                while (ow_read(&ow) == 0);

                // read the result from each device
                for (int i = 0; i < num_devs; i += 1) {
                    ow_reset (&ow);
                    ow_send (&ow, OW_MATCH_ROM);
                    for (int b = 0; b < 64; b += 8) {
                        ow_send (&ow, romcode[i] >> b);
                    }
                    ow_send (&ow, DS18B20_READ_SCRATCHPAD);
                    int16_t temp = 0;
                    temp = ow_read (&ow) | (ow_read (&ow) << 8);
                    printf ("\t%d: %f C", i, temp / 16.0);
                    
                    //Convert from C to F (Simple conversion for test purposes only)
                    float K1 = 1.8;
                    float K2 = 32.0;
                    float temp_F = temp*K1/16.0 + K2;
                    printf ("\t%d: %f F", i, temp_F);
                    
                }
                putchar ('\n');
            }
            
        } else {
            puts ("could not initialise the driver");
        }
    } else {
        puts ("could not add the program");
    }

    while(true);
}

編譯與燒錄

程式編譯後依下列步驟燒錄到 Raspberry Pico 2 W 中:

DigiKey_Coffee_Cup # make -j8
DigiKey_Coffee_Cup # picotool load pio_onewire.uf2

邏輯分析與協定觀察

SparkFun 邏輯分析儀 觀測 1‑Wire 協議。如下所示。重新啟動 Raspberry Pico 2 W 後,定期輪詢的 3 個 Analog Devices DS18B20 溫度感測器的讀數如下所示。

透過 minicom 終端和上方所示的 Sparkfun 邏輯分析儀捕獲的數據,確認了 3 個 Analog Devices DS18B20 溫度感測器的 64-bit 雷射刻錄 ROM 代碼。接下來這張來自先前連結中設定的 Sparkfun 邏輯分析儀的截圖,放大顯示了 1-Wire 網路層(類似於內建的 Analog Devices DS28EA00 溫度感測器協定解碼器)、重設/存在偵測過程以及 ROM 指令 (0x55)(只有與 64-bit ROM 程式碼序列是完全匹配裝置從裝置上從裝置上發出的脈衝。此外,下方還顯示了 3 個共用 1-Wire 匯流排的 Analog Devices DS18B20 溫度感測器中被查詢感測器的回應。

Sparkfun 邏輯分析儀內建 1-Wire(link layer)和 1-Wire(network layer)接口,本示範中使用的是類似 Analog Devices DS28EA00 溫度感測器的 1-Wire 協定解碼器,如上圖所示。 Sparkfun 邏輯分析儀可依需求簡化設計、測試和故障排除流程。

本示範展示如何使用 Raspberry Pico 2W 和 C SDK,透過 1-Wire 線路連接 3 個 Analog Devices DS18B20 溫度感測器。 DS18B20 溫度感測器是分散式溫度感測設計的理想選擇,特別適用於對系統控制線數量有限制的應用。其寄生功率模式僅需 2 個 Pin (DQ 和 GND)即可運作,簡化了具有多點連接的分佈式溫度感測應用。這款功能強大的 Analog Devices DS18B20 溫度感測器可從 DigiKey 購買。

與 DS28EA00 的比較與延伸

若需要更強韌的 1‑Wire 介面與更多 I/O 彈性,可考慮 DS28EA00(具介面遲滯與假電子信號 Glitch 濾波、兩個通用可程式 PIO Pin、Chain State Transition 可偵測網路中裝置的物理順序),用於更複雜的多節點場景。