在一時候我們需要相對簡單的檢測溫度信號,而DS18B20就是一款功能和應用都相對簡單的溫度傳感器,通過單線就可以實現檢測溫度信號的需求。這一篇我們就來實現操作DS18B20獲取溫度數據的驅動。
1、功能概述
DS18B20是常用的數字溫度傳感器,其輸出的是數字信號,具有體積小,硬件開銷低,抗干擾能力強,精度高的特點。單總線數字式溫度傳感器,由于具有結構簡單,不需要外接電路,可用一根I/O數據線既供電又傳輸數據,可由用戶設置溫度報警界限等特點,近年來廣泛用于糧庫等需要測量和控制溫度的地方。
1.1、硬件描述
DS18B20數字溫度傳感器提供9-12位攝氏度溫度測量數據,可編程非易失存儲器設置溫度監測的上限和下限,提供溫度報警。器件可以工作在-55°C至+125°C范圍,在-10°C至+85°C范圍內測量精度為±0.5°C。此外,DS18B20還可以直接利用數據線供電 (寄生供電),無需外部電源。DS18B20數字溫度傳感器提供有三種封裝,其引腳定義分別如下表所示:
DS18B20數字溫濕度傳感器有一個64位ROM存儲器,用于存儲設備唯一的串行代碼。暫存存儲器包含2字節的溫度寄存器,該寄存器存儲溫度傳感器的數字輸出。此外,暫存存儲器提供對1字節的上、下報警觸發寄存器(TH和TL)和1字節配置寄存器的訪問。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)。具體描述如下表所示:
1.2.3、功能操作
實現DS18B20溫度傳感器操作,需在發送ROM指令之后發送功能指令。DS18B20溫度傳感器共有6條功能指令,分別是:溫度轉換指令(44H)、讀暫存器指令(BEH)、寫暫存器指令(4EH)、復制暫存器指令(48H)、重調EEPROM指令(B8H)、讀電源供電方式指令(B4H)。具體描述見下表所示:
2、驅動設計與實現
我們已經了解了DS18B20溫度傳感器的基本情況和數據通訊的相關信息。接下來我們將基于此設計并實現DS18B20溫度傳感器的驅動程序。
2.1、對象定義
我們依然采用基于對象操作的方式來實現,在使用一個對象之前我們需要獲得這個對象。同樣的我們想要基于對象操作DS18B20溫度傳感器就需要先定義DS18B20溫度傳感器的對象。
2.1.1、對象的抽象
我們要得到DS18B20溫度傳感器對象,需要先分析其基本特性。一般來說,一個對象至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個方面思考一下DS18B20溫度傳感器的對象。
先來考慮屬性,作為屬性肯定是用于標識或記錄對象特征的東西。我們來考慮DS18B20溫度傳感器對象屬性。每一個DS18B20溫度傳感器都有一個序列號,在總線上有多個DS18B20溫度傳感器時,是區別設備的唯一標識,所以我們將序列號作為屬性。同時溫度數據表示了DS18B20溫度傳感器當前的工作狀態,所以我們也將其作為屬性。
接著我們還需要考慮DS18B20溫度傳感器對象的操作問題。我們知道DS18B20溫度傳感器采用的是單總線通訊,既然是單總線就需要控制總線的輸入輸出方向,而且這對這條總線在不同的輸入輸出方向,我們需要讀數據和寫數據,而這些操作都依賴于硬件平臺,所以我們將它們定義為DS18B20溫度傳感器對象的操作。處于時序控制的需要,我們需要延時操作函數,而在不同的軟硬件平臺延時操作會有差異,我們也將其作為對象的操作。
根據上述我們對DS18B20溫度傳感器的分析,我們可以定義DS18B20溫度傳感器的對象類型如下:
/* 定義DS18B20對象類型 */
typedef struct Ds18b20Object {
Uint8_t sn[6];//Ds18b20元件序列號
float temperature;//溫度數據
void (*SetBit)(Ds18b20PinValueType vBit);//寫數據位到DS18B20
uint8_t (*GetBit)(void);//從DS18B20讀取一位數據
void (*SetPinMode)(Ds18b20IOModeType mode);//設置DS18B20的數據引腳的輸入輸出模式
void (*Delayus)(volatile uint32_t nTime); //延時us操作指針
}Ds18b20ObjectType;
2.1.2、對象初始化
我們知道,一個對象僅作聲明是不能使用的,我們需要先對其進行初始化,所以這里我們來考慮DS18B20溫度傳感器對象的初始化函數。一般來說,初始化函數需要處理幾個方面的問題。一是檢查輸入參數是否合理;二是為對象的屬性賦初值;三是對對象作必要的初始化配置。據此我們設計DS18B20溫度傳感器對象的初始化函數如下:
/*對DS18B20操作進行初始化*/
Ds18b20StatusType Ds18b20Initialization(Ds18b20ObjectType *ds18b20,
Ds18b20SetBitType setBit,
Ds18b20GetBitType getBit,
Ds18b20SetPinModeType pinDirection,
Ds18b20DelayType delayus)
{
if((ds18b20==NULL)||(setBit==NULL)||(getBit==NULL)||(delayus==NULL))
{
return DS18B20_InitialError;
}
ds18b20->SetBit=setBit;
ds18b20->GetBit=getBit;
ds18b20->Delayus=delayus;
if(pinDirection==NULL)
{
ds18b20->SetPinMode=SetPinModeDefault;
}
else
{
ds18b20->SetPinMode=pinDirection;
}
ds18b20->temperature=0.0;
ResetDs18b20(ds18b20);
if(PresenceDs18b20(ds18b20))
{
return DS18B20_NoResponse;
}
GetDs18b20SerialNumber(ds18b20);
return DS18B20_OK;
}
2.2、對象操作
我們已經完成了DS18B20溫度傳感器對象類型的定義和對象初始化函數的設計。得到對象并非我們的目標,我們的主要目標是獲取對象的數據,接下來我們還要實現面向DS18B20溫度傳感器的各類操作。
2.2.1、初始化通訊
在單線總線上的所有事務都以初始化序列開始。初始化序列包括由主機發送的復位脈沖和從從設備(如DS18B20)發送的存在脈沖。存在脈沖讓總線主機知道從設備(例如DS18B20)在總線上并且準備好操作。復位和存在脈沖的操作時序如下圖:
其操作過程描述如下:
(1) 先將數據線置高電平“1”。
(2) 延時(該時間要求的不是很嚴格,但是盡可能的短一點)
(3) 數據線拉到低電平“0”。
(4) 延時750微秒(該時間的時間范圍可以從480到960微秒)。
(5) 數據線拉到高電平“1”。
(6) 延時等待(如果初始化成功則在15到60微秒時間之內產生一個由DS18B20所返回的低電平“0”。據該狀態可以來確定它的存在,但是應注意不能無限的進行等待,不然會使程序進入死循環,所以要進行超時控制)。
(7) 若CPU讀到了數據線上的低電平“0”后,還要做延時,其延時的時間從發出的高電平算起(第(5)步的時間算起)最少要480微秒。
(8) 將數據線再次拉高到高電平“1”后結束。
/*主機給從機發送復位脈沖*/
static void ResetDs18b20(Ds18b20ObjectType *ds18b20)
{
/* 主機設置為推挽輸出*/
ds18b20->SetPinMode(DS18B20_Out);
/* 主機至少產生480us 的低電平復位信號*/
ds18b20->SetBit(DS18B20_Reset);
ds18b20->Delayus(550);
/* 主機在產生復位信號后,需將總線拉高*/
ds18b20->SetBit(DS18B20_Set);
/*從機接收到主機的復位信號后,會在15~60us 后給主機發一個存在脈沖*/
ds18b20->Delayus(15);
}
/*檢測從機給主機返回的存在脈沖 0:成功;1:失敗*/
static uint8_t PresenceDs18b20(Ds18b20ObjectType *ds18b20)
{
uint8_t pulse_time = 0;
/* 主機設置為上拉輸入*/
ds18b20->SetPinMode(DS18B20_In);
/* 等待存在脈沖的到來,存在脈沖為一個60~240us 的低電平信號*/
/*如果存在脈沖沒有來則做超時處理,從機接收到主機的復位信號后,會在15~60us 后給主機發一個存在脈沖*/
while( ds18b20->GetBit() && pulse_time<100 )
{
pulse_time++;
ds18b20->Delayus(1);
}
/* 經過100us 后,存在脈沖都還沒有到來*/
if( pulse_time >=100 )
return 1;
else
pulse_time = 0;
/* 存在脈沖到來,且存在的時間不能超過240us*/
while(!ds18b20->GetBit() && (pulse_time<240))
{
pulse_time++;
ds18b20->Delayus(1);
}
if( pulse_time >=240 )
{
return 1;
}
else
{
return 0;
}
}
2.2.2、寫操作
主機寫DS18B20有兩種類型的寫時時段:“寫1”時間段和“寫0”時間段。總線主機使用一個寫1時間段來將邏輯1寫入DS18B20,而一個寫0時間段來將邏輯0寫入DS18B20。所有寫時段必須至少60μs持續時間與個人之間的最小1μs復蘇的時間寫插槽。兩種類型的寫時間段都是由主控器將單線總線拉低來啟動的。其操作時序如下圖:
我們可以總結其操作過程如下:
(1) 數據線先置低電平“0”。
(2) 延時確定的時間為15微秒。
(3) 按從低位到高位的順序發送字節(一次只發送一位)。
(4) 延時時間為45微秒。
(5) 將數據線拉到高電平。
(6) 重復上(1)到(6)的操作直到所有的字節全部發送完為止。
(7) 最后將數據線拉高。
/*向DS18B20寫一個字節*/
static void WriteByteToDs1820(Ds18b20ObjectType *ds18b20,uint8_t commond)
{
uint8_t i, testb;
ds18b20->SetPinMode(DS18B20_Out);
for(i=0; i<8; i++)
{
testb = commond&0x01;
commond = commond>>1;
// 寫0和寫1的時間至少要大于60us
if (testb)
{
ds18b20->SetBit(DS18B20_Reset);
// 1us < 這個延時 < 15us
ds18b20->Delayus(10);
ds18b20->SetBit(DS18B20_Set);
ds18b20->Delayus(45);
}
else
{
ds18b20->SetBit(DS18B20_Reset);
// 60us < Tx 0 < 120us
ds18b20->Delayus(60);
ds18b20->SetBit(DS18B20_Set);
// 1us < Trec(恢復時間) < 無窮大
}
ds18b20->Delayus(2);
}
}
2.2.3、讀操作
DS18B20只能在主機發出讀時段時將數據傳輸給主機。因此,主控機必須在發出read Scratchpad [BEh]或read Power Supply [B4h]命令后立即生成都時間段,以便DS18B20能夠提供所請求的數據。另外,在發出Convert T [44h]或Recall E2 [B8h]命令后,主機可以生成讀時間段,以查看操作的狀態,具體操作如下列時序圖:
對上述描述和時序圖我們可以得到相關的讀操作步驟:
(1)將數據線拉高“1”。
(2)延時2微秒。
(3)將數據線拉低“0”。
(4)延時3微秒。
(5)將數據線拉高“1”。
(6)延時5微秒。
(7)讀數據線的狀態得到1個狀態位,并進行數據處理。
(8)延時60微秒。
/*從DS18B20讀取一個位,返回值:1/0*/
static uint8_t ReadBitFromDs18b20(Ds18b20ObjectType *ds18b20)
{
uint8_t data;
ds18b20->SetPinMode(DS18B20_Out);
ds18b20->SetBit(DS18B20_Reset);
ds18b20->Delayus(2);
ds18b20->SetBit(DS18B20_Set);
ds18b20->SetPinMode(DS18B20_In);
ds18b20->Delayus(12);
data=ds18b20->GetBit();
ds18b20->Delayus(50);
return data;
}
3、驅動的使用
我們已經設計并實現了DS18B20溫度傳感器的驅動程序。我們還需要基于這一驅動程序設計一個簡單的應用來驗證其是否正確。
3.1、聲明并初始化對象
使用基于對象的操作我們需要先得到這個對象,所以我們先要使用前面定義的DS18B20溫度傳感器對象類型聲明一個DS18B20溫度傳感器對象變量,具體操作格式如下:
Ds18b20ObjectType ds18b20;
我們雖然聲明了這個對象變量,但還不能立即使用。我們還需要使用驅動中定義的初始化函數對這個變量進行初始化。這個初始化函數所需要的輸入參數如下:
Ds18b20ObjectType *ds18b20,被初始化的對象變量
Ds18b20SetBitType setBit,向總線寫一位操作
Ds18b20GetBitType getBit,從總線讀一位操作
Ds18b20SetPinModeType pinDirection,總線輸入輸出模式控制
Ds18b20DelayType delayus,為秒延時操作
對于這些參數,對象變量我們已經定義了。主要的是我們需要定義幾個函數,并將函數指針作為參數。這幾個函數的類型如下:
/*寫數據位到DS18B20*/
typedef void (*Ds18b20SetBitType)(Ds18b20PinValueType vBit);
/*從DS18B20讀取一位數據*/
typedefuint8_t (*Ds18b20GetBitType)(void);
/*設置DS18B20的數據引腳的輸入輸出模式*/
typedef void (*Ds18b20SetPinModeType)(Ds18b20IOModeType mode);
/* 定義延時操作函數指針類型 */
typedef void (*Ds18b20DelayType)(volatile uint32_t nTime);
對于這幾個函數我們根據樣式定義就可以了,具體的操作可能與使用的硬件平臺有關系。具體函數定義如下:
//設置DS18B20引腳的輸出值
void Ds18b20SetPinOutValue(Ds18b20PinValueType setValue)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,(GPIO_PinState)setValue);
}
//讀取引腳電平
uint8_t Ds18b20ReadPinBit(void)
{
return (uint8_t)HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11);
}
//設置引腳的輸入輸出方向
void Ds18b20SetPinMode(Ds18b20IOModeType mode)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_11;
if(mode==DS18B20_In)
{
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
}
else
{
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
}
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
對于延時函數我們可以采用各種方法實現。我們采用的STM32平臺和HAL庫則可以直接使用HAL_Delay()函數。于是我們可以調用初始化函數如下:
Ds18b20Initialization(&ds18b20,Ds18b20SetPinOutValue,Ds18b20ReadPinBit,Ds18b20SetPinMode,Delayus);
3.2、基于對象進行操作
我們定義了對象變量并使用初始化函數給其作了初始化。接著我們就來考慮操作這一對象獲取我們想要的數據。我們在驅動中已經將獲取數據并轉換為轉換值的比例值,接下來我們使用這一驅動開發我們的應用實例。
/*獲取數據值*/
void GetMeasureDataFromDHT11(void)
{
float temperature;//溫度值
GetDS18b20TemperatureValue(&ds18b20);
temperature=ds18b20.temperature;
}
4、應用總結
我們實現了DS18B20溫度傳感器的驅動程序,并基于這一驅動程序設計了簡單的應用程序。我們也成功獲得了溫度數據,充分說明我們的驅動設計是正確的。事實上,在我們的項目中多次使用DS18B20溫度傳感器,這一驅動也是多次被使用到,結果令人滿意。
單總線數據傳輸時,會改變總線的輸入輸出方向。在我們的應用中,我們修改了對應GPIO引腳的輸入輸出模式。事實上如果我們在STM32中使用時,我們可將該引腳配置為開漏輸出模式,加上總線的上拉電阻,可以在不修改GPIO的輸入輸出模式的情況下實現讀寫。
使用驅動時需要注意,本驅動程序只考慮了總線上只有一個DS18B20的情況。在一條總線上有多個DS18B20溫度傳感器時,當前的驅動程序是不能夠實現操作的,需要對驅動作相應修改。
評論