DS18B20 1-WIRE ROM搜索算法詳解

轉自:http://blog.sina.com.cn/s/blog_57ad1bd20102uxxw.html

1-WIRE搜索算法詳解(1)

 

0前言

美信公司(http://www.maximintegrated.com/cn)生產了許多1-Wire®器件產品,硬件電路極致簡單,而相應軟件就顯得複雜。美信網站的《應用筆記187介紹了單線ROM搜索算法並提供了TMEX API測試程序的源代碼,該算法較爲複雜,而且是通用於多平臺windows\JAVA等)的API,無法直接在KeilC上調試並寫入單片機。本人在學習理解其算法後,適當修改源代碼,並作上了詳細的註釋和圖解,代碼在KeilC環境下調試通過,並在一個掛接4DS18B20的小型1-Wire環境中測試成功,順利獲取4ROM碼。現把算法分析及測試過程成文如下,其中算法部分保留了大部分AN187的內容,但作了許多修改和補充,原文可參見http://www.maximintegrated.com/cn/app-notes/index.mvp/id/187本文介紹的搜索算法及源代碼,對任何現有的或將要推出的1-Wire器件都是有效的。

 

1測試環境及圖示

硬件連接:4DS18B20並接,MCU爲笙泉MG82G516仿真芯片(其他普通51系列都可,仿真芯片可以方便調試),軟件爲Keil 3

DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(1)

1.    4DS18B20在麪包板上


DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(1)

2.笙泉THO65B+仿真器(可仿真51芯片)



DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(1)

3.Saleae Logic分析儀



DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(1)

 

4.全家福(開發板僅安裝芯片及供電用)

 

1中,4DS18B20並接,紅黃黑三線爲電源、信號、地,綠色線通黃色線,接邏輯分析儀。原來邏輯分析儀從單片機端採集1-Wire信號,但實測中主機拉高電平(從機低電平)時,邏輯分析儀採集到一個尖刺脈衝,造成分析儀識別1-Wire協議失誤;後將採集線接到從機端,信號正常。

2是笙泉THO65B+仿真器,USB接口,可仿真51系列,方便程序調試。該仿真器芯片型號爲MPC82G516,類似於STC15F2K60S2,爲1T單片機,片上64KB Flash, 60KB程序存儲器, 3KB IAP存儲器, 1KB ISP 引導程序空間,以及1KRAM

3爲邏輯分析儀,十分小巧,支持8路信號輸入,最高採樣速率24M,實際一般使用16MUSB接口,附送軟件。圖4爲開發板,本例中主要功能僅爲提供CPU插座及供電,1-Wire器件連接是相當簡單的。

 

 再給出一個用邏輯分析採集到的ROM搜索的全過程相關圖片,有了這個分析儀,就可以十分方便地讀出搜索到的ROM碼,還能通過程序執行時產生“波形”分析調試代碼,也可以方便的測定延時函數的時間。

DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(1)

 

DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(1)


DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(1)


 

2   1-Wire®器件的ROM

Maxim1-Wire®器件都帶有一個64位的唯一註冊碼,生產時通過激光刻蝕在內部只讀存儲器內(ROM),用於在1-Wire網絡中通過1-Wire主機對其尋址。如果1-Wire網絡中從機器件數量不定且ROM碼未知,則可採用搜索算法查找這些碼。下表是64Bit ROM碼的組成示意:
MSB表示Most Significant Bit最高有效位,LSB表示Least Significant Bit最低有效位)       

MSB                        64ROM                          LSB

8CRC

MSB              LSB

48位序列號

MSB              LSB

8位家庭碼

MSB               LSB

搜索順序是從低位到高位的,如下:

64位←——————————————————————第1

5. 64位唯一的ROM註冊碼

 

 DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(1)

 

6、通過邏輯分述儀採集到的1-Wire信號

圖片說明:

1. 上圖是邏輯分析儀在ROM搜索到1ROM1-Wire總線上採集到的信號,可見搜索耗時15ms

2. 因低位先傳送,邏輯分析儀上顯示低位在左邊;

3. 信號自左到右分別爲:復位R、存在脈衝、主機發搜索命令8位、從機回覆64ROM碼(包括家族碼0x2848ROM8CRC碼)

4. 分析軟件識別出了1-Wire協議,在信號頂部標示了內容和數位,閱讀時相當直觀。

5. 將圖6中家族碼的部分放大如圖7

DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(1)

 

7. 搜索過程中採集的家族碼部分放大圖

圖片說明

1.    家族碼一共爲8位,圖中有3通道信號,最上面第1通道從1-Wire總線採集,第2通道是“1寫”程序時通過轉換指定單片機引腳得到(進入該程序時置位,退出前復位);第3路爲“2讀”信號;均爲高電平;

2.    識別8位“一寫”代碼可見從左到右爲00010100,反過來就0010100028H

3.    如何識別1-Wire讀寫的時序,請參見1-Wire技術資料或DS18B20數據表資料,本文不描述。

 

 

3  1-Wire搜索協議

搜索算法採用的是二叉樹型結構,搜索過程沿各分節點進行,直到找到器件的ROM碼即葉子爲止;後續的搜索操作沿着節點上的其它路徑進行,按照同樣的方式直到找到總線上的所有器件代碼。本文後面提供了一個簡單示例:4ROM,每個僅取4位,構造了一棵二叉樹,並在此示例上推導出搜索算法的許多細節及實現。

 

搜索算法首先通過復位(reset)和在線應答脈衝(presence pulse)時隙將1-Wire總線上的所有器件復位;成功地執行該操作後,發送1 個字節的搜索命令;搜索命令使1-Wire器件準備就緒、開始進行搜索操作。

 

搜索命令分爲兩類:標準搜索命令(F0 hex)用來搜索連接到網絡中所有器件;報警或有條件搜索命令(EC hex)只用來搜索那些處於報警狀態下的器件,這種方式縮小了搜索範圍,可以快速查找到所需要注意的器件。本文附帶代碼僅使用了標準搜索命令。

 

搜索命令發出之後,開始實際的搜索過程。首先總線上的所有從機器件同時發送ROM(也叫註冊碼)中的第一位(最低有效位) (參見1)與所有的1-Wire通信一樣,無論是讀取數據還是向從機器件寫數據,都由1-Wire主機啓動每一位操作。按照1-Wire協議設置特性,當所有從機器件同時應答主機時,結果相當於全部發送數據位的邏輯AND;從機發送其ROM碼的第一位後,主機啓動下一位操作、接着從機發送第一位數據的補碼;從兩次讀到的數據位可以對ROM碼的第一位做出幾種判斷(參見下表)上述二次讀取1-Wire總線上當前位ROM碼的正碼和反碼的操作,本文簡述爲“二讀”

 

1. 檢索信息位

主機讀一位

從機提供當前位正碼

主機再讀一位

從機提供該位反碼

二讀後可以判斷獲得的信息

注意:主線讀到的是各個響應從機的“線與”邏輯

0

0

連接器件的該位有01,這是一個差異位(混碼位)

0

1

連接器件的該位均爲0

1

0

連接器件的該位均爲1

1

1

沒有器件與總線相連

 

按照搜索算法的要求,“二讀”後1-Wire主機必須向總線上的從機發回一個指定位,稱爲“一寫”;如果從機器件中ROM碼的當前位的值與該數據位匹配,則繼續參與搜索過程;若從機器件的當前位與之不匹配,則該器件轉換到休眠狀態,直到下一個1-Wire復位信號到來被再次喚醒。其餘63ROM碼的搜索依然按照這種讀兩位寫一位的模式進行重複操作。

 

按照這種搜索算法進行下去,最終除了一個從機器件外所有從機將進入等待狀態,經過一輪檢測,就可得到最後保留(未進入等待狀態)器件的ROM碼。在後續搜索過程中,選用不同的路徑(或分支)來查找其它器件的ROM碼。需要注意的是本文ROM碼的數據位用第1(最低有效位)到第64 (最高有效位)表示,而不是第0位到第63位的模式;這樣設置允許將差異位置計數器初始值置爲0,爲以後的比較提供了方便。

 

上述過程可簡述爲“二讀一寫”,“二讀”結果爲“00”,主機就判定這一位是“差異位”,也可稱爲“衝突位”“混碼位”。舉例來說,“二讀”就相當於“老師問名”,設想學生的名字就是64ROM碼,老師第1問時學生反應爲:“1的不吭聲,0就高喊到”;第2問時學生反過來:“0的不吭聲,1的高喊到”,老師通過二次答覆就可分辨:“不吭聲+不吭聲”表示一個學生也沒有;“不吭聲+到”表示大家都是1,“到+不吭聲”表示大家都是0;而“到+到”收表示有01。試想,如果有01,只要有1個喊到,老師就能聽到,這種合成效果就是所謂的“線與”,當然這裏應稱爲“聲與”。

 

搜索到“差異位”時,主機如何確定搜索方向(0還是1),這是本算法的核心問題,這也是典型的“二叉樹遍歷”算法。或許有人會提出一種“笨”算法,就是窮舉64位組合,從000…000111…111遍歷,有應答的從機就會被一個一個找出來,這確實是一種簡單的“笨”算法,在許多地方很有用,但對於64ROM來說,假設一位的訪問需要100us64位則需要6.4ms,窮舉全部ROM碼有264=1844億億,極端的遍歷時間爲37億年,這確實是一種無法實現的笨辦法。


下表列出了1-Wire主機和從機的搜索過程

Master

Slave

發送“復位”信號

產生“存在”脈衝

發送“搜索”命令

全體器件進入“應付搜索”狀態,不斷地根據主機信號作出反應

從總線讀取1位“線與”

全體器件發送64ROM碼的第1

從總線讀取1位“線與”

全體器件發送64ROM碼的第1位的反碼

向總線寫1位(按算法)

器件們接收此位後,凡與ROM碼的第1位不符的器件進入休眠狀態

64ROM碼後續位

器件發後續位ROM

64ROM碼後續位的反碼

器件發後續位ROM碼的反碼

寫後續各位

器件們接收此位後,凡與ROM碼的當前位不符的器件進入休眠狀態.

 

從上表可以看出:如果所有總線上的器件在當前位具有相同值,那麼只有一條分支路徑可選;總線上沒有器件響應的情況是一種異常狀態,可能是要查找的器件在搜尋過程中與1-Wire總線脫離。如果出現這種情況,應中止搜索,併發出1-Wire復位信號起始新的搜索過程。如果當前位既有0也有1,這種情況稱爲位值差異,它對在後續搜索過程中查找器件起關鍵作用。搜索算法指定在第一輪查詢中若出現差異(數據位/補碼 = 0/0),則選用‘0’路徑。注意:這一點是由本文檔中介紹的特定算法決定的,其它算法中或許首先選用‘1’路徑。算法需要記錄最後一次值差異的位置以供下一次搜索使用。

 

本文介紹的搜索算法是一個通用的程序,Maxim工程師極巧妙地設計了一些變量,只要在搜索前設置這些變量的初始值,程序就能完成不同的搜索功能,下文中會列出這些變量,但理解其奧祕需要花點時間。比如程序對最初8ROM中出現的最後一次位差異設置了一個專門的變量保持跟蹤,因爲64位註冊碼的前8位是家族碼,利用此變量可以在器件的搜索過程中可以按照其家族碼進行分類。運用該變量可以有選擇性地跳過1-Wire器件的整個分組。如需進行選擇性的搜索,可參考關於高級變量搜索的詳細解釋。

 

64ROM碼中還包含8循環冗餘校驗(CRC)CRC值用於驗證是否搜索到正確的ROM碼。ROM碼的排列如圖1所示。關於CRC校驗的原理,內容豐富到需要專文論述,這裏不詳述,可參見Maxim網站相關筆記。

 

(未完待續)

1-Wire搜索算法詳解(2)

 

4 實例及算法分析

要理解算法,或制定算法,我們需要通過一個實例來解釋:

 

 

        ROM示例(僅列出前4位)

ROM編號

1234……

ROM1

0011……

ROM2

1010……

ROM3

1111……

ROM4

0001……

   DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(2)
                                                       
84ROM的實例

 

假設1-Wire掛有4ROM如上表, 這裏ROM碼僅設4位,編號從14,而實際器件ROM位序號從164。右圖則是按“先01”遍歷順序所構造的二叉樹,可見第一次遍歷得到ROM40001,第二次遍歷得到ROM10011,依次類推。圖中打有的位置就是分叉點,即差異位(或稱爲混碼位)。

 

現在根據上例觀察各次遍歷,提煉出算法及實現細節:

1.第一次遍歷時,第1Bit1時遇到第一個差異位,按左序遍歷即先01的順序,我們選擇0(稱爲方向0);Bit2不是差異位,算法根據“二讀”後方便地選0Bit3又是差異位,再選擇方向0Bit4也不是差異位。遍歷結果得到ROM4。這裏可得出結論1:凡遇到新的差異位,選擇0

 

2.第二次遍歷,按實例分析,Bit1處再選擇方向0Bit20Bit3不能再選0了,改選1Bit41;得到ROM1。問題隨即產生:問題1Bit1爲何走0?此處確爲差異位,但已不是首次經過,不能套用結論1。問題2Bit3處爲何改走1?簡單分析問題2可得出結論2:凡上次遍歷時最後一個走0的差異位本次應走1。很明顯,“上次遍歷時最後一個差異位”下的左分支已經走過,這次應該往右走了。再回答問題1可以得到結論3:凡上次遍歷時最後一個走0的差異位之前的差異位仍按上次遍歷的老路走。這個結論比較拗口,觀察實例來解釋:Bit3是差異位,右分支還沒走過,現在我要走Bit3下邊的右分支,Bit3之前當然是按上次路線來走了。

 

3.然後是否就可以進行第三次遍歷了呢?且慢,有一個重要的問題還沒處理。第二次遍歷前,那個“上次遍歷時最後一個走0的差異位”應該Bit3,我們先設置一個變量LastDiscripancy來記住這個值,即LastDiscripancy=3;從前面的分析我們知道,這個變量的值是決定本次遍歷時算法在哪個位置改道的依據,而且這個變量在本次遍歷全過程中應該保持不變的,如果在該節點前或後有任意多少的差異位,均按結論1(新差異位)和結論3(老差異位)來處理。也可以理解成結論2在一次遍歷中必須適用且僅適用一次,這也就是每次遍歷都能找出一個新的ROM的核心所在。那麼第二次遍歷後,變量LastDiscripancy應該指向哪個節點呢?直接觀察就可發現應該指向Bit1

 

4.那麼算法中如何實現每次遍歷後變量LastDiscripancy的更新呢?要知道這個指針隨着每次遍歷在或上或下地移動,如第一次遍歷後指向Bit3,第二次後指向Bit1,第三次後指向Bit2。還有,第一次開始遍歷前、第四次遍歷後該變量又指向哪裏呢?由於實際ROM多達64位,從機如果數據多的話,差異位也會隨之增加,所以算法再引入一個變量Last_Zero,在每次遍歷前設爲0,即指向起始位置前,然後遍歷ROM碼的1-64位時,該變量始終指向最後一個走0的差異位,等到遍歷完成後,將該變量的值賦給LastDiscripancy即可。這樣我們再觀察實例的第二次遍歷,Last_Zero遍歷前爲0Bit1時,符合“走0的差異位”,於是Last_Zero=1,後續Bit3儘管是差異位,但不走0,所以遍歷完成後,該值還是1,最後交給LastDiscripancy,自己又指向0,爲第三次遍歷作好準備。

 

5.第三次遍歷就變得順理成章:LastDiscripancy=1Bit1處需運用結論2,算法改走1。而且第三次遍歷讓Bit2成爲了“最後一個走0的差異位”,第四次遍歷就在此處改走1了。第四次遍歷搜索到最後一個rom,但還得告訴程序結束信號,從而退出搜索。結束信號用什麼判定?4ROM循環4次?當然不是,上述表圖只是舉例,實際總路線上掛接多少從機是未知的。答案還是利用變量LastDiscripancy來判定,開始搜索前自然有LastDiscripancy=0,但一旦進入搜索程式,每次遍歷後該變量總是指向ROM碼中間可能的某一位(1-64),直至遍歷到最後一個從機,這時必定是所有差異位均走1,如果Last_Zero保持爲初始值0,遍歷過程中未作修改,遍歷完成後LastDiscripancy=Last_Zero=0。以此爲條件可判定搜索全部完成。

 

6.綜上,當算法執行到一個差異位時,需判斷區分爲三種情況:首次遇到型、上次最後走0型、上次非最後走0型。可以通過二個變量的比較來表徵這三種情況,即比較當前搜索的位id_bit_number和上次最後走0的位LastDiscrepancy 的大小來判別,如下表:

 

3. 搜索路徑方向的確定

三種情況

當前搜索位 vs 最後走0差異位

路徑(變量:search_direction

結論2:上輪最後差異位

Id_bit_number=LastDiscripancy

1

結論3:非最後差異位

Id_bit_number < LastDiscripancy

同上次 (來自存儲的最近ROM)

結論1:新遇差異位

Id_bit_number > LastDiscripancy

0

其中變量Id_bit_number表示當前搜索位,每一次遍歷時從164步進;

變量search_direction表示當前節點經過二讀及判斷後確定的搜索方向,是0還是1

其他變量說明請參見流程圖中附文。

 

 

5  流程圖:

9列出了對一個從器件進行搜索的流程圖;注意:流程圖附文中列出了涉及到的一些關鍵變量,並進行了說明,在本文描述內容、流程圖及源代碼中也將用到這些名稱的變量。

其他說明:

1 該流程最終被代碼表達成一個函數,可以執行對一個器件的搜索;

2 當主機復位後未收到從機的存在脈衝、最後設備變量LastDeviceFlat=1、二讀後均爲1這三種情況,函數執行初始化變量後返回False

3 紅色虛線框中流程,是針對差異位的處理,分3種情況走不同分支,該部分是本算法的精華;

4 紅色虛線框中最下面的二個綠色背景框,是對家族碼的處理,適用於多類型器件混合網絡的指定處理,如單類器件組網,可以跳過此部分;

5 圖中各指令框邊上列出了部分變量、變量比較、變量賦值表達式,以方便後續理解代碼;

程序每步搜索確定的ROM碼將存入數組變量ROM_no[]中,流程圖中“同上次”框中將調用此數組變量的值,獲取上輪遍歷時該位值。

DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(2)

 DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法詳解(2)

 

  

6 實現

上述流程圖算法實際上是一個通用的函數,可實現一次從機ROM碼的搜索,在附錄代碼中即爲OWSearch()函數,執行一次該函數,可以有以下幾種結果:

1.復位信號發出後,未收到任何應答信號,表明無器件掛接或硬件電路故障,函數返回False退出;

2.上輪搜索中程序判斷爲最後一個器件,本次執行函數也是返回False退出;

3.在某位“二讀”時出現“11”的信號,這是執行中出現的異常,函數也是返回False退出;

4.首次或再次進入此函數,信號也正常,函數將搜索到總線上的第一個器件ROM碼;

在上述4種結果中,第1和第3是異常後退出,第2是器件搜索完畢後結束退出。第4是首次執行搜索和再次執行搜索這二種情況,可以表述後下面二個函數:First和Next,二個函數通過對LastDiscrepancyLastFamilyDiscrepancyLastDeviceFlagROM_NO值的處理,利用同一流程實現了兩個不同類型的搜索操作;這兩個操作是搜索1-Wire器件ROM碼的基礎,本文附後列出了全部代碼,可供測試。

First  

FIRST’操作是搜索1-Wire總線上的第一個從機器件。該操作是通過將LastDiscrepancy、LastFamilyDiscrepancy和LastDeviceFlag置零,然後進行搜索完成的。最後ROM碼從ROM_NO寄存器中讀出。若1-Wire總線上沒有器件,復位序列就檢測不到應答脈衝,搜索過程中止。

Next

‘NEXT’操作是搜索1-Wire總線上的下一個從機器件;一般情況下,此搜索操作是在‘FIRST’操作之後或上一次‘NEXT’操作之後進行;保持上次搜索後這些值的狀態不變、執行又一次搜索即可實現‘NEXT’操作。之後從ROM_NO寄存器中來讀出新一個ROM碼。若前一次搜索到的是1-Wire上的最後一個器件,則返回一個無效標記FALSE,並且把狀態設置成下一次調用搜索算法時將是‘FIRST’操作的狀態。

        MaximAN187應用筆記圖3 (a, b, c)例舉了三個器件的搜索過程,爲簡單起見該示例中的ROM碼只有2位。具體過程有二個圖和一個表。與本文中的示例也大同小異,所以本文不再列出。有興趣的可直接閱讀AN187應用筆記。




7 高級變量搜索

3種利用同一組狀態變量LastDiscrepancyLastFamilyDiscrepancyLastDeviceFlagROM_NO實現的高級變量搜索算法,這幾種高級搜索算法允許來指定作爲搜索目標或需要跳過搜索的器件的類型(家族碼)以及驗證某類型的器件是否在線(參見4)

如果理解了算法原理及各個變量的作用,不難理解“高級變量搜索”的三種功能。本文附帶代碼爲簡便起見,刪除了這些內容,讀者如感興趣可直接訪問AN187應用筆記。

 

Verify

‘VERIFY’操作用來檢驗已知ROM碼的器件是否連接在1-Wire總線上,通過提供ROM碼並對該碼進行目標搜索就可確定此器件是否在線。首先,將ROM_NO寄存器值設置爲已知的ROM碼值,然後將LastDiscrepancyLastDeviceFlag標誌位分別設置爲64 (40h)0 進行搜索操作,然後讀ROM_NO的輸出結果;如果搜索成功並且ROM_NO中存儲的仍是要搜索器件的ROM碼值,那麼此器件就在1-Wire總線上。

 

Target Setup

‘TARGET SETUP’操作就是用預置搜索狀態的方式首先查找一個特殊的家族類型,每個1-Wire器件都有一個字節的家族碼內嵌在ROM碼中(參見圖1),主機可以通過家族碼來識別器件所具有的特性和功能。若1-Wire總線上有多片器件時,通常是將搜索目標首先定位在需注意的器件類型上。爲了將一個特殊的家族作爲搜索目標,需要將所希望的家族碼字節放到ROM_NO寄存器的第一個字節中,並且將ROM_NO寄存器的復位狀態置零,然後將LastDiscrepancy設置爲64 (40h);把LastDeviceFlagLastFamilyDiscrepancy設置爲0。在執行下一次搜索算法時就能找出所期望的產品類型的第一個器件;並將此值存入ROM_NO寄存器。需要注意的是如果1-Wire總線上沒有掛接所期望的產品類型的器件,就會找出另一類型的器件,所以每次搜索完成後,都要對ROM_NO寄存器中存儲的結果進行校驗。

 

Family Skip Setup

‘FAMILY SKIP SETUP’操作用來設置搜索狀態以便跳過搜索到的指定家族中的所有器件,此操作只有在一個搜索過程結束後才能使用。通過把LastFamilyDiscrepancy複製到LastDiscrepancy,並清除LastDeviceFlag即可實現該操作;在下一搜索過程就會找到指定家族中的下一個器件。如果當前家族碼分組是搜索過程中的最後一組,那麼搜索過程結束並將LastDeviceFlag置位。

 

要注意的是,FamilySkipSetupTargetSetup函數實際上並沒有進行搜索操作,它們只不過是用來設置搜索寄存器,以便在下一次執行‘NEXT’操作時能跳過或找到所期望的類型。

4. 搜索變量狀態的設置

功能函數

LastDiscrepancy

LastFamily- Discrepancy

LastDeviceFlag

ROM_NO

FIRST

0

0

0

搜索結果

NEXT

保持原值

保持原值

保持原值

保持搜索結果

VERIFY

64

0

0

驗證與預設相同

TARGET SETUP

64

0

0

僅設家族碼,其他爲0

FAMILY SKIP SETUP

複製於LastFamilyDiscrepancy

0

0

保持原值

 

8 結論

本文提供的搜索算法可以找出任意給定的1-Wire器件組中獨一無二的ROM碼,這是保證多點1-Wire總線應用的關鍵,已知ROM碼後就可以對逐一選定的某個1-Wire器件來進行操作。本文還對一些變量搜索算法做了詳細論述,這些變量搜索算法能夠查找或跳過特定類型的1-Wire器件。

或許Maxim覺得1-Wire 器件的軟件開銷讓人望而生畏,因此設計了專用芯片DS2480B系列,可以實現串口到1-Wire線路的驅動,包括與本文檔中相同的搜索算法,詳細資料請參閱DS2480B數據資料和應用筆記192;以及DS2490 芯片,實現USB口到1-Wire橋接,也可實現了整個搜索過程;還有一款I2C橋接1-Wire的芯片。使用這些芯片,無須編寫複雜算法,只需向芯片發送命令即可實現多種控制,其最大優點是芯片中集成了1-Wire操作控制的硬件,省卻了1-Wire複雜的時序控制,因而其他編程可使用高級語言來編寫,甚至直接使用Maxim提供的平臺軟件在PC上開發1-Wire應用系統。

9 附錄

《詳解3》中將給出了實現搜索過程的例程,並給出了‘C’程序代碼,需要注意的是,Maxim並沒有給出“復位、讀寫總線”等低級1-Wire函數C代碼,而是提示我們可以調用TMEX API實現,考慮到本程序在51系列平臺上獨立調試,代碼中提供了這些函數。關於TMEX API和其它一些1-Wire API的詳細資料請參考應用筆記155

另須注意:低級1-Wire函數對時序控制有極高要求,其延時代碼必須確保符合讀寫時序,本文程序在1T單片機MPC82G516調試通過,如改用其他1T單片機或12T單片機,必須對延時函數重新調試參數,以獲得1-Wire規定時序。另:代碼在實測中也適用於“寄生供電”,即不給從機連接VCC電源,代碼也可正常工作。關於“寄生供電”的詳細信息,請參見Maxim其他應用筆記。

 

(介紹完,代碼待續)

1-Wire搜索算法詳解(3)

 

C代碼:

#include
#include 

sbit DQ=P0^7;  //1-wire總線
sbit K1=P0^6;  //標誌變量,用於觀察進出某段代碼所用時間
sbit P_Read=P0^5; //
sbit P_Write=P0^4; //

// definitions
#define FALSE 0
#define TRUE  1

//幾個延時函數,供一線低級操作時調用
//如果改用不同的MPU,如12T,則必須修改這幾個函數,確保時間符合協議要求
void Delay480us();  //@12.000MHz
void Delay410us();  //@12.000MHz
void Delay3_88us(unsigned char i);

//一線低級操作函數
bit  OWReset();       //復位
void OWWriteBit(bit bit_value);  //寫一位
bit  OWReadBit();     //讀一位
void OWWriteByte(unsigned char byte_value); //寫一個字節
//////

//搜索函數
bit  OWSearch();  //算法核心函數,完成一次ROM搜索過程
bit  OWFirst();   //調用OWSearch完成第一次搜索
bit  OWNext();   //調用OWSearch完成下一次搜索
unsigned char docrc8(unsigned char value); //執行CRC校驗

//全局搜索變量
unsigned char ROM_NO[8];  //數組,存放本次搜索到的ROM碼(8個字節)
char LastDiscrepancy;  //每輪搜索後指向最後一個走0的差異位
char LastFamilyDiscrepancy; //指向家族碼(前8位)中最後一個走0的差異位
bit LastDeviceFlag;   //搜到最後一個ROM後,程序通過判別將該變量置1,下輪搜索時即會結束退出
unsigned char crc8;    //CRC校驗變量
 


//--------------------------------------------------------------------------
//    在單總線上搜索第一個器件
// 返回TRUE: 找到, 存入ROM_NO緩衝;FALSE:無設備
// 先將初始化3個變量,然後調用OWSearch算法進行搜索
//--------------------------------------------------------------------------
bit OWFirst()
{
   LastDiscrepancy = 0;
   LastDeviceFlag = FALSE;
   LastFamilyDiscrepancy = 0;
   return OWSearch();
}

//--------------------------------------------------------------------------
//    在單總線上搜索下一個器件
// 返回TRUE: 找到, 存入ROM_NO緩衝;FALSE:無設備,結束搜索
// 在前一輪搜索的基礎上(3個變量均在前一輪搜索中有明確的值),再執行一輪搜索
//--------------------------------------------------------------------------
bit OWNext()
{
   return OWSearch();
}

 

 

 

//--------------------------------------------------------------------------
//     單總線搜索算法,利用了一些狀態變量,這是算法的核心程序,代碼也較長
//     返回TRUE: 找到, 存入ROM_NO緩衝;FALSE:無設備,結束搜索
//--------------------------------------------------------------------------
bit OWSearch()
{
   char id_bit_number;    //指示當前搜索ROM位(取值範圍爲1-64)
     //下面三個狀態變量含義:
  //last_zero:  指針,記錄一次搜索(ROM1-64位)最後一位往0走的混碼點編號
  //search_direction:搜索某一位時選擇的搜索方向(0或1),也是“一寫”的bit位值
  //rom_byte_number: ROM字節序號,作爲ROM_no[]數組的下標,取值爲1—8
   char last_zero, rom_byte_number, search_result;

   bit id_bit, cmp_id_bit,search_direction; //二讀(正碼、反碼)、及一寫(決定二叉搜索方向)
   unsigned char rom_byte_mask ; //ROM字節掩碼,

   // 初始化本次搜索變量
   id_bit_number = 1;
   last_zero = 0;
   rom_byte_number = 0;
   rom_byte_mask = 1;
   search_result = 0;
   crc8 = 0;

// ------------------------------------------------------------------
//1。是否搜索完成(已到最後一個設備)?
//-------------------------------------------------------------------
   if (!LastDeviceFlag)  // LastDeviceFlag由上輪搜索確定是否爲最後器件,當然首次進入前必須置False
   {
      if (OWReset())    //復位總線
      {
         LastDiscrepancy = 0;  //復位幾個搜索變量
         LastDeviceFlag = FALSE;
         LastFamilyDiscrepancy = 0;
         return FALSE;    //如果無應答,返回F,退出本輪搜索程序
      }

      OWWriteByte(0xF0);   //發送ROM搜索命令F0H
   Delay3_88us(60);

//=====================================================================
// 開始循環處理1-64位ROM,每位必須進行“二讀”後進行判斷,確定搜索路徑
// 然後按選定的路徑進行“一寫”,直至完成全部位的搜索,這樣一次循環
// 可以完成一輪搜索,找到其中一個ROM碼。
//=====================================================================
      do        //逐位讀寫搜索,1-64位循環
      {
         id_bit = OWReadBit();   //二讀:先讀正碼、再讀反碼
         cmp_id_bit = OWReadBit();

         if (id_bit  && cmp_id_bit)  //二讀11,則無器件退出程序
            break;
         else       //二讀不爲11,則需分二種情況
         {
            //*********************************************
   // 第一種情況:01或10,直接可明確搜索方向
            if (id_bit != cmp_id_bit)
               search_direction = id_bit;  // 記下搜索方向search_direction的值待“一寫”

   //*********************************************
            else
            {
               // 否則就是第二種情況:遇到了混碼點,需分三種可能分析:
               // 1。當前位未到達上輪搜索的“最末走0混碼點”(由LastDiscrepancy存儲)
      //    說明當前經歷的是一個老的混碼點,判別特徵爲當前位在(小於)LastDiscrepancy前
      //    不管上次走的是0還是1,只需按上次走的路即可,該值需從ROM_NO中的當前位獲取
               if (id_bit_number < LastDiscrepancy)
                  search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0);
      // 從
              
      else
               // 2。當前位正好爲上輪標記的最末的混碼點,這個混碼點也就是上次走0的點
      //    那麼這次就需要走1
      // 3。除去上二種可能,那就是第3種可能: 這是一個新的混碼點,
      //    id_bit_number>LastDiscrepancy
      //。。然而下一條語句巧妙地將上二種可能合在一起處理,看不懂我也沒辦法了
                  search_direction = (id_bit_number == LastDiscrepancy);
   
   //************************************************

            // 確定了混碼點的路徑方向還沒完事,還需要更新一個指針:last_zero
   // 這個指針每搜索完一位後(注意是一bit不是一輪)總是指向新的混碼點
   // 凡遇到新的混碼點,我們按算法都是先走0,所以凡遇走0的混碼點必須更新此指針
               if (search_direction == 0)
               {
                  last_zero = id_bit_number;

                  // 下面二條是程序的高級功能了:64位ROM中的前8位是器件的家族代碼,
      // 用LastFamilyDiscrepancy這個指針來記錄前8位ROM中的最末一個混碼點
      // 可用於在多類型器件的單線網絡中對家族分組進行操作
                  if (last_zero < 9)
                     LastFamilyDiscrepancy = last_zero;
               }
            }

            // 確定了要搜索的方向search_direction,該值即ROM中當前位的值,需要寫入ROM
            // 然而64位ROM需分8個字節存入ROM_NO[],程序使用了一個掩碼字節rom_byte_mask
   // 以最低位爲例:該字節值爲00000001,如記錄1則二字節或,寫0則與反掩碼
            if (search_direction == 1)
               ROM_NO[rom_byte_number] |= rom_byte_mask;
            else
               ROM_NO[rom_byte_number] &= ~rom_byte_mask;

            // 關鍵的一步操作終於到來了:一寫
            OWWriteBit(search_direction);

            // 一個位的操作終於完成,但還需做些工作,以準備下一位的操作:
            // 包括:位變量id_bit_number指向下一位;字節掩碼左移一位
            id_bit_number++;
            rom_byte_mask <<= 1;

            // 如果夠8位一字節了,則對該字節計算CRC處理、更新字節號變量、重設掩碼
            if (rom_byte_mask == 0)
            {
                docrc8(ROM_NO[rom_byte_number]);  // CRC計算原理參考其他文章
                rom_byte_number++;
                rom_byte_mask = 1;
            }
         }
      }
      while(rom_byte_number < 8);  // ROM bytes編號爲 0-7
   //流程圖中描述從1到64的位循環,本代碼中是利用rom_byte_number<8來判斷的
   //至此,終於完成8個字節共64位的循環處理

 //=================================================================================
 
      // 一輪搜索成功,找到的一個ROM碼也校驗OK,則還要處理二個變量
      if (!((id_bit_number < 65) || (crc8 != 0)))
      {
         // 一輪搜索結束後,變量last_zero指向了本輪中最後一個走0的混碼位
   // 然後再把此變量保存在LastDiscrepancy中,用於下一輪的判斷
   // 當然,last_zero在下輪初始爲0,搜索是該變量是不斷變動的
        LastDiscrepancy = last_zero;

         // 如果這個指針爲0,說明全部搜索結束,再也沒有新ROM號器件了
         if (LastDiscrepancy == 0)
            LastDeviceFlag = TRUE;  //設置結束標誌
        
         search_result = TRUE;  //返回搜索成功
      }
   }

// ------------------------------------------------------------------
//搜索完成,如果搜索不成功包括搜索到了但CRC錯誤,復位狀態變量到首次搜索的狀態。
//-------------------------------------------------------------------
   if (!search_result || !ROM_NO[0])
   {
      LastDiscrepancy = 0;
      LastDeviceFlag = FALSE;
      LastFamilyDiscrepancy = 0;
      search_result = FALSE;
   }
   return search_result;
}

//=====================================================================
   至此,OWSearch函數結束。函數實現的是一輪搜索,如成功,可得到一個ROM碼
//=======================================================================


//=====================================================================
// 1-Wire函數調用所需的延時函數,注意不同MCU下須重調參數
//=====================================================================

void Delay480us()  //@12.000MHz
{
 unsigned char i, j;
 i = 2;
 j = 90;
 do
 {
  while (--j);
 } while (--i);
}

void Delay410us()  //@12.000MHz
{
 unsigned char i, j;
 i = 2;
 j = 40;
 do
 {
  while (--j);
 } while (--i);
}


void Delay3_88us(unsigned char i)  //@12.000MHz  只能3-80us
{
 //i*=3;
 i-=1;
 while (--i);
}
  


//=====================================================================
//  一線函數:復位、讀一位、寫一位、寫一字節
//=====================================================================
bit OWReset()
{
        bit result;
  unsigned char i;
        DQ=0;    // 拉低總線啓動復位信號
        Delay480us();  
        DQ=1;    // Releases the bus
  i=53;   //延時70us
  while(--i);
        result = DQ;  // 採樣總線上從機存在信號
        Delay410us();  //
        return result;  //
}

//-----------------------------------------------------------------------------
void OWWriteBit(bit dat)  //寫一位函數
 //
{
  unsigned char i;
  P_Write=1;     //寫函數執行的標誌變量,用於調試
        if (dat)    //寫1
        {
                DQ=0;    // Drives DQ low
                i=4;     // 延時6us
u
    while(--i);
                DQ=1;    // 釋放總線
                i=38;     // 延時54us,不要看i的值,實測爲54us
    while(--i);  //
        }
        else     //寫0
        {
                DQ=0;    //
    i=35;     // 延時60
    while(--i);
                DQ=1;    // 釋放總線
    i=7;   // 延時10
    while(--i);
        }
  P_Write=0;
}

//-----------------------------------------------------------------------------
bit OWReadBit()     //讀一位
{
        bit result;
  unsigned char i;
     P_Read=1;    // 時間隙6+9+55
        DQ=0;      // Drives DQ low
        i=8;   //6
  while(--i);
        DQ=1;      // 釋放
        i=6;   //9
  while(--i);
        result = DQ;    // 取樣總線
        i=32;   //45
  while(--i);     //
    P_Read=0;
        return result;
}

//-----------------------------------------------------------------------------
void OWWriteByte(unsigned char dat) //寫一字節
{
        char loop;
        for (loop = 0; loop < 8; loop++) //低位先傳
        {
                OWWriteBit(dat & 0x01);
                dat >>= 1;     //右移
         }
}

//-----------------------------------------------------------------------------
char OWReadByte(void)  //讀一字節,本例中並未用到
{
        char loop, result=0;
        for (loop = 0; loop < 8; loop++)
        {
          result >>= 1;
            if (OWReadBit())
             result |= 0x80;
        }
        return result;
}

//  CRC計算用表
static unsigned char dscrc_table[] = {
        0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65,
      157,195, 33,127,252,162, 64, 30, 95,  1,227,189, 62, 96,130,220,
       35,125,159,193, 66, 28,254,160,225,191, 93,  3,128,222, 60, 98,
      190,224,  2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255,
       70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89,  7,
      219,133,103, 57,186,228,  6, 88, 25, 71,165,251,120, 38,196,154,
      101, 59,217,135,  4, 90,184,230,167,249, 27, 69,198,152,122, 36,
      248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91,  5,231,185,
      140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205,
       17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80,
      175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238,
       50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115,
      202,148,118, 40,171,245, 23, 73,  8, 86,180,234,105, 55,213,139,
       87,  9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22,
      233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168,
      116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53};
  

//--------------------------------------------------------------------------
//迭代計算CRC,返回當前CRC值
unsigned char docrc8(unsigned char value)
{
   // 詳見應用筆記AN27
   crc8 = dscrc_table[crc8 ^ value];   //^表示按位異或
   return crc8;
}

//主函數,
void main()
{
   bit rslt;
   K1=0;   //這三個位用於觀察“二讀一寫”
   P_Read=0;
   P_Write=0;

   rslt = OWFirst();  //搜索第一個ROM
   while (rslt)   //如果搜索成功,繼續搜索下一個
   {
      rslt = OWNext();
   }
   while(1);
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章