1.Abstract
如前篇隨筆所寫,將以前遇到最難懂的兩個部分重拾一下。前一篇寫的是I2C協議(http://www.cnblogs.com/hechengfei/p/4117840.html),這一篇就來寫基於DS18B20的1-wire協議。以前用到它的時候是借助別人寫的文章和配套程序,整了一個一知半解;現在重新學習一遍,我想參考的資料就只有唯一一份了——芯片手冊,最為權威和齊全的。在平靜下來寫這個的時候,着實花了很大精力將手冊從頭看到尾,部分的時序圖在草稿紙上畫了畫,理解也更加深刻了一些。
總線的結構都是差不多的,相對於上篇中的I2C總線,1-wire總線結構從邏輯上來說還簡單一點,要說復雜的地方,那就是比較嚴格的時序了——規定的時間內必須完成某事,否則容易出岔子。關於1-wire總線的優劣勢,在仔細分析完一個具體的器件以后,再來做結論比較適合,下面開始正文。
2.Content
掛在1-wire總線上的,一般都只有一個主器件,一個或者多個從器件;而且通常情況下單個器件的情況居多。構成主器件的,都是微控制器或者可編程邏輯器件(CPLD/FPGA),從器件就是各個廠商生產的具體功能芯片了(尤其是DALLAS Semiconductor 公司)。下面以一個具體的實例為例,分析下它是如何工作的。器件使用典型的DS18B20溫度傳感器,在1-wire總線結構中,它只能作從器件。
2.1 協議分析
和分析I2C的協議一樣,把掛在總線上的器件分為兩種;一種是主器件,它具有控制總線的權利,主動與從器件打交道;另外一種是從器件,它受主器件的指揮;一般都是站在主器件的角度去看總線(因為從器件是受控對象,它的功能已經實現好了,只需要給它特定的指令,它就會自動地去做某件事兒)。
和其他總線通信一樣,主器件需要知道從器件的一些基本信息——器件地址和器件內部的控制信息。對於DS18B20來說,解決它的器件地址還真不是一件容易的事兒,看看它的地址信息是怎么分配的。
DS18B20的內部ROM有64位,分為8個字節(8 * 8 = 64bit);第一個字節是器件家族的固定編碼,DS18B20為28H;中間的6個字節48位為器件的序列號,也就是器件地址(最為關注的就是它了);最后一個字節是前面 8 + 48 = 56 位的循環冗余校驗碼(CRC CODE),若是主器件想讀取該器件的ROM信息,可以將前56位編碼做一個循環冗余校驗,生成一個8bit的循環冗余校驗碼,然后和最后的這一個字節進行對比,若相同,則說明讀取信息是正確的,否則,主器件的讀取是有誤的。
從上面的分析中,找到了關鍵的地址信息——中間的這個48bit序列號,按照數學的邏輯組合,它可以分配2的48次方281,474,976,710,656個地址,以億為單位算,有281474億個地址!實在是太多了,所以器件生產商將每一個器件都做了一個固定的序列號;不過這樣有一個麻煩的是,拿到手中的器件的地址信息最開始是不知道的,要想獲得某個器件的地址,必須得用主器件將它的這個序列號讀取出來;然而實際上,在1-wire總線上掛了多個DS18B20的器件,要將它們的地址都獲取出來,是非常麻煩的;芯片手冊里給出了一種算法,將不同的器件識別出來,這里的識別指的不是准確的獲取它們的序列信息。所以通常情況下,在未知器件序列號的情況下,都是采用單個從器件跟主器件連接的。要想主器件上一根I/O口線連接多個從器件,那么就得想想其他辦法了。
這里提出一種簡單的方法供參考。鑒於DS18B20的數據輸出口傳輸的是數字信號,故MCU的數據端口也應該配置的數據輸入輸出端口;將它們抽象起來看,就是一個數字端口能分時的與多個數字端口連接,常采用方法就是用選擇器,n個地址的選擇器就可以分2的n次方個連接端口,下圖是以8選1的集成芯片74151做例子解釋。
本圖的重要目的是為了解釋接口的關系,圖紙並不完全。左邊是DS18B20的插座,數據接入選擇器的輸入端,輸出端和輸入端口選擇的地址接在MCU輸入輸出端子上。如果還需要接更多的DS18B20器件,可以采用選擇器路數更多的功能芯片,如16選1,32選1等;如果還需要接有更多的從器件,按照這種邏輯可以使用CPLD/FPGA器件,它們的引腳數比較眾多,是做數字設計的好幫手。這樣用多路選擇器的方法可以解決多個DS18B20分時共用一個數據端口的問題。
由上述可知,1-wire總線上在同一個時間點上主器件只和一個從器件相連,構成一對一的關系,所以主器件知道從器件的地址信息就不再有意義了。在1-wire通信中,主器件需要了解從器件的控制信息顯得更為重要一些了。
對應於主器件,和從器件通信只有兩件事兒,其一是向從器件寫數據,另外一個是從從器件讀數據。基於DS18B20的1-wire通信協議將器件的控制信息全部列舉出來,主器件只需要發送這些控制命令,就可以執行相應的操作了。
由於DS18B20內部的寄存器不多(只有七個,其中有三個作為保留),所以對器件進行讀寫的控制就比較簡單,發送相應的讀寫命令指令以后,器件就會按照協議進行數據交換。整體的讀寫操作流程如下圖所示。
和其他的通信協議一樣,主器件的寫操作分為五大步驟,建立通信、寫ROM操作指令、寫控制操作指令、數據交換、結束通信。
1-wire協議對它們進行了詳細地規定;建立通信是一段480us~960us的低電平復位,如下圖所示。
操作首先是主控給一段480-960us的低電平,然后釋放總線(將總線拉高);緊接着DS18B20等待15-60us以后,輸出一個時常為60-240us的低電平,表示收到主器件發來的初始化信息;之后將總線釋放掉。主器件可以通過從器件返回的這個低電平判斷器件是否正常連接(相當於一個應答)。
寫ROM操作指令。具體主器件是如何將數據一位一位地通過1-wire傳送到從器件,這個在后邊講述;這里將它抽象出來,作為一個指令來敘述。 寫ROM的操作指令有5個,如下表所示。
表2.1 ROM指令對照表
ROM指令 | 指令功能 |
33H | READ ROM |
55H | MATCH ROM |
F0H | SERACH ROM |
ECH | ALARM SERACH ROM |
CCH | SKIP ROM |
雖然器件給出了五種ROM的操作指令,但是在實際應用中,主器件與從器件的連接是1對1的,所以一般情況下是不需要對ROM數據進行太多的處理的,所以CCH(SKIP ROM)這條ROM指令用得最多。
寫一個控制操作指令。在主器件進行完寫ROM操作指令以后,緊接着就是要寫一個控制指令了,控制指令表由圖FIG2.3給出了,這里只將控制指令部分和指令功能部分列成表給出。
表2.2 控制指令對照表
控制指令 | 指令功能 |
44H | Convert T |
BEH | Read Scratchpad |
4EH | Write Scratchpad |
48H | Copy Scratchpad |
B8H | Recall E2 |
B4H | Read Power Supply |
簡單的解釋一下吧,44H(Convert T)指令是讓從器件進行溫度轉換的指令,在正確讀取溫度數據之前,需要對當前的溫度進行轉換;BEH(Read Scratchpad)指令是將從器件中的9個RAM數據讀取出來,DS18B20的內部RAM結構圖如下圖所示。
前兩個數據是溫度轉換數據,緊接着的兩個是溫度上下限控制數據,第五個是配置寄存器,第6到第8個是保留寄存器,第9個是前邊數據的CRC校驗數據。與掉電數據丟失的SCRATCHPAD RAM塊相比,器件內部設置了一個掉電不丟失的電可擦除E2RAM,用於保存溫度上下限控制數據和配置寄存器數據。
這些數據是按BYTE0 到BUTE8的順序逐步讀出的。若需要讀取E2RAM塊的數據,則需要先執行將E2RAM的數據寫入到SCRATCHPAD的控制指令。下面簡要的說明一下其他控制指令的功能。
BEH(READ SCRATCHPAD)指令是讀取SCRATCHPAD的指令,執行此條指令以后,從器件會將內部SCRAPTCHPAD的數據逐步的發送至1-wire上。當然在讀的這個過程中,主器件隨時可以中斷讀操作,只對特定感興趣的數據讀取。
4EH(WRITE SCRATCHPAD)指令是寫SCRATCHPAD指令,執行此條指令以后,主器件需要完整地將溫控上下限數據(TH 與TL)和配置數據(CONFIG)依次發送到1-wire上。值得注意的是此條指令的寫只是將數據存放到SCRATCHPAD中,而不是E2RAM中,它是會掉電丟失的。
48H(COPY SCRACTCHPAD)指令是將SCRATCHPAD中的溫控上下限數據(TH與TL)和配置數據(CONFIG)寫到E2RAM中,用於掉電數據保持。執行此條指令以后后續不需要再寫數據。
B8H(RECALL SCRATCHPAD)指令是將E2RAM的數據讀到SCRATCHPAD中,一般是用於讀取E2RAM的數據進行校驗的場合。執行此條指令以后后續不需要再寫數據。
B4H(READ POWER SUPPLY)指令是讀取芯片的供電方式。一種是獨立的電源供電,返回1;另外一種是用DQ引腳端復合供電,返回0。執行此條指令以后后續不需要再寫數據。
了解總體的數據讀寫操作流程以后,剩下的就是具體的讀寫操作過程了。因為讀寫的操作過程數據線上只有0和1的變化,沒有時鍾的輔助,所以DS18B20對時序的控制非常嚴格。還是站在主器件的角度去看。
主器件的寫是一個特定時間內完成特定數據的變化的過程。主器件首先要發送一個大於1us的低電平,后續就發送相應的數據位,整個1位寫的時常至少為60us。如上圖左邊寫0的過程,主器件首先發送一個大於1us的低電平電平,由於數據是0,所以后續的電平為低電平,直至整個位長時間結束(至少60 - 1us)。寫完1位數據以后,需要釋放數據線,否則數據線低電平時間超過480us,則導致器件開始初始化操作。右邊寫1的過程也是一樣,主器件首先發送一個大於1us的低電平,由於數據是1,所以發送完低電平以后就需要釋放數據線,數據線高電平時間一直保持到整個位長時間結束(至少60-1us);與發送1相比,最后可以不需要釋放總線(因為數據線本就是高電平)。圖中灰色的方框內給出了典型的操作時間;在前15個us以內,寫操作主控需要將數據線拉低至少1us,然后將要發送的數據電平發送到數據上並保持穩定,這整段的時間最好在15us左右,余下的15+30 = 45us是從器件DS18B20讀取數據(電平采樣)的過程;采樣完成以后,需要釋放數據線,兩位數據讀取的時間要大於1us。
理解了主器件寫一位數據的操作流程,那么來理解主器件讀一位數據的操作流程就比較簡單了,它們的格式都是一樣的。主器件讀取一位數據之前,也需要發送一個大於1us的低電平,然后等待從器件將相應的數據放到數據線上直到穩定;主器件等待一段時間以后對數據線進行采樣,如果數據線為高電平,表示從器件發送了一個1;相反,如果數據線為低電平,則表示從器件發送一個0數據。整個讀取一位數據的時長至少為60us,1位數據讀取完畢以后,從器件會自動的將數據線釋放掉,做好發送下一位數據的准備。
插一段理解。縱觀通信特點,讀和寫都的過程都是差不多的;每一次讀或寫的開始,主器件都需要發送一個至少1us時常的低電平,可以認為是數據傳輸的開始,緊接着是將數據放到數據線並穩定下來,這整個的時常大約為15us,余下的時間就是等待主器件或者從器件的采樣操作,最后就是將數據線釋放掉,准備下一次數據的傳輸操作。
2.2 基於MCU的協議實現
用MCS-51來實現一下,采用的是傳統51單片機,12M晶振,故指令時間最短為1us。
2.2.1 DS18B20 復位
bit Init_DS18B20(void) { bit dat=0; // DQ復位 DQ = 1; DelayUs2x(5); // 稍做延時,高電平維持一小段時間 DQ = 0; //單片機將DQ拉低 DelayUs2x(200); //精確延時 大於 480us 小於960us DelayUs2x(200); DQ = 1; // 拉高總線 DelayUs2x(50); //15~60us 后 接收60-240us的存在脈沖 dat=DQ; //如果x=0則初始化成功, x=1則初始化失敗 DelayUs2x(25); //稍作延時返回 return dat; }
2.2.2 MCU讀取一個字節
unsigned char ReadOneChar(void) { unsigned char i=0; unsigned char dat = 0; for (i=8;i>0;i--) { DQ = 0; // 給低電平信號 dat>>=1; _Nop(); _Nop(); // 維持一段時間 DQ = 1; // 給脈沖信號 if(DQ) dat|=0x80; DelayUs2x(25); // MCU采樣 } return(dat); }
2.2.3 MCU寫一個字節
void WriteOneChar(unsigned char dat) { unsigned char i=0; for (i=8; i>0; i--) { DQ = 0; // 先發送一個低電平 _Nop(); _Nop(); // 低電平時常大於1us DQ = dat&0x01; // 將數據發送到總線上 DelayUs2x(25); // 保持數據一段時間 DQ = 1; // 釋放數據線 dat>>=1; } DelayUs2x(25); // 時序緩沖處理,可省略 }
2.2.4 讀取溫度轉換值
unsigned int ReadTemperature(void) { unsigned char a=0; unsigned int b=0; unsigned int t=0; Init_DS18B20(); WriteOneChar(0xCC); // 跳過讀序號列號的操作 WriteOneChar(0x44); // 啟動溫度轉換 DelayMs(750); // 等待數據轉換完畢 Init_DS18B20(); // 初始化DS18B20 WriteOneChar(0xCC); //跳過讀序號列號的操作 WriteOneChar(0xBE); //讀取溫度寄存器等(共可讀9個寄存器) 前兩個就是溫度 a=ReadOneChar(); //低位 b=ReadOneChar(); //高位 b<<=8; t=a+b; // 數據合並 return(t); }
值得注意的是,主器件每次對從器件的操作,都需要符合固定的通信格式,詳細地可以參照2.1節協議分析。
2.3 協議驗證
限於手上只有一個DS18B20溫度芯片,所以直接將它與MCU相連就可以了,采用默認的12位溫度轉換方式。不考慮擴展的情況。測試的工具是一個量程為0置100度的液體溫度計,溫度計的分辨率為0.1度。下面是一個室內室外早晚的測試結果表。
表2.2 溫度測試表
測量次數 | 溫度計測量值(度) | DS18B20測量值(度) |
1 | 12.5 | 12.496 |
2 | 16.5 | 16.496 |
3 | 18.7 | 18.695 |
4 | 14.8 | 14.795 |
對照上述的結果表,可以看出測量結果還是比較精確的。產生誤差的原因有兩個方面,一個是傳感器量化的誤差,另外一個是溫度計的測量精度有限,人眼來讀取數值時難免會有些誤差。
從上述的結果分析中可以看出,主器件MCU與從器件DS18B20之間按照1-wire的通信方式是成功的。
3.Conclusion
總體上來說,1-wire協議還是不復雜的,但是對時序的要求還是比較高;相對其他的兩線或者三線協議,1線協議要實現單根線上掛多個器件是非常麻煩,這或許也是1-wire協議用得不是很廣泛的原因吧。
4.Reference
1). AT24C02 datasheet