溫度傳感器DS18B20

發(fā)布時間:2024-03-07
ds18b20是美信公司的一款溫度傳感器,單片機(jī)可以通過1-wire和ds18b20進(jìn)行通信,最終將溫度讀出。1-wire總線的硬件接口很簡單,只需要把18b20的數(shù)據(jù)引腳和單片機(jī)的一個io口接上就可以通信。硬件的簡單,隨之而來的,就是軟件時序的復(fù)雜。1-wire總線的時序比較復(fù)雜,很多同學(xué)在這里獨(dú)立看時序圖都看不明白,所以這里還要帶著大家來研究18b20的時序圖。我們先來看一下ds18b20的硬件原理圖,如圖1所示。
圖1ds18b20
ds18b20通過編程,可以實(shí)現(xiàn)最高12位的溫度存儲值,在寄存器中,以補(bǔ)碼的格式存儲,如圖2所示。
圖2ds18b20溫度表示
一共2個字節(jié),lsb是低字節(jié),msb是高字節(jié),其中msb是字節(jié)的高位,lsb是字節(jié)的低位。大家可以看出來,二進(jìn)制數(shù)字,每一位代表的溫度的含義,都表示出來了。其中s表示的是符號位,低11位都是2的冪,用來表示最終的溫度。ds18b20的溫度測量范圍是從-55度到+125度,而溫度數(shù)據(jù)的表現(xiàn)形式,有正負(fù)溫度,寄存器中每個數(shù)字如同卡尺的刻度一樣分布,如圖3所示。
圖3ds18b20溫度顯示
二進(jìn)制數(shù)字最低位變化1,代表溫度變化0.0625度的映射關(guān)系。當(dāng)0度的時候,那就是0x0000,當(dāng)溫度125度的時候,對應(yīng)十六進(jìn)制是0x07d0,當(dāng)溫度是零下55度的時候,對應(yīng)的數(shù)字是0xfc90。反過來說,當(dāng)數(shù)字是0x0001的時候,那溫度就是0.0625度了。
首先,我先根據(jù)手冊上ds18b20工作協(xié)議過程大概講解一下。
1、初始化。和i2c的尋址類似,1-wire總線開始也需要檢測這條總線上是否存在ds18b20這個器件。如果這條總線上存在ds18b20,總線會根據(jù)時序要求返回一個低電平脈沖,如果不存在的話,也就不會返回脈沖,即總線保持為高電平,所以習(xí)慣上稱之為檢測存在脈沖。此外,獲取存在脈沖不僅僅是檢測是否存在ds18b20,還要通過這個脈沖過程通知ds18b20準(zhǔn)備好,單片機(jī)要進(jìn)行操作它了,如圖4所示。
圖4獲取存在脈沖
大家注意看圖,實(shí)粗線是我們單片機(jī)io口拉低這個引腳,虛粗線是ds18b20拉低這個引腳,細(xì)線是單片機(jī)和ds18b20釋放總線后,依靠上拉電阻的作用把io口引腳拉上去的。這個我們前邊提到過了,51單片機(jī)釋放總線就是給高電平即可。
存在脈沖檢測過程,首先我們單片機(jī)要拉低這個引腳,持續(xù)大概480us到960us之間的時間即可,我們的程序中持續(xù)了500us。然后,單片機(jī)釋放總線,就是給高電平,ds18b20等待大概15到60us后,會主動拉低這個引腳大概是60到240us,而后ds18b20會主動釋放總線,這樣io口會被上拉電阻自動拉高。
有的同學(xué)還是不能夠徹底理解,程序列出來逐句解釋。首先,由于ds18b20時序要求非常嚴(yán)格,所以在操作時序的時候,為了防止中斷干擾總線時序,先關(guān)閉總中斷。然后第一步,拉低ds18b20這個引腳,持續(xù)500us;第二步,延時60us;第三步,讀取存在脈沖,并且等待存在脈沖結(jié)束。
bitget18b20ack(void)//復(fù)位總線,獲取存在脈沖,以啟動一次讀寫操作
{
bitack;
ea=0;//禁止總中斷
io_18b20=0;//產(chǎn)生500us復(fù)位脈沖
delayx10us(50);
io_18b20=1;
delayx10us(6);//延時60us
ack=io_18b20;//讀取存在脈沖
while(!io_18b20);//等待存在脈沖結(jié)束
ea=1;//重新使能總中斷
returnack;
}
很多同學(xué)對第二步不理解,時序圖上明明是ds18b20等待15us到60us,為什么要延時60us呢?舉個例子,媽媽在做飯,告訴你大概5分鐘到10分鐘飯就可以吃了,那么我們什么時候去吃,能夠絕對保證吃上飯呢?很明顯,10分鐘以后去吃肯定可以吃上飯。同樣的道理,ds18b20等待大概是15us到60us,我們要保證讀到這個存在脈沖,那么60us以后去讀肯定可以讀到。當(dāng)然,不能延時太久,太久,超過75us,就可能讀不到了,為什么是75us,大家自己思考一下。
2、rom操作指令。我們學(xué)i2c總線的時候,總線上可以掛多個器件,通過不同的器件地址來訪問不同的器件。同樣,1-wire總線也可以掛多個器件,但是他只有一條線,如何區(qū)分不同的器件呢?
在每個ds18b20內(nèi)部都有一個唯一的64位長的序列號,這個序列號值就存在ds18b20內(nèi)部的rom中。開始的8位是產(chǎn)品類型編碼(ds18b20是10h),接著的48位是每個器件唯一的序號,最后的8位是crc校驗(yàn)碼。ds18b20可以引出去很長的線,最長可以到幾十米,測不同位置的溫度。單片機(jī)可以通過和ds18b20之間的通信,獲取每個傳感器所采集到的溫度信息,也可以同時給所有的ds18b20發(fā)送一些指令。這些指令相對來說比較復(fù)雜,而且應(yīng)用很少,所以這里大家有興趣自己查手冊自己完成,我們這里只講一條總線上只接一個器件的指令和程序。
skiprom(跳過rom):0xcc。當(dāng)總線上只有一個器件的時候,可以跳過rom,不進(jìn)行rom檢測。
3、ram存儲器操作指令。
ram讀取指令,只講2條,其他的大家有需要可以隨時去查資料。
readscratchpad(讀暫存寄存器):0xbe
這里要注意的是,我們的ds18b20的溫度數(shù)據(jù)是2個字節(jié),我們讀取數(shù)據(jù)的時候,先讀取到的是低字節(jié)的低位,讀完了第一個字節(jié)后,再讀高字節(jié)的低位,一直到兩個字節(jié)全部讀取完畢。
converttemperature(啟動溫度轉(zhuǎn)換):0x44
當(dāng)我們發(fā)送一個啟動溫度轉(zhuǎn)換的指令后,ds18b20開始進(jìn)行轉(zhuǎn)換。從轉(zhuǎn)換開始到獲取溫度,ds18b20是需要時間的,而這個時間長短取決于ds18b20的精度。前邊說ds18b20最高可以用12位來存儲溫度,但是也可以用11位,10位和9位一共四種格式。位數(shù)越高,精度越高,9位模式最低位變化1溫度變化0.5度,同時轉(zhuǎn)換速度也要快一些,如圖5所示。
圖5ds18b20溫度轉(zhuǎn)換時間
其中寄存器r1和r0決定了轉(zhuǎn)換的位數(shù),出場默認(rèn)值就是11,也就是12位表示溫度,最大的轉(zhuǎn)換時間是750ms。當(dāng)啟動轉(zhuǎn)換后,至少要再等750ms之后才能讀取溫度,否則讀到的溫度有可能是錯誤的值。這就是為什么很多同學(xué)讀ds18b20的時候,第一次讀出來的是85度,這個值要么是沒有啟動轉(zhuǎn)換,要么是啟動轉(zhuǎn)換了,但還沒有等待一次轉(zhuǎn)換徹底完成,讀到的是一個錯誤的數(shù)據(jù)。
4、ds18b20的位讀寫時序。
ds18b20的時序圖不是很好理解,大家對照時序圖,結(jié)合我的解釋學(xué)明白。寫時序圖如圖6所示。
圖6ds18b20位寫入時序
當(dāng)要給ds18b20寫入‘0’的時候,單片機(jī)直接將引腳拉低,持續(xù)時間大于60us小于120us就可以了。圖上顯示的意思是,單片機(jī)先拉低15us之后,ds18b20會在從15us到60us之間的時間來讀取這一位,ds18b20最早會15us的時刻讀取,典型值是30us的時刻讀取,最多不會超過60us,ds18b20必然讀取完畢,所以持續(xù)時間超過60us即可。
當(dāng)要給ds18b20寫入‘1’的時候,單片機(jī)先將這個引腳拉低,拉低時間大于1us,然后馬上釋放總線,即拉高引腳,并且持續(xù)時間也要大于60us。和寫‘0’類似的是,ds18b20會在15到60us之間來讀取這個‘1’。
可以看出來,ds18b20的時序比較嚴(yán)格,寫的過程中最好不要有中斷打斷,但是在兩個“位”之間的間隔,是大于1小于無窮的,那在這個時間段,我們是可以開中斷來處理其他程序的。發(fā)送一個字節(jié)的數(shù)據(jù)程序如下。
voidwrite18b20(unsignedchardat)//向ds18b20寫入一個字節(jié)數(shù)據(jù)
{
unsignedcharmask;
ea=0;//禁止總中斷
for(mask=0x01;mask!=0;mask<<=1)//低位在先,依次移出8個bit
{
io_18b20=0;//產(chǎn)生2us低電平脈沖
_nop_();
_nop_();
if((mask&dat)==0)//輸出該bit值
io_18b20=0;
else
io_18b20=1;
delayx10us(6);//延時60us
io_18b20=1;//拉高通信引腳
}
ea=1;//重新使能總中斷
}
讀時序圖如圖7所示。
圖7 ds18b20位讀取時序
當(dāng)要讀取ds18b20的數(shù)據(jù)的時候,我們的單片機(jī)首先要拉低這個引腳,并且至少保持1us的時間,然后釋放引腳,釋放完畢后要盡快讀取。從拉低這個引腳到讀取引腳狀態(tài),不能超過15us。大家從圖7可以看出來,主機(jī)采樣時間,也就是mastersamples,是在15us之內(nèi)必須完成的,讀取一個字節(jié)數(shù)據(jù)的程序如下。
unsignedcharread18b20(void)//從ds18b20讀取一個字節(jié)數(shù)據(jù)
{
unsignedchardat;
unsignedcharmask;
ea=0;//禁止總中斷
for(mask=0x01;mask!=0;mask<<=1)//低位在先,依次采集8個bit
{
io_18b20=0;//產(chǎn)生2us低電平脈沖
_nop_();
_nop_();
io_18b20=1;//結(jié)束低電平脈沖,等待18b20輸出數(shù)據(jù)
_nop_();//延時2us
_nop_();
if(!io_18b20)//讀取通信引腳上的值
dat&=~mask;
else
dat|=mask;
delayx10us(6);//再延時60us
}
ea=1;//重新使能總中斷
returndat;
}
ds18b20所表示的溫度值中,有小數(shù)和整數(shù)兩部分。常用的帶小數(shù)的數(shù)據(jù)處理方法有兩種,一種是定義成浮點(diǎn)型直接小數(shù)整數(shù)處理,第二種是定義成整型,然后把小數(shù)和整數(shù)部分分離出來,在合適的位置點(diǎn)上小數(shù)點(diǎn)即可。我們在程序中使用的是第二種方法,下面我們就寫一個程序,將我們讀到的溫度值顯示在1602液晶上,并且保留一位小數(shù)數(shù)字。
/***********************lcd1602.c文件程序源代碼*************************/
#include<reg52.h>
#definelcd1602_dbp0
sbitlcd1602_rs=p1^0;
sbitlcd1602_rw=p1^1;
sbitlcd1602_e=p1^5;
voidlcdwaitready()//等待液晶準(zhǔn)備好
{
unsignedcharsta;
lcd1602_db=0xff;
lcd1602_rs=0;
lcd1602_rw=1;
do
{
lcd1602_e=1;
sta=lcd1602_db;//讀取狀態(tài)字
lcd1602_e=0;
}while(sta&0x80);//bit7等于1表示液晶正忙,重復(fù)檢測直到其等于0為止
}
voidlcdwritecmd(unsignedcharcmd)//寫入命令函數(shù)
{
lcdwaitready();
lcd1602_rs=0;
lcd1602_rw=0;
lcd1602_db=cmd;
lcd1602_e=1;
lcd1602_e=0;
}
voidlcdwritedat(unsignedchardat)//寫入數(shù)據(jù)函數(shù)
{
lcdwaitready();
lcd1602_rs=1;
lcd1602_rw=0;
lcd1602_db=dat;
lcd1602_e=1;
lcd1602_e=0;
}
voidlcdshowstr(unsignedcharx,unsignedchary,constunsignedchar*str)//顯示字符串,屏幕起始坐標(biāo)(x,y),字符串指針str
{
unsignedcharaddr;
//由輸入的顯示坐標(biāo)計(jì)算顯示ram的地址
if(y==0)
addr=0x00+x;//第一行字符地址從0x00起始
else
addr=0x40+x;//第二行字符地址從0x40起始
//由起始顯示ram地址連續(xù)寫入字符串
lcdwritecmd(addr|0x80);//寫入起始地址
while(*str!='\0')//連續(xù)寫入字符串?dāng)?shù)據(jù),直到檢測到結(jié)束符
{
lcdwritedat(*str);
str++;
}
}
voidlcdinit()//液晶初始化函數(shù)
{
lcdwritecmd(0x38);//16*2顯示,5*7點(diǎn)陣,8位數(shù)據(jù)接口
lcdwritecmd(0x0c);//顯示器開,光標(biāo)關(guān)閉
lcdwritecmd(0x06);//文字不動,地址自動+1
lcdwritecmd(0x01);//清屏
}
/***********************ds18b20.c文件程序源代碼*************************/
#include<reg52.h>
#include<intrins.h>
sbitio_18b20=p3^2;//ds18b20通信引腳
voiddelayx10us(unsignedchart)//軟件延時函數(shù),延時時間(t*10)us
{
do{
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
}while(--t);
}
bitget18b20ack(void)//復(fù)位總線,獲取存在脈沖,以啟動一次讀寫操作
{
bitack;
ea=0;//禁止總中斷
io_18b20=0;//產(chǎn)生500us復(fù)位脈沖
delayx10us(50);
io_18b20=1;
delayx10us(6);//延時60us
ack=io_18b20;//讀取存在脈沖
while(!io_18b20);//等待存在脈沖結(jié)束
ea=1;//重新使能總中斷
returnack;
}
voidwrite18b20(unsignedchardat)//向ds18b20寫入一個字節(jié)數(shù)據(jù)
{
unsignedcharmask;
ea=0;//禁止總中斷
for(mask=0x01;mask!=0;mask<<=1)//低位在先,依次移出8個bit
{
io_18b20=0;//產(chǎn)生2us低電平脈沖
_nop_();
_nop_();
if((mask&dat)==0)//輸出該bit值
io_18b20=0;
else
io_18b20=1;
delayx10us(6);//延時60us
io_18b20=1;//拉高通信引腳
}
ea=1;//重新使能總中斷
}
unsignedcharread18b20(void)//從ds18b20讀取一個字節(jié)數(shù)據(jù)
{
unsignedchardat;
unsignedcharmask;
ea=0;//禁止總中斷
for(mask=0x01;mask!=0;mask<<=1)//低位在先,依次采集8個bit
{
io_18b20=0;//產(chǎn)生2us低電平脈沖
_nop_();
_nop_();
io_18b20=1;//結(jié)束低電平脈沖,等待18b20輸出數(shù)據(jù)
_nop_();//延時2us
_nop_();
if(!io_18b20)//讀取通信引腳上的值
dat&=~mask;
else
dat|=mask;
delayx10us(6);//再延時60us
}
ea=1;//重新使能總中斷
returndat;
}
bitstart18b20()//啟動一次18b20溫度轉(zhuǎn)換,返回值代表是否啟動成功
{
bitack;
ack=get18b20ack();//執(zhí)行總線復(fù)位,并獲取18b20應(yīng)答
if(ack==0)//如18b20正確應(yīng)答,則啟動一次轉(zhuǎn)換
{
write18b20(0xcc);//跳過rom操作
write18b20(0x44);//啟動一次溫度轉(zhuǎn)換
}
return~ack;//ack==0表示操作成功,所以返回值為其取反值
}
bitget18b20temp(int*temp)//讀取ds18b20溫度值,返回值代表是否讀取成功
{
bitack;
unsignedcharlsb,msb;//16bit溫度值的低字節(jié)和高字節(jié)
ack=get18b20ack();//執(zhí)行總線復(fù)位,并獲取18b20應(yīng)答
if(ack==0)//如18b20正確應(yīng)答,則讀取溫度值
{
write18b20(0xcc);//跳過rom操作
write18b20(0xbe);//發(fā)送讀命令
lsb=read18b20();//讀溫度值的低字節(jié)
msb=read18b20();//讀溫度值的高字節(jié)
*temp=((int)msb<<8)+lsb;//合成為16bit整型數(shù)
}
return~ack;//ack==0表示操作應(yīng)答,所以返回值為其取反值
}
/***********************main.c文件程序源代碼*************************/
#include<reg52.h>
bitflag1s=0;//1s定時標(biāo)志
unsignedchart0rh=0;//t0重載值的高字節(jié)
unsignedchart0rl=0;//t0重載值的低字節(jié)
voidconfigtimer0(unsignedintms);
unsignedcharinttostring(unsignedchar*str,intdat);
externbitstart18b20();
externbitget18b20temp(int*temp);
externvoidlcdinit();
externvoidlcdshowstr(unsignedcharx,unsignedchary,constunsignedchar*str);
voidmain()
{
bitres;
inttemp;//讀取到的當(dāng)前溫度值
intintt,dect;//溫度值的整數(shù)和小數(shù)部分
unsignedcharlen;
unsignedcharstr[12];
lcdinit();//初始化液晶
start18b20();//啟動ds18b20
configtimer0(10);//t0定時10ms
ea=1;//開總中斷
while(1)
{
if(flag1s)//每秒更新一次溫度
{
flag1s=0;
res=get18b20temp(&temp);//讀取當(dāng)前溫度
if(res)//讀取成功時,刷新當(dāng)前溫度顯示
{
intt=temp>>4;//分離出溫度值整數(shù)部分
dect=temp&0xf;//分離出溫度值小數(shù)部分
len=inttostring(str,intt);//整數(shù)部分轉(zhuǎn)換為字符串
str[len++]='.';//添加小數(shù)點(diǎn)
dect=(dect*10)/16;//二進(jìn)制的小數(shù)部分轉(zhuǎn)換為1位十進(jìn)制位
str[len++]=dect+'0';//十進(jìn)制小數(shù)位再轉(zhuǎn)換為ascii字符
while(len<6)//用空格補(bǔ)齊到6個字符長度
{
str[len++]='';
}
str[len]='\0';//添加字符串結(jié)束符
lcdshowstr(0,0,str);//顯示到液晶屏上
}
else//讀取失敗時,提示錯誤信息
{
lcdshowstr(0,0,error!);
}
start18b20();//重新啟動下一次轉(zhuǎn)換
}
}
}
unsignedcharinttostring(unsignedchar*str,intdat)//整型數(shù)轉(zhuǎn)換為十進(jìn)制字符串,返回值為轉(zhuǎn)換后的字符串長度
{
signedchari;
unsignedcharlen=0;
unsignedcharbuf[6];
if(dat<0)//如果為負(fù)數(shù),首先取絕對值,并添加負(fù)號
{
dat=-dat;
*str++='-';
len++;
}
for(i=0;i<=4;i++)//由低到高轉(zhuǎn)換為十進(jìn)制位
{
buf[i]=dat%10;
dat/=10;
}
for(i=4;i>=1;i--)//查找有效數(shù)字最高位,以忽略更高位的‘0’
{
if(buf[i]!=0)
{
break;
}
}
for(;i>=0;i--)//有效數(shù)字位轉(zhuǎn)換為ascii碼
{
*str++=buf[i]+'0';
len++;
}
*str='\0';//添加字符串結(jié)束符
returnlen;//返回字符串長度
}
voidconfigtimer0(unsignedintms)//t0配置函數(shù)
{
unsignedlongtmp;
tmp=11059200/12;//定時器計(jì)數(shù)頻率
tmp=(tmp*ms)/1000;//計(jì)算所需的計(jì)數(shù)值
tmp=65536-tmp;//計(jì)算定時器重載值
tmp=tmp+12;//修正中斷響應(yīng)延時造成的誤差
t0rh=(unsignedchar)(tmp>>8);//定時器重載值拆分為高低字節(jié)
t0rl=(unsignedchar)tmp;
tmod&=0xf0;//清零t0的控制位
tmod|=0x01;//配置t0為模式1
th0=t0rh;//加載t0重載值
tl0=t0rl;
et0=1;//使能t0中斷
tr0=1;//啟動t0
}
voidinterrupttimer0()interrupt1//t0中斷服務(wù)函數(shù)
{
staticunsignedchartmr1s=0;
th0=t0rh;//定時器重新加載重載值
tl0=t0rl;
tmr1s++;
if(tmr1s>=100)//定時1s
{
tmr1s=0;
flag1s=1;
}
上一個:鐵路煤炭運(yùn)輸抑塵劑噴灑好處
下一個:竣工驗(yàn)收位發(fā)言程序

耐火阻燃電纜的特性及使用與檢測
中國現(xiàn)存有哪些珍稀的茶樹?
鼠標(biāo)點(diǎn)擊哪里都沒反應(yīng)怎么辦(鼠標(biāo)點(diǎn)擊哪里都沒反應(yīng)了)
單級可調(diào)光離線 AC/DC 控制器的設(shè)計(jì)
車載u盤什么牌子音質(zhì)好用,車載U盤哪個牌子的好
煙焦與霉變的茶不可飲用
電腦上的文件隱藏起來找不到了怎么辦(電腦上的文件隱藏之后要怎么恢復(fù))
怎么看自己的哪個盤是固態(tài)的,怎么看哪個是固態(tài)硬盤的
廣聯(lián)達(dá)GCL2008土建算量軟件問題答疑匯總
域名備案的檢驗(yàn)單用畫圖簽名可以嗎
十八禁 网站在线观看免费视频_2020av天堂网_一 级 黄 色 片免费网站_绝顶高潮合集Videos