以下說明當匯流排上存在多個 DS18B20 晶元時, 識別各個 DS18B20 的編號併進行通信的演算法. 其實這是 1-Wire 匯流排的搜索演算法, 當 1-Wire 匯流排上掛接了多個設備時, 匯流排控制端需要通過 ROM Search 命令來判斷匯流排上存在的設備以及獲取他們的8位元組唯一ROM. 1-WI... ...
目錄
- DS18B20數字溫度計 (一) 電氣特性, 寄生供電模式和遠距離接線
- DS18B20數字溫度計 (二) 測溫, ROM和CRC演算法
- DS18B20數字溫度計 (三) 1-WIRE匯流排 ROM搜索演算法和實際測試
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...
示例搜索過程
搜索步驟如下
- 單線匯流排控制端(以下簡稱總控)執行 RESET, 所有的 DS18B20設備(以下簡稱設備)響應這個RESET
- 總控執行 Search ROM 命令
- 總控讀取1個bit. 這時每個設備都會將自己的ROM的第一個bit放到匯流排上, ROM1 和 ROM4 會對匯流排寫0(拉低匯流排), 而 ROM2 和 ROM3 則會對匯流排寫1, 允許匯流排保持高電平. 這時候總控讀取的是0(低電平).
- 總控繼續讀下一個bit, 每個設備會將第一個bit的補碼放到匯流排上, 這時候 ROM1 和 ROM4 寫1, 而 ROM2 和 ROM3 寫0, 因此總控依然讀到一個0, 這時候總控會知道存在多個設備, 並且它們的ROM在這一位上的值不同.
- (說明)從每次的兩步讀取中觀察到的值分別有以下的含義
- 00 有多個設備, 且在這一位上值不同
- 01 所有設備的 ROM在這一位上的值是0
- 10 所有設備的 ROM在這一位上的值是1
- 11 匯流排上沒有設備
- 總控寫入一個bit, 比如寫入0, 表示在後面的搜索中屏蔽 ROM2 和 ROM3, 僅留下 ROM1 和 ROM4
- 總控再執行兩次讀操作, 讀到的值為0,1, 這表示匯流排上所有設備在這一位上的值都是0
- 總控寫入一個bit, 因為值是確定的, 這次寫入的是0
- 總控再執行兩次讀操作, 讀到的值為0,0, 這表示匯流排上還有多個設備, 在這一位上的值不同
- 總控寫入一個bit, 這次寫入0, 這將屏蔽 ROM1, 僅留下 ROM4
- 總控重覆進行三步操作, 讀出 ROM4 剩餘的位, 完成第一次搜索
- 總控再次重覆之前的搜索直到第7位
- 總控寫入一個bit, 這次寫入1, 將屏蔽 ROM4, 僅保留 ROM1
- 總控通過重覆三步操作, 讀出 ROM1 剩餘的位
- 總控再次重覆之前的搜索直到第3位
- 總控寫入一個bit, 這次寫入1, 將屏蔽 ROM1 和 ROM4 僅保留 ROM2 和 ROM3
- 重覆之前的邏輯, 當所有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進行分叉.
遍歷邏輯
在每一輪遍歷中
- 從低位開始, 每一位進行兩次讀, 得到這一位的值和補碼
- 對前面的結果進行判斷
- 如果為11, 說明沒有設備, 直接退出
- 如果為01, 說明這一位都是0, 寫入 Buff, 同時將 Stack 這一位設成 1, 表示這一位已確認
- 如果為10, 說明這一位都是1, 寫入 Buff, 同時將 Stack 這一位設成 1, 表示這一位已確認
- 如果為00, 說明這一位產生了分叉, 需要繼續判斷
- 對分叉的判斷, 與 Split_Point 記錄的值進行比較
- 如果當前位置比已知的分叉點更淺, 說明還沒到該分叉的位置, 繼續設置成 Buff 中上一次使用的值, Stack不變
- 如果當前位置等於分叉點, 說明已經到了上次定好的分叉位置, 上次已經用0分叉過了, 這次就用1進行分叉, 這一位就確認了, 將 Stack 這一位設成 1, 表示已確認
- 如果當前位置比已知的分叉點位置還要深, 說明發現了新的分叉點(例如用1分叉後, 進入了新的子樹, 發現下麵還有分叉), 更新 Split_Point 記錄分叉點位置, 將 Stack 這一位設成 0 (未確認), 用預設的0繼續往下走
- 在這輪遍歷結束後, Buff 就得到了一個新的地址
- 檢查 Split_Point 是否需要往上挪: 在 Stack 上找到 Split_Point 標識的位置, 如果值為1, 則將 Split_Point 設置到最淺的一個0的位置. (例如這次正好在分叉點使用1分叉, 當前點確認了, 而之後又全是確認的情況, 需要將分叉點往上移)
- 結束條件: 和深度遍歷一樣, 每一輪遍歷後分叉點可能會上下變化, 當分叉點的位置為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模擬寄生供電電路, 與上位機只連了兩根線.
實際的測試輸出如下, 第一列輸出的是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 ␍␊
參考
- 單線匯流排搜索演算法 1-WIRE SEARCH ALGORITHM https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/187.html