DS18B20數字溫度計 (三) 1-WIRE匯流排 ROM搜索演算法和實際測試

来源:https://www.cnblogs.com/milton/archive/2022/06/12/16367287.html
-Advertisement-
Play Games

以下說明當匯流排上存在多個 DS18B20 晶元時, 識別各個 DS18B20 的編號併進行通信的演算法. 其實這是 1-Wire 匯流排的搜索演算法, 當 1-Wire 匯流排上掛接了多個設備時, 匯流排控制端需要通過 ROM Search 命令來判斷匯流排上存在的設備以及獲取他們的8位元組唯一ROM. 1-WI... ...


目錄

DS18B20 搜索演算法

以下說明當匯流排上存在多個 DS18B20 晶元時, 識別各個 DS18B20 的編號併進行通信的演算法.

其實這是 1-Wire 匯流排的搜索演算法, 當 1-Wire 匯流排上掛接了多個設備時, 匯流排控制端需要通過 ROM Search 命令來判斷匯流排上存在的設備以及獲取他們的8位元組唯一ROM.

1-WIRE SEARCH ALGORITHM 演算法規則和實現機制

ROM搜索演算法的核心規則, 是在搜索中重覆進行一個簡單的三步操作

步驟1: 讀一次: 得到一位的值

總控讀取1個bit. 這時每個設備都會將ROM當前這一位的bit值放到匯流排上, 如果這位是0, 就會對匯流排寫0(拉低匯流排), 如果這位是1, 則會對匯流排寫1, 允許匯流排保持高電平. 如果兩者都存在, 總控讀取的是0(低電平).

步驟2: 再讀一次: 得到這位的補碼

總控繼續讀一個bit, 這時候每個設備會將ROM當前這一位的bit的補碼放到匯流排上, 如果這位是0就會寫1, 如果這位是1則會寫0, 如果兩者都存在, 總控會讀到一個0, 這樣總控就會知道存在多個設備, 並且它們的ROM在這一位上的值不同.

步驟3: 寫一次: 指定這一位的目標值

總控寫入一個bit, 比如寫入0, 表示在後面的搜索中選擇這一位為0的設備, 屏蔽掉這一位為1的設備

迴圈

匯流排控制端在8位元組ROM的每一位上執行這個三步操作後, 就能知道一個 DS18B20 的 8位元組 ROM 值, 如果匯流排上有多個 DS18B20, 則需要重覆多次.

搜索示例

示例數據

下麵的例子假設匯流排上有4個設備, 對應的ROM值分別為

  • ROM1 00110101...
  • ROM2 10101010...
  • ROM3 11110101...
  • ROM4 00010001...

示例搜索過程

搜索步驟如下

  1. 單線匯流排控制端(以下簡稱總控)執行 RESET, 所有的 DS18B20設備(以下簡稱設備)響應這個RESET
  2. 總控執行 Search ROM 命令
  3. 總控讀取1個bit. 這時每個設備都會將自己的ROM的第一個bit放到匯流排上, ROM1 和 ROM4 會對匯流排寫0(拉低匯流排), 而 ROM2 和 ROM3 則會對匯流排寫1, 允許匯流排保持高電平. 這時候總控讀取的是0(低電平).
  4. 總控繼續讀下一個bit, 每個設備會將第一個bit的補碼放到匯流排上, 這時候 ROM1 和 ROM4 寫1, 而 ROM2 和 ROM3 寫0, 因此總控依然讀到一個0, 這時候總控會知道存在多個設備, 並且它們的ROM在這一位上的值不同.
  5. (說明)從每次的兩步讀取中觀察到的值分別有以下的含義
    • 00 有多個設備, 且在這一位上值不同
    • 01 所有設備的 ROM在這一位上的值是0
    • 10 所有設備的 ROM在這一位上的值是1
    • 11 匯流排上沒有設備
  6. 總控寫入一個bit, 比如寫入0, 表示在後面的搜索中屏蔽 ROM2 和 ROM3, 僅留下 ROM1 和 ROM4
  7. 總控再執行兩次讀操作, 讀到的值為0,1, 這表示匯流排上所有設備在這一位上的值都是0
  8. 總控寫入一個bit, 因為值是確定的, 這次寫入的是0
  9. 總控再執行兩次讀操作, 讀到的值為0,0, 這表示匯流排上還有多個設備, 在這一位上的值不同
  10. 總控寫入一個bit, 這次寫入0, 這將屏蔽 ROM1, 僅留下 ROM4
  11. 總控重覆進行三步操作, 讀出 ROM4 剩餘的位, 完成第一次搜索
  12. 總控再次重覆之前的搜索直到第7位
  13. 總控寫入一個bit, 這次寫入1, 將屏蔽 ROM4, 僅保留 ROM1
  14. 總控通過重覆三步操作, 讀出 ROM1 剩餘的位
  15. 總控再次重覆之前的搜索直到第3位
  16. 總控寫入一個bit, 這次寫入1, 將屏蔽 ROM1 和 ROM4 僅保留 ROM2 和 ROM3
  17. 重覆之前的邏輯, 當所有00讀數都被處理, 說明設備的ROM已經全部被讀取.

總控通過單線匯流排讀取所有設備, 每個設備需要的時間為960 µs + (8 + 3 x 64) 61 µs = 13.16 ms, 識別速度為每秒鐘75個設備.

代碼邏輯

使用代碼實現時, 整體的邏輯是按一個固定的方向(先0後1)深度優先遍歷一個二叉樹.

數據結構

  • 預設一個8位元組數組 Buff 用於記錄路徑(即ROM的讀數)
  • 預設一個8位元組數組 Stack, 用於記錄每一位的值是否確定, 如果確定就是1, 未確定就是0.
  • 預設一個整數變數 Split_Point 用於記錄每一輪搜索中得到的最深分叉點的位置, 下一次到這一位就用1進行分叉.

遍歷邏輯

在每一輪遍歷中

  1. 從低位開始, 每一位進行兩次讀, 得到這一位的值和補碼
  2. 對前面的結果進行判斷
    1. 如果為11, 說明沒有設備, 直接退出
    2. 如果為01, 說明這一位都是0, 寫入 Buff, 同時將 Stack 這一位設成 1, 表示這一位已確認
    3. 如果為10, 說明這一位都是1, 寫入 Buff, 同時將 Stack 這一位設成 1, 表示這一位已確認
    4. 如果為00, 說明這一位產生了分叉, 需要繼續判斷
  3. 對分叉的判斷, 與 Split_Point 記錄的值進行比較
    1. 如果當前位置比已知的分叉點更淺, 說明還沒到該分叉的位置, 繼續設置成 Buff 中上一次使用的值, Stack不變
    2. 如果當前位置等於分叉點, 說明已經到了上次定好的分叉位置, 上次已經用0分叉過了, 這次就用1進行分叉, 這一位就確認了, 將 Stack 這一位設成 1, 表示已確認
    3. 如果當前位置比已知的分叉點位置還要深, 說明發現了新的分叉點(例如用1分叉後, 進入了新的子樹, 發現下麵還有分叉), 更新 Split_Point 記錄分叉點位置, 將 Stack 這一位設成 0 (未確認), 用預設的0繼續往下走
  4. 在這輪遍歷結束後, Buff 就得到了一個新的地址
  5. 檢查 Split_Point 是否需要往上挪: 在 Stack 上找到 Split_Point 標識的位置, 如果值為1, 則將 Split_Point 設置到最淺的一個0的位置. (例如這次正好在分叉點使用1分叉, 當前點確認了, 而之後又全是確認的情況, 需要將分叉點往上移)
  6. 結束條件: 和深度遍歷一樣, 每一輪遍歷後分叉點可能會上下變化, 當分叉點的位置為0時, 說明遍歷結束

代碼實現

搜索邏輯的C語言代碼實現

/**
 * buff, stack 和 split_point 都是全局變數, 由外部傳入
 * 
 */
uint8_t DS18B20_Search(uint8_t *buff, uint8_t *stack, uint8_t split_point)
{
    uint8_t len = 64, pos = 0;
    /* 分叉點的初始值應該用0xFF, 如果輸入參數為0, 將其設為0xFF */
    split_point = (split_point == 0x00)? 0xFF : split_point;
    /* Reset line */
    DS18B20_Reset();
    /* Start searching */
    DS18B20_WriteByte(ONEWIRE_CMD_SEARCHROM);

    // len 初始值為64, 對 8 位元組 ROM 做一個遍歷
    while (len--)
    {
        // 兩次讀, 讀取這一位bit值和補碼
        __BIT pb = DS18B20_ReadBit();
        __BIT cb = DS18B20_ReadBit();
        if (pb && cb) // 都是1, 表示沒有設備
        {
            return 0;
        }
        else if (pb) // pb=1, cb=0, 說明這一位為1
        {
            // 在buff上記錄這一位
            *(buff + pos / 8) |= 0x01 << (pos % 8);
            DS18B20_WriteBit(SET);
            // 在stack上將這一位記錄為1, 表示已確認
            *(stack + pos / 8) |= 0x01 << (pos % 8);
        }
        else if (cb) // pb=0, cb=1, 說明這一位為0
        {
            // 在buff上記錄這一位
            *(buff + pos / 8) &= ~(0x01 << (pos % 8));
            DS18B20_WriteBit(RESET);
            // 在stack上將這一位記錄為1, 表示已確認
            *(stack + pos / 8) |= 0x01 << (pos % 8);
        }
        else // 出現分叉點
        {
            if (split_point == 0xFF || pos > split_point)
            {
                // 比上次記錄的點更深, 出現了新的分叉點
                *(buff + pos / 8) &= ~(0x01 << (pos % 8));
                DS18B20_WriteBit(RESET);
                // 在stack上將這一位記錄為0, 表示未確認
                *(stack + pos / 8) &= ~(0x01 << (pos % 8));
                // 記錄新的分叉點位置
                split_point = pos;
            }
            else if (pos == split_point)
            {
                // 到達了上次記錄的分叉點位置, 這次使用1繼續往下走
                *(buff + pos / 8) |= 0x01 << (pos % 8);
                DS18B20_WriteBit(SET);
                // 在stack上將這一位記錄為1, 表示已確認
                *(stack + pos / 8) |= 0x01 << (pos % 8);
            }
            else
            {
                // 這個分叉點處於中間位置, 還沒到處理時間, 繼續使用上次記錄的值
                DS18B20_WriteBit(*(buff + pos / 8) >> (pos % 8) & 0x01);
            }
        }
        pos++;
    }
    // 重新定位分叉點, 將其指向到stack上最後一個未確認的位置
    while (split_point > 0 && *(stack + split_point / 8) >> (split_point % 8) & 0x01 == 0x01) split_point--;
    return split_point;
}

調用方法

sp = 0;
do
{
    // ROM search and store ROM bytes to addr
    sp = DS18B20_Detect(addr, Search_Stack, sp);
    // Print the new split point and address
    UART1_TxHex(sp);
    UART1_TxChar(' ');
    PrintArray(addr, 0, 8);
    UART1_TxString("\r\n");
} while (sp);

運行實測

對一個掛載了19個 DS18B20 的 1-Wire 匯流排進行實際測試, 用1uF電容和1N4148模擬寄生供電電路, 與上位機只連了兩根線.

DS18B20搜索測試

實際的測試輸出如下, 第一列輸出的是Split_Point的值, 表示當前的分叉深度, 後半部分是這個DS18B20採樣的溫度值和CRC

0F 2854FD96F0013C1A........B20155057FA5A5669A CRC:9A ␍␊
0D 28D44496F0013C4C........BD0155057FA5A56660 CRC:60 ␍␊
0B 28744196F0013CC2........B50155057FA5A5664A CRC:4A ␍␊
09 280CCB96F0013C8D........B20155057FA5A5669A CRC:9A ␍␊
0B 28D2A396F0013C75........B50155057FA5A5664A CRC:4A ␍␊
0D 288AFB48F6973CFD.......BE0155057FA581665F CRC:5F ␍␊
0C 28AA8196F0013C37........B40155057FA5A56609 CRC:09 ␍␊
0A 283A9096F0013C37........B80155057FA5A56636 CRC:36 ␍␊
08 283E5996F0013C3A........B80155057FA5A56636 CRC:36 ␍␊
0B 2811E896F0013C2A........B70155057FA5816636 CRC:36 ␍␊
0C 28C90196F0013C66........B40155057FA5A56609 CRC:09 ␍␊
0D 28597196F0013CBA........B80155057FA5A56636 CRC:36 ␍␊
0A 28794648F65D3C26........B60155057FA5A5668F CRC:8F ␍␊
0B 2865BB96F0013CB5........BD0155057FA5A56660 CRC:60 ␍␊
0C 28ADCB96F0013CE6........BA0155057FA581664A CRC:4A ␍␊
09 281D1648F64B3CEA.......BD0155057FA5A56660 CRC:60 ␍␊
0B 2843E896F0013C6A........BB0155057FA5A566F3 CRC:F3 ␍␊
0A 289B0896F0013CD5........B70155057FA5816636 CRC:36 ␍␊
00 28EF5C96F0013C1B........BE0155057FA5A566A5 CRC:A5 ␍␊

參考


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 工作很多年後,才發現有很多工具類庫,可以大大簡化代碼量,提升開發效率,初級開發者卻不知道。而這些類庫早就成為了業界標準類庫,大公司的內部也都在使用,如果剛工作的時候就有人告訴我使用這些工具類庫,該多好! 一塊看一下有哪些工具類庫你也用過。 1. Java自帶工具方法 1.1 List集合拼接成以逗號 ...
  • 又到每天Python小技巧分享的時候了,今天給大家分享的是怎麼樣去爬取清純小姐姐照片(沒有人會拒絕美女吧,小聲說),這篇文章好像有點刺激,未成年的小伙伴就不要進來了。快來看看這些清純的小姐姐的容顏,話不多說,上教程。 先來看看效果圖 不好意思,圖片有點辣眼睛,被攔截了,還沒有還給我..... imp ...
  • 介紹了 wait notify notifyAll park unpark ReentrantLock等相關知識 ...
  • 第一種方案 使用itext填充靜態PDF模板 然後生成PDF新文件 1 使用Adobe Acrobat 編輯原PDF 添加文本域 itext依賴 <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId ...
  • 一個工作了3年的粉絲私信我,在面試的時候遇到了這樣一個問題。 ”請說一下ReentrantLock的實現原理“,他當時根據自己的理解零零散散的說了一些。 但是似乎沒有說到關鍵點上,讓我出一期說一下回答思路。 好吧,關於這個問題,我們來看看普通人和高手的回答。 普通人: ReentrantLock的一 ...
  • 一、OpenFeign介紹 OpenFeign是⼀種聲明式,模版化的HTTP客戶端。使⽤OpenFeign進⾏遠程調⽤時,開發者完全感知不到這是在進⾏遠程調⽤,⽽是像在調⽤本地⽅法⼀樣。使⽤⽅式是註解+接⼝形式,把需要調⽤的遠程接⼝封裝到接⼝當中,映射地址為遠程接⼝的地址。在啟動SpringClou ...
  • 一、創建新的database clickhouse創建資料庫的語法幾乎和其他的關係型資料庫是一樣的,區別就是clickhouse存在集群cluster和庫引擎engine的概念,可以根據需要進行指定。如果沒有特殊需求,預設即可。 CREATE DATABASE [IF NOT EXISTS] db_ ...
  • 異常和正常代碼性能旗鼓相當,但是全局過濾器對性能影響比較大,大概降低了60%左右,全局過濾器走了管道,但是這跟微軟官方的性能優化又有衝突,想必微軟官方也是出於對全局過濾器異常處理的考慮吧。同時對於添加了業務的情況下,這個降低會被稀釋,沒去做壓測對比哈,正常用戶體量還不至於被這個給影響到穩定性。所以怎... ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...