在一時(shí)候我們需要相對簡單的檢測溫度信號(hào),而DS18B20就是一款功能和應(yīng)用都相對簡單的溫度傳感器,通過單線就可以實(shí)現(xiàn)檢測溫度信號(hào)的需求。這一篇我們就來實(shí)現(xiàn)操作DS18B20獲取溫度數(shù)據(jù)的驅(qū)動(dòng)。
1、功能概述
DS18B20是常用的數(shù)字溫度傳感器,其輸出的是數(shù)字信號(hào),具有體積小,硬件開銷低,抗干擾能力強(qiáng),精度高的特點(diǎn)。單總線數(shù)字式溫度傳感器,由于具有結(jié)構(gòu)簡單,不需要外接電路,可用一根I/O數(shù)據(jù)線既供電又傳輸數(shù)據(jù),可由用戶設(shè)置溫度報(bào)警界限等特點(diǎn),近年來廣泛用于糧庫等需要測量和控制溫度的地方。
1.1、硬件描述
DS18B20數(shù)字溫度傳感器提供9-12位攝氏度溫度測量數(shù)據(jù),可編程非易失存儲(chǔ)器設(shè)置溫度監(jiān)測的上限和下限,提供溫度報(bào)警。器件可以工作在-55°C至+125°C范圍,在-10°C至+85°C范圍內(nèi)測量精度為±0.5°C。此外,DS18B20還可以直接利用數(shù)據(jù)線供電 (寄生供電),無需外部電源。DS18B20數(shù)字溫度傳感器提供有三種封裝,其引腳定義分別如下表所示:
DS18B20數(shù)字溫濕度傳感器有一個(gè)64位ROM存儲(chǔ)器,用于存儲(chǔ)設(shè)備唯一的串行代碼。暫存存儲(chǔ)器包含2字節(jié)的溫度寄存器,該寄存器存儲(chǔ)溫度傳感器的數(shù)字輸出。此外,暫存存儲(chǔ)器提供對1字節(jié)的上、下報(bào)警觸發(fā)寄存器(TH和TL)和1字節(jié)配置寄存器的訪問。DS18B20數(shù)字溫濕度傳感器的功能框圖如下圖所示:
配置寄存器允許用戶將溫度-數(shù)字轉(zhuǎn)換的分辨率設(shè)置為9、10、11或12位。TH、TL和配置寄存器是非易失性的(EEPROM),因此它們將在設(shè)備斷電時(shí)保留數(shù)據(jù)。
1.2、數(shù)據(jù)通訊
DS18B20通過1-Wire總線通信,只需要一條數(shù)據(jù)線 (和地線) 即可與處理器進(jìn)行數(shù)據(jù)傳輸。每個(gè)DS18B20具有唯一的64位序列號(hào),從而允許多個(gè)DS18B20掛接在同一條1-Wire總線。可以方便地采用一個(gè)微處理器控制多個(gè)分布在較大區(qū)域的DS18B20。該功能非常適合HVAC環(huán)境控制、樓宇、大型設(shè)備、機(jī)器、過程監(jiān)測與控制系統(tǒng)內(nèi)部的溫度測量等應(yīng)用。
DS18B20傳感器進(jìn)行的功能操作是在發(fā)送命令的基礎(chǔ)上完成的,上電后傳感器處于空閑狀態(tài),需要控制器發(fā)送命令才能完成溫度轉(zhuǎn)換。訪問DS18B20溫度傳感器需要按照固定的順序操作:
步驟1、初始化通訊
步驟2、操作ROM命令(后面跟著任何需要的數(shù)據(jù)交換)
步驟3、DS18B20功能命令(后面跟著任何需要的數(shù)據(jù)交換)
每次訪問DS18B20時(shí),遵循這個(gè)序列是非常重要的,因?yàn)槿绻蛄兄械娜魏尾襟E丟失或順序混亂,DS18B20將不會(huì)響應(yīng)。這個(gè)規(guī)則的例外是搜索ROM [F0h]和警報(bào)搜索[ECh]命令。發(fā)出這兩個(gè)ROM命令后,主機(jī)必須按順序返回步驟1。
1.2.1、通訊初始化
在單線總線上的所有事務(wù)都以初始化序列開始。初始化序列包括由總線主發(fā)送的復(fù)位脈沖和從服務(wù)器發(fā)送的存在脈沖。存在脈沖讓總線主人知道從設(shè)備(例如DS18B20)在總線上并且準(zhǔn)備好操作。復(fù)位和存在脈沖的時(shí)間在單線信號(hào)部分有詳細(xì)說明。
1.2.2、ROM操作
對傳感器的功能操作的次序是首先完成對芯片內(nèi)部的ROM操作,有5條操作ROM的指令可用于器件識(shí)別,它們分別是:ReadROM(33H)、Match ROM(55H)、Skip ROM(CCH)、SearchROM(F0H)、Alarm Search(ECH)。具體描述如下表所示:
1.2.3、功能操作
實(shí)現(xiàn)DS18B20溫度傳感器操作,需在發(fā)送ROM指令之后發(fā)送功能指令。DS18B20溫度傳感器共有6條功能指令,分別是:溫度轉(zhuǎn)換指令(44H)、讀暫存器指令(BEH)、寫暫存器指令(4EH)、復(fù)制暫存器指令(48H)、重調(diào)EEPROM指令(B8H)、讀電源供電方式指令(B4H)。具體描述見下表所示:
2、驅(qū)動(dòng)設(shè)計(jì)與實(shí)現(xiàn)
我們已經(jīng)了解了DS18B20溫度傳感器的基本情況和數(shù)據(jù)通訊的相關(guān)信息。接下來我們將基于此設(shè)計(jì)并實(shí)現(xiàn)DS18B20溫度傳感器的驅(qū)動(dòng)程序。
2.1、對象定義
我們依然采用基于對象操作的方式來實(shí)現(xiàn),在使用一個(gè)對象之前我們需要獲得這個(gè)對象。同樣的我們想要基于對象操作DS18B20溫度傳感器就需要先定義DS18B20溫度傳感器的對象。
2.1.1、對象的抽象
我們要得到DS18B20溫度傳感器對象,需要先分析其基本特性。一般來說,一個(gè)對象至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個(gè)方面思考一下DS18B20溫度傳感器的對象。
先來考慮屬性,作為屬性肯定是用于標(biāo)識(shí)或記錄對象特征的東西。我們來考慮DS18B20溫度傳感器對象屬性。每一個(gè)DS18B20溫度傳感器都有一個(gè)序列號(hào),在總線上有多個(gè)DS18B20溫度傳感器時(shí),是區(qū)別設(shè)備的唯一標(biāo)識(shí),所以我們將序列號(hào)作為屬性。同時(shí)溫度數(shù)據(jù)表示了DS18B20溫度傳感器當(dāng)前的工作狀態(tài),所以我們也將其作為屬性。
接著我們還需要考慮DS18B20溫度傳感器對象的操作問題。我們知道DS18B20溫度傳感器采用的是單總線通訊,既然是單總線就需要控制總線的輸入輸出方向,而且這對這條總線在不同的輸入輸出方向,我們需要讀數(shù)據(jù)和寫數(shù)據(jù),而這些操作都依賴于硬件平臺(tái),所以我們將它們定義為DS18B20溫度傳感器對象的操作。處于時(shí)序控制的需要,我們需要延時(shí)操作函數(shù),而在不同的軟硬件平臺(tái)延時(shí)操作會(huì)有差異,我們也將其作為對象的操作。
根據(jù)上述我們對DS18B20溫度傳感器的分析,我們可以定義DS18B20溫度傳感器的對象類型如下:
/* 定義DS18B20對象類型 */
typedef struct Ds18b20Object {
Uint8_t sn[6];//Ds18b20元件序列號(hào)
float temperature;//溫度數(shù)據(jù)
void (*SetBit)(Ds18b20PinValueType vBit);//寫數(shù)據(jù)位到DS18B20
uint8_t (*GetBit)(void);//從DS18B20讀取一位數(shù)據(jù)
void (*SetPinMode)(Ds18b20IOModeType mode);//設(shè)置DS18B20的數(shù)據(jù)引腳的輸入輸出模式
void (*Delayus)(volatile uint32_t nTime); //延時(shí)us操作指針
}Ds18b20ObjectType;
2.1.2、對象初始化
我們知道,一個(gè)對象僅作聲明是不能使用的,我們需要先對其進(jìn)行初始化,所以這里我們來考慮DS18B20溫度傳感器對象的初始化函數(shù)。一般來說,初始化函數(shù)需要處理幾個(gè)方面的問題。一是檢查輸入參數(shù)是否合理;二是為對象的屬性賦初值;三是對對象作必要的初始化配置。據(jù)此我們設(shè)計(jì)DS18B20溫度傳感器對象的初始化函數(shù)如下:
/*對DS18B20操作進(jìn)行初始化*/
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、對象操作
我們已經(jīng)完成了DS18B20溫度傳感器對象類型的定義和對象初始化函數(shù)的設(shè)計(jì)。得到對象并非我們的目標(biāo),我們的主要目標(biāo)是獲取對象的數(shù)據(jù),接下來我們還要實(shí)現(xiàn)面向DS18B20溫度傳感器的各類操作。
2.2.1、初始化通訊
在單線總線上的所有事務(wù)都以初始化序列開始。初始化序列包括由主機(jī)發(fā)送的復(fù)位脈沖和從從設(shè)備(如DS18B20)發(fā)送的存在脈沖。存在脈沖讓總線主機(jī)知道從設(shè)備(例如DS18B20)在總線上并且準(zhǔn)備好操作。復(fù)位和存在脈沖的操作時(shí)序如下圖:
其操作過程描述如下:
(1) 先將數(shù)據(jù)線置高電平“1”。
(2) 延時(shí)(該時(shí)間要求的不是很嚴(yán)格,但是盡可能的短一點(diǎn))
(3) 數(shù)據(jù)線拉到低電平“0”。
(4) 延時(shí)750微秒(該時(shí)間的時(shí)間范圍可以從480到960微秒)。
(5) 數(shù)據(jù)線拉到高電平“1”。
(6) 延時(shí)等待(如果初始化成功則在15到60微秒時(shí)間之內(nèi)產(chǎn)生一個(gè)由DS18B20所返回的低電平“0”。據(jù)該狀態(tài)可以來確定它的存在,但是應(yīng)注意不能無限的進(jìn)行等待,不然會(huì)使程序進(jìn)入死循環(huán),所以要進(jìn)行超時(shí)控制)。
(7) 若CPU讀到了數(shù)據(jù)線上的低電平“0”后,還要做延時(shí),其延時(shí)的時(shí)間從發(fā)出的高電平算起(第(5)步的時(shí)間算起)最少要480微秒。
(8) 將數(shù)據(jù)線再次拉高到高電平“1”后結(jié)束。
/*主機(jī)給從機(jī)發(fā)送復(fù)位脈沖*/
static void ResetDs18b20(Ds18b20ObjectType *ds18b20)
{
/* 主機(jī)設(shè)置為推挽輸出*/
ds18b20->SetPinMode(DS18B20_Out);
/* 主機(jī)至少產(chǎn)生480us 的低電平復(fù)位信號(hào)*/
ds18b20->SetBit(DS18B20_Reset);
ds18b20->Delayus(550);
/* 主機(jī)在產(chǎn)生復(fù)位信號(hào)后,需將總線拉高*/
ds18b20->SetBit(DS18B20_Set);
/*從機(jī)接收到主機(jī)的復(fù)位信號(hào)后,會(huì)在15~60us 后給主機(jī)發(fā)一個(gè)存在脈沖*/
ds18b20->Delayus(15);
}
/*檢測從機(jī)給主機(jī)返回的存在脈沖 0:成功;1:失敗*/
static uint8_t PresenceDs18b20(Ds18b20ObjectType *ds18b20)
{
uint8_t pulse_time = 0;
/* 主機(jī)設(shè)置為上拉輸入*/
ds18b20->SetPinMode(DS18B20_In);
/* 等待存在脈沖的到來,存在脈沖為一個(gè)60~240us 的低電平信號(hào)*/
/*如果存在脈沖沒有來則做超時(shí)處理,從機(jī)接收到主機(jī)的復(fù)位信號(hào)后,會(huì)在15~60us 后給主機(jī)發(fā)一個(gè)存在脈沖*/
while( ds18b20->GetBit() && pulse_time<100 )
{
pulse_time++;
ds18b20->Delayus(1);
}
/* 經(jīng)過100us 后,存在脈沖都還沒有到來*/
if( pulse_time >=100 )
return 1;
else
pulse_time = 0;
/* 存在脈沖到來,且存在的時(shí)間不能超過240us*/
while(!ds18b20->GetBit() && (pulse_time<240))
{
pulse_time++;
ds18b20->Delayus(1);
}
if( pulse_time >=240 )
{
return 1;
}
else
{
return 0;
}
}
2.2.2、寫操作
主機(jī)寫DS18B20有兩種類型的寫時(shí)時(shí)段:“寫1”時(shí)間段和“寫0”時(shí)間段??偩€主機(jī)使用一個(gè)寫1時(shí)間段來將邏輯1寫入DS18B20,而一個(gè)寫0時(shí)間段來將邏輯0寫入DS18B20。所有寫時(shí)段必須至少60μs持續(xù)時(shí)間與個(gè)人之間的最小1μs復(fù)蘇的時(shí)間寫插槽。兩種類型的寫時(shí)間段都是由主控器將單線總線拉低來啟動(dòng)的。其操作時(shí)序如下圖:
我們可以總結(jié)其操作過程如下:
(1) 數(shù)據(jù)線先置低電平“0”。
(2) 延時(shí)確定的時(shí)間為15微秒。
(3) 按從低位到高位的順序發(fā)送字節(jié)(一次只發(fā)送一位)。
(4) 延時(shí)時(shí)間為45微秒。
(5) 將數(shù)據(jù)線拉到高電平。
(6) 重復(fù)上(1)到(6)的操作直到所有的字節(jié)全部發(fā)送完為止。
(7) 最后將數(shù)據(jù)線拉高。
/*向DS18B20寫一個(gè)字節(jié)*/
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的時(shí)間至少要大于60us
if (testb)
{
ds18b20->SetBit(DS18B20_Reset);
// 1us < 這個(gè)延時(shí) < 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(恢復(fù)時(shí)間) < 無窮大
}
ds18b20->Delayus(2);
}
}
2.2.3、讀操作
DS18B20只能在主機(jī)發(fā)出讀時(shí)段時(shí)將數(shù)據(jù)傳輸給主機(jī)。因此,主控機(jī)必須在發(fā)出read Scratchpad [BEh]或read Power Supply [B4h]命令后立即生成都時(shí)間段,以便DS18B20能夠提供所請求的數(shù)據(jù)。另外,在發(fā)出Convert T [44h]或Recall E2 [B8h]命令后,主機(jī)可以生成讀時(shí)間段,以查看操作的狀態(tài),具體操作如下列時(shí)序圖:
對上述描述和時(shí)序圖我們可以得到相關(guān)的讀操作步驟:
(1)將數(shù)據(jù)線拉高“1”。
(2)延時(shí)2微秒。
(3)將數(shù)據(jù)線拉低“0”。
(4)延時(shí)3微秒。
(5)將數(shù)據(jù)線拉高“1”。
(6)延時(shí)5微秒。
(7)讀數(shù)據(jù)線的狀態(tài)得到1個(gè)狀態(tài)位,并進(jìn)行數(shù)據(jù)處理。
(8)延時(shí)60微秒。
/*從DS18B20讀取一個(gè)位,返回值: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、驅(qū)動(dòng)的使用
我們已經(jīng)設(shè)計(jì)并實(shí)現(xiàn)了DS18B20溫度傳感器的驅(qū)動(dòng)程序。我們還需要基于這一驅(qū)動(dòng)程序設(shè)計(jì)一個(gè)簡單的應(yīng)用來驗(yàn)證其是否正確。
3.1、聲明并初始化對象
使用基于對象的操作我們需要先得到這個(gè)對象,所以我們先要使用前面定義的DS18B20溫度傳感器對象類型聲明一個(gè)DS18B20溫度傳感器對象變量,具體操作格式如下:
Ds18b20ObjectType ds18b20;
我們雖然聲明了這個(gè)對象變量,但還不能立即使用。我們還需要使用驅(qū)動(dòng)中定義的初始化函數(shù)對這個(gè)變量進(jìn)行初始化。這個(gè)初始化函數(shù)所需要的輸入?yún)?shù)如下:
Ds18b20ObjectType *ds18b20,被初始化的對象變量
Ds18b20SetBitType setBit,向總線寫一位操作
Ds18b20GetBitType getBit,從總線讀一位操作
Ds18b20SetPinModeType pinDirection,總線輸入輸出模式控制
Ds18b20DelayType delayus,為秒延時(shí)操作
對于這些參數(shù),對象變量我們已經(jīng)定義了。主要的是我們需要定義幾個(gè)函數(shù),并將函數(shù)指針作為參數(shù)。這幾個(gè)函數(shù)的類型如下:
/*寫數(shù)據(jù)位到DS18B20*/
typedef void (*Ds18b20SetBitType)(Ds18b20PinValueType vBit);
/*從DS18B20讀取一位數(shù)據(jù)*/
typedefuint8_t (*Ds18b20GetBitType)(void);
/*設(shè)置DS18B20的數(shù)據(jù)引腳的輸入輸出模式*/
typedef void (*Ds18b20SetPinModeType)(Ds18b20IOModeType mode);
/* 定義延時(shí)操作函數(shù)指針類型 */
typedef void (*Ds18b20DelayType)(volatile uint32_t nTime);
對于這幾個(gè)函數(shù)我們根據(jù)樣式定義就可以了,具體的操作可能與使用的硬件平臺(tái)有關(guān)系。具體函數(shù)定義如下:
//設(shè)置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);
}
//設(shè)置引腳的輸入輸出方向
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);
}
對于延時(shí)函數(shù)我們可以采用各種方法實(shí)現(xiàn)。我們采用的STM32平臺(tái)和HAL庫則可以直接使用HAL_Delay()函數(shù)。于是我們可以調(diào)用初始化函數(shù)如下:
Ds18b20Initialization(&ds18b20,Ds18b20SetPinOutValue,Ds18b20ReadPinBit,Ds18b20SetPinMode,Delayus);
3.2、基于對象進(jìn)行操作
我們定義了對象變量并使用初始化函數(shù)給其作了初始化。接著我們就來考慮操作這一對象獲取我們想要的數(shù)據(jù)。我們在驅(qū)動(dòng)中已經(jīng)將獲取數(shù)據(jù)并轉(zhuǎn)換為轉(zhuǎn)換值的比例值,接下來我們使用這一驅(qū)動(dòng)開發(fā)我們的應(yīng)用實(shí)例。
/*獲取數(shù)據(jù)值*/
void GetMeasureDataFromDHT11(void)
{
float temperature;//溫度值
GetDS18b20TemperatureValue(&ds18b20);
temperature=ds18b20.temperature;
}
4、應(yīng)用總結(jié)
我們實(shí)現(xiàn)了DS18B20溫度傳感器的驅(qū)動(dòng)程序,并基于這一驅(qū)動(dòng)程序設(shè)計(jì)了簡單的應(yīng)用程序。我們也成功獲得了溫度數(shù)據(jù),充分說明我們的驅(qū)動(dòng)設(shè)計(jì)是正確的。事實(shí)上,在我們的項(xiàng)目中多次使用DS18B20溫度傳感器,這一驅(qū)動(dòng)也是多次被使用到,結(jié)果令人滿意。
單總線數(shù)據(jù)傳輸時(shí),會(huì)改變總線的輸入輸出方向。在我們的應(yīng)用中,我們修改了對應(yīng)GPIO引腳的輸入輸出模式。事實(shí)上如果我們在STM32中使用時(shí),我們可將該引腳配置為開漏輸出模式,加上總線的上拉電阻,可以在不修改GPIO的輸入輸出模式的情況下實(shí)現(xiàn)讀寫。
使用驅(qū)動(dòng)時(shí)需要注意,本驅(qū)動(dòng)程序只考慮了總線上只有一個(gè)DS18B20的情況。在一條總線上有多個(gè)DS18B20溫度傳感器時(shí),當(dāng)前的驅(qū)動(dòng)程序是不能夠?qū)崿F(xiàn)操作的,需要對驅(qū)動(dòng)作相應(yīng)修改。
評論
查看更多