天天看點

外設驅動庫開發筆記14:DS18B20溫度變送器驅動

  在一時候我們需要相對簡單的檢測溫度信号,而DS18B20就是一款功能和應用都相對簡單的溫度傳感器,通過單線就可以實作檢測溫度信号的需求。這一篇我們就來實作操作DS18B20擷取溫度資料的驅動。

1、功能概述

  DS18B20是常用的數字溫度傳感器,其輸出的是數字信号,具有體積小,硬體開銷低,抗幹擾能力強,精度高的特點。單總線數字式溫度傳感器,由于具有結構簡單,不需要外接電路,可用一根I/O資料線既供電又傳輸資料,可由使用者設定溫度報警界限等特點,近年來廣泛用于糧庫等需要測量和控制溫度的地方。

1.1、硬體描述

  DS18B20數字溫度傳感器提供9-12位攝氏度溫度測量資料,可程式設計非易失存儲器設定溫度監測的上限和下限,提供溫度報警。器件可以工作在-55°C至+125°C範圍,在-10°C至+85°C範圍内測量精度為±0.5°C。此外,DS18B20還可以直接利用資料線供電 (寄生供電),無需外部電源。DS18B20數字溫度傳感器提供有三種封裝,其引腳定義分别如下表所示:

外設驅動庫開發筆記14:DS18B20溫度變送器驅動

  DS18B20數字溫濕度傳感器有一個64位ROM存儲器,用于儲存設備唯一的串行代碼。暫存存儲器包含2位元組的溫度寄存器,該寄存器存儲溫度傳感器的數字輸出。此外,暫存存儲器提供對1位元組的上、下報警觸發寄存器(TH和TL)和1位元組配置寄存器的通路。DS18B20數字溫濕度傳感器的功能框圖如下圖所示:

外設驅動庫開發筆記14:DS18B20溫度變送器驅動

  配置寄存器允許使用者将溫度-數字轉換的分辨率設定為9、10、11或12位。TH、TL和配置寄存器是非易失性的(EEPROM),是以它們将在裝置斷電時保留資料。

1.2、資料通訊

  DS18B20通過1-Wire總線通信,隻需要一條資料線 (和地線) 即可與處理器進行資料傳輸。每個DS18B20具有唯一的64位序列号,進而允許多個DS18B20挂接在同一條1-Wire總線。可以友善地采用一個微處理器控制多個分布在較大區域的DS18B20。該功能非常适合HVAC環境控制、樓宇、大型裝置、機器、過程監測與控制系統内部的溫度測量等應用。

  DS18B20傳感器進行的功能操作是在發送指令的基礎上完成的,上電後傳感器處于空閑狀态,需要控制器發送指令才能完成溫度轉換。通路DS18B20溫度傳感器需要按照固定的順序操作:

  步驟1、初始化通訊

  步驟2、操作ROM指令(後面跟着任何需要的資料交換)

  步驟3、DS18B20功能指令(後面跟着任何需要的資料交換)

  每次通路DS18B20時,遵循這個序列是非常重要的,因為如果序列中的任何步驟丢失或順序混亂,DS18B20将不會響應。這個規則的例外是搜尋ROM [F0h]和警報搜尋[ECh]指令。發出這兩個ROM指令後,主機必須按順序傳回步驟1。

1.2.1、通訊初始化

  在單線總線上的所有事務都以初始化序列開始。初始化序列包括由總線主發送的複位脈沖和從伺服器發送的存在脈沖。存在脈沖讓總線主人知道從裝置(例如DS18B20)在總線上并且準備好操作。複位和存在脈沖的時間在單線信号部分有詳細說明。

1.2.2、ROM操作

  對傳感器的功能操作的次序是首先完成對晶片内部的ROM操作,有5條操作ROM的指令可用于器件識别,它們分别是:ReadROM(33H)、Match ROM(55H)、Skip ROM(CCH)、SearchROM(F0H)、Alarm Search(ECH)。 具體描述如下表所示:

外設驅動庫開發筆記14:DS18B20溫度變送器驅動

1.2.3、功能操作

  實作DS18B20溫度傳感器操作,需在發送ROM指令之後發送功能指令。DS18B20溫度傳感器共有6條功能指令,分别是:溫度轉換指令(44H)、讀暫存器指令(BEH)、寫暫存器指令(4EH)、複制暫存器指令(48H)、重調EEPROM指令(B8H)、讀電源供電方式指令(B4H)。具體描述見下表所示:

外設驅動庫開發筆記14:DS18B20溫度變送器驅動

2、驅動設計與實作

  我們已經了解了DS18B20溫度傳感器的基本情況和資料通訊的相關資訊。接下來我們将基于此設計并實作DS18B20溫度傳感器的驅動程式。

2.1、對象定義

  我們依然采用基于對象操作的方式來實作,在使用一個對象之前我們需要獲得這個對象。同樣的我們想要基于對象操作DS18B20溫度傳感器就需要先定義DS18B20溫度傳感器的對象。

2.1.1、對象的抽象

  我們要得到DS18B20溫度傳感器對象,需要先分析其基本特性。一般來說,一個對象至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個方面思考一下DS18B20溫度傳感器的對象。

  先來考慮屬性,作為屬性肯定是用于辨別或記錄對象特征的東西。我們來考慮DS18B20溫度傳感器對象屬性。每一個DS18B20溫度傳感器都有一個序列号,在總線上有多個DS18B20溫度傳感器時,是差別裝置的唯一辨別,是以我們将序列号作為屬性。同時溫度資料表示了DS18B20溫度傳感器目前的工作狀态,是以我們也将其作為屬性。

  接着我們還需要考慮DS18B20溫度傳感器對象的操作問題。我們知道DS18B20溫度傳感器采用的是單總線通訊,既然是單總線就需要控制總線的輸入輸出方向,而且這對這條總線在不同的輸入輸出方向,我們需要讀資料和寫資料,而這些操作都依賴于硬體平台,是以我們将它們定義為DS18B20溫度傳感器對象的操作。處于時序控制的需要,我們需要延時操作函數,而在不同的軟硬體平台延時操作會有差異,我們也将其作為對象的操作。

  根據上述我們對DS18B20溫度傳感器的分析,我們可以定義DS18B20溫度傳感器的對象類型如下:

1 /* 定義DS18B20對象類型 */
2 typedef struct Ds18b20Object {
3 Uint8_t sn[6]; //Ds18b20元件序列号
4 float temperature; //溫度資料
5 void (*SetBit)(Ds18b20PinValueType vBit);//寫資料位到DS18B20
6 uint8_t (*GetBit)(void);//從DS18B20讀取一位資料
7 void (*SetPinMode)(Ds18b20IOModeType mode);//設定DS18B20的資料引腳的輸入輸出模式
8 void (*Delayus)(volatile uint32_t nTime);       //延時us操作指針
9 }Ds18b20ObjectType;      

2.1.2、對象初始化

  我們知道,一個對象僅作聲明是不能使用的,我們需要先對其進行初始化,是以這裡我們來考慮DS18B20溫度傳感器對象的初始化函數。一般來說,初始化函數需要處理幾個方面的問題。一是檢查輸入參數是否合理;二是為對象的屬性賦初值;三是對對象作必要的初始化配置。據此我們設計DS18B20溫度傳感器對象的初始化函數如下:

1 /*對DS18B20操作進行初始化*/
 2 Ds18b20StatusType Ds18b20Initialization(Ds18b20ObjectType *ds18b20,
 3 Ds18b20SetBitType setBit,
 4 Ds18b20GetBitType getBit,
 5 Ds18b20SetPinModeType pinDirection,
 6 Ds18b20DelayType delayus)
 7 {
 8   if((ds18b20==NULL)||(setBit==NULL)||(getBit==NULL)||(delayus==NULL))
 9   {
10     return DS18B20_InitialError;
11   }
12  
13   ds18b20->SetBit=setBit;
14   ds18b20->GetBit=getBit;
15   ds18b20->Delayus=delayus;
16  
17   if(pinDirection==NULL)
18   {
19     ds18b20->SetPinMode=SetPinModeDefault;
20   }
21   else
22   {
23     ds18b20->SetPinMode=pinDirection;
24   }
25  
26   ds18b20->temperature=0.0;
27  
28   ResetDs18b20(ds18b20);
29   if(PresenceDs18b20(ds18b20))
30   {
31     return DS18B20_NoResponse;
32   }
33  
34   GetDs18b20SerialNumber(ds18b20);
35  
36   return DS18B20_OK;
37 }      

2.2、對象操作

  我們已經完成了DS18B20溫度傳感器對象類型的定義和對象初始化函數的設計。得到對象并非我們的目标,我們的主要目标是擷取對象的資料,接下來我們還要實作面向DS18B20溫度傳感器的各類操作。

2.2.1、初始化通訊

  在單線總線上的所有事務都以初始化序列開始。初始化序列包括由主機發送的複位脈沖和從從裝置(如DS18B20)發送的存在脈沖。存在脈沖讓總線主機知道從裝置(例如DS18B20)在總線上并且準備好操作。複位和存在脈沖的操作時序如下圖:

外設驅動庫開發筆記14:DS18B20溫度變送器驅動

  其操作過程描述如下:

  (1) 先将資料線置高電平“1”。

  (2) 延時(該時間要求的不是很嚴格,但是盡可能的短一點)

  (3) 資料線拉到低電平“0”。

  (4) 延時750微秒(該時間的時間範圍可以從480到960微秒)。

  (5) 資料線拉到高電平“1”。

  (6) 延時等待(如果初始化成功則在15到60微秒時間之内産生一個由DS18B20所傳回的低電平“0”。據該狀态可以來确定它的存在,但是應注意不能無限的進行等待,不然會使程式進入死循環,是以要進行逾時控制)。

  (7) 若CPU讀到了資料線上的低電平“0”後,還要做延時,其延時的時間從發出的高電平算起(第(5)步的時間算起)最少要480微秒。

  (8) 将資料線再次拉高到高電平“1”後結束。

1 /*主機給從機發送複位脈沖*/
 2 static void ResetDs18b20(Ds18b20ObjectType *ds18b20)
 3 {
 4   /* 主機設定為推挽輸出*/
 5   ds18b20->SetPinMode(DS18B20_Out);
 6   /* 主機至少産生480us 的低電平複位信号*/
 7   ds18b20->SetBit(DS18B20_Reset);
 8   ds18b20->Delayus(550);
 9   /* 主機在産生複位信号後,需将總線拉高*/
10   ds18b20->SetBit(DS18B20_Set);
11   /*從機接收到主機的複位信号後,會在15~60us 後給主機發一個存在脈沖*/
12   ds18b20->Delayus(15);
13 }
14  
15 /*檢測從機給主機傳回的存在脈沖 0:成功;1:失敗*/
16 static uint8_t PresenceDs18b20(Ds18b20ObjectType *ds18b20)
17 {
18   uint8_t pulse_time = 0;
19   /* 主機設定為上拉輸入*/
20   ds18b20->SetPinMode(DS18B20_In);
21  
22   /* 等待存在脈沖的到來,存在脈沖為一個60~240us 的低電平信号*/
23   /*如果存在脈沖沒有來則做逾時處理,從機接收到主機的複位信号後,會在15~60us 後給主機發一個存在脈沖*/
24   while( ds18b20->GetBit() && pulse_time<100 )
25   {
26     pulse_time++;
27     ds18b20->Delayus(1);
28   }
29   /* 經過100us 後,存在脈沖都還沒有到來*/
30   if( pulse_time >=100 )
31     return 1;
32   else
33     pulse_time = 0;
34   /* 存在脈沖到來,且存在的時間不能超過240us*/
35   while(!ds18b20->GetBit() && (pulse_time<240))
36   {
37     pulse_time++;
38     ds18b20->Delayus(1);
39   }
40   if( pulse_time >=240 )
41   {
42     return 1;
43   }
44   else
45   {
46     return 0;
47   }
48 }      

2.2.2、寫操作

  主機寫DS18B20有兩種類型的寫時時段:“寫1”時間段和“寫0”時間段。總線主機使用一個寫1時間段來将邏輯1寫入DS18B20,而一個寫0時間段來将邏輯0寫入DS18B20。所有寫時段必須至少60µs持續時間與個人之間的最小1µs複蘇的時間寫插槽。兩種類型的寫時間段都是由主要器将單線總線拉低來啟動的。其操作時序如下圖:

外設驅動庫開發筆記14:DS18B20溫度變送器驅動

  我們可以總結其操作過程如下:

  (1) 資料線先置低電平“0”。

  (2) 延時确定的時間為15微秒。

  (3) 按從低位到高位的順序發送位元組(一次隻發送一位)。

  (4) 延時時間為45微秒。

  (5) 将資料線拉到高電平。

  (6) 重複上(1)到(6)的操作直到所有的位元組全部發送完為止。

  (7) 最後将資料線拉高。

1 /*向DS18B20寫一個位元組*/
 2 static void WriteByteToDs1820(Ds18b20ObjectType *ds18b20,uint8_t commond)
 3 {
 4   uint8_t i, testb;
 5   
 6   ds18b20->SetPinMode(DS18B20_Out);
 7   
 8   for(i=0; i<8; i++)
 9   {
10     testb = commond&0x01;
11     commond = commond>>1;
12     // 寫0和寫1的時間至少要大于60us
13     if (testb)
14     {
15       ds18b20->SetBit(DS18B20_Reset);
16       // 1us < 這個延時 < 15us
17       ds18b20->Delayus(10);
18       ds18b20->SetBit(DS18B20_Set);
19       ds18b20->Delayus(45);
20     }
21     else
22     {
23       ds18b20->SetBit(DS18B20_Reset);
24       // 60us < Tx 0 < 120us
25       ds18b20->Delayus(60);
26       ds18b20->SetBit(DS18B20_Set);
27       // 1us < Trec(恢複時間) < 無窮大
28     }
29     ds18b20->Delayus(2);
30   }
31 }      

2.2.3、讀操作

  DS18B20隻能在主機發出讀時段時将資料傳輸給主機。是以,主要機必須在發出read Scratchpad [BEh]或read Power Supply [B4h]指令後立即生成都時間段,以便DS18B20能夠提供所請求的資料。另外,在發出Convert T [44h]或Recall E2 [B8h]指令後,主機可以生成讀時間段,以檢視操作的狀态,具體操作如下列時序圖:

外設驅動庫開發筆記14:DS18B20溫度變送器驅動

  對上述描述和時序圖我們可以得到相關的讀操作步驟:

  (1)将資料線拉高“1”。

  (2)延時2微秒。

  (3)将資料線拉低“0”。

  (4)延時3微秒。

  (5)将資料線拉高“1”。

  (6)延時5微秒。

  (7)讀資料線的狀态得到1個狀态位,并進行資料處理。

  (8)延時60微秒。

1 /*從DS18B20讀取一個位,傳回值:1/0*/
 2 static uint8_t ReadBitFromDs18b20(Ds18b20ObjectType *ds18b20)
 3 {
 4   uint8_t data;
 5   
 6   ds18b20->SetPinMode(DS18B20_Out);
 7   ds18b20->SetBit(DS18B20_Reset);
 8   ds18b20->Delayus(2);
 9   ds18b20->SetBit(DS18B20_Set);
10   ds18b20->SetPinMode(DS18B20_In);
11   ds18b20->Delayus(12);
12   data=ds18b20->GetBit();
13   ds18b20->Delayus(50);
14   return data;
15 }      

3、驅動的使用

  我們已經設計并實作了DS18B20溫度傳感器的驅動程式。我們還需要基于這一驅動程式設計一個簡單的應用來驗證其是否正确。

3.1、聲明并初始化對象

  使用基于對象的操作我們需要先得到這個對象,是以我們先要使用前面定義的DS18B20溫度傳感器對象類型聲明一個DS18B20溫度傳感器對象變量,具體操作格式如下:

  Ds18b20ObjectType ds18b20;

  我們雖然聲明了這個對象變量,但還不能立即使用。我們還需要使用驅動中定義的初始化函數對這個變量進行初始化。這個初始化函數所需要的輸入參數如下:

  Ds18b20ObjectType *ds18b20,被初始化的對象變量

  Ds18b20SetBitType setBit,向總線寫一位操作

  Ds18b20GetBitType getBit,從總線讀一位操作

  Ds18b20SetPinModeType pinDirection,總線輸入輸出模式控制

  Ds18b20DelayType delayus,為秒延時操作

  對于這些參數,對象變量我們已經定義了。主要的是我們需要定義幾個函數,并将函數指針作為參數。這幾個函數的類型如下:

1 /*寫資料位到DS18B20*/
 2 typedef void (*Ds18b20SetBitType)(Ds18b20PinValueType vBit);
 3 
 4 /*從DS18B20讀取一位資料*/
 5 typedef uint8_t (*Ds18b20GetBitType)(void);
 6 
 7 /*設定DS18B20的資料引腳的輸入輸出模式*/
 8 typedef void (*Ds18b20SetPinModeType)(Ds18b20IOModeType mode);
 9 
10 /* 定義延時操作函數指針類型 */
11 typedef void (*Ds18b20DelayType)(volatile uint32_t nTime);      

  對于這幾個函數我們根據樣式定義就可以了,具體的操作可能與使用的硬體平台有關系。具體函數定義如下:

1 //設定DS18B20引腳的輸出值
 2 void Ds18b20SetPinOutValue(Ds18b20PinValueType setValue)
 3 {
 4   HAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,(GPIO_PinState)setValue);
 5 }
 6  
 7 //讀取引腳電平
 8 uint8_t Ds18b20ReadPinBit(void)
 9 {
10   return (uint8_t)HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11);
11 }
12  
13 //設定引腳的輸入輸出方向
14 void Ds18b20SetPinMode(Ds18b20IOModeType mode)
15 {
16   GPIO_InitTypeDef GPIO_InitStruct;
17   
18   GPIO_InitStruct.Pin = GPIO_PIN_11;
19   if(mode==DS18B20_In)
20   {  
21     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
22     GPIO_InitStruct.Pull = GPIO_NOPULL;
23   }
24   else
25   {  
26     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
27     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
28   }  
29   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
30 }      

  對于延時函數我們可以采用各種方法實作。我們采用的STM32平台和HAL庫則可以直接使用HAL_Delay()函數。于是我們可以調用初始化函數如下:

  Ds18b20Initialization(&ds18b20,Ds18b20SetPinOutValue,Ds18b20ReadPinBit,Ds18b20SetPinMode,Delayus);

3.2、基于對象進行操作

  我們定義了對象變量并使用初始化函數給其作了初始化。接着我們就來考慮操作這一對象擷取我們想要的資料。我們在驅動中已經将擷取資料并轉換為轉換值的比例值,接下來我們使用這一驅動開發我們的應用執行個體。

1 /*擷取資料值*/
2 void GetMeasureDataFromDHT11(void)
3 {
4   float temperature; //溫度值
5   GetDS18b20TemperatureValue(&ds18b20);
6   temperature=ds18b20.temperature;
7 }      

4、應用總結

  我們實作了DS18B20溫度傳感器的驅動程式,并基于這一驅動程式設計了簡單的應用程式。我們也成功獲得了溫度資料,充分說明我們的驅動設計是正确的。事實上,在我們的項目中多次使用DS18B20溫度傳感器,這一驅動也是多次被使用到,結果令人滿意。

  單總線資料傳輸時,會改變總線的輸入輸出方向。在我們的應用中,我們修改了對應GPIO引腳的輸入輸出模式。事實上如果我們在STM32中使用時,我們可将該引腳配置為開漏輸出模式,加上總線的上拉電阻,可以在不修改GPIO的輸入輸出模式的情況下實作讀寫。

  使用驅動時需要注意,本驅動程式隻考慮了總線上隻有一個DS18B20的情況。在一條總線上有多個DS18B20溫度傳感器時,目前的驅動程式是不能夠實作操作的,需要對驅動作相應修改。

歡迎關注:

如果您希望更友善且及時的閱讀相關文章,關注我的微信公衆号【木南創智】