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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...