亚洲春色中文字幕久久久-三上亚,一吻二脱三床四吻胸,国产真实伦对白视频全集,在线毛片观看,精品成品入口黄网,国产毛aⅴ片久久久,亚洲AV色香蕉一区二区三区老师,萧皇后A级艳片,色情日本视频更新,99久久亚洲精品日本无码

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 503|回復: 2
打印 上一主題 下一主題
收起左側(cè)

第13章 DS18B20溫度傳感器和Flash存儲器13.1

[復制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:1167894 發(fā)表于 2026-4-30 13:12 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式
DS18B20是一種常用的溫度傳感器,提供了感知周圍溫度的手段。Flash存儲器是一款常用的數(shù)據(jù)存儲器件,相比較于EEPROMFLASH的存儲容量更大、單位成本更低。
本章除了學習這兩個器件外,還要學習控制這兩種器件的兩種通信協(xié)議--1-wire總線協(xié)議(一般常用)SPI總線協(xié)議(重要且常用)。
13.1 溫度傳感器DS18B20
DS18B20是美信公司的一款溫度傳感器,單片機可以通過1-Wire協(xié)議與DS18B20進行通信,最終將溫度讀出。1-Wire總線的硬件接口很簡單,只需要DS18B20的數(shù)據(jù)引腳和單片機的一個I/O口接上就可以了。先來看一下DS18B20的硬件原理圖,如圖13-1所示。

13-1  DS18B20電路原理圖
DS18B20通過編程,可以實現(xiàn)最高12位的溫度存儲值,在寄存器中,以補碼的格式存儲(補碼的相關(guān)內(nèi)容請自學了解),如圖13-2所示。

13-2  DS18B20溫度數(shù)據(jù)格式
共兩個字節(jié),LSB是低字節(jié),MSB是高字節(jié),其中MSb是字節(jié)的高位,LSb是字節(jié)的低位。每一位代表的溫度的含義,都表示出來。其中S表示的是符號位,低11位都是2的冪,用來表示最終的溫度。DS18B20的溫度測量范圍是從-55度到+125度,而溫度數(shù)據(jù)的表現(xiàn)形式,有正負溫度,寄存器中每個數(shù)字如同卡尺的刻度一樣分布,如圖13-3所示。

13-3  DS18B20溫度值
二進制數(shù)字最低位變化1,代表溫度變化0.0625度的映射關(guān)系。當0度的時候,就是0x0000,當溫度125度的時候,對應十六進制是0x07D0,當溫度是零下55度的時候,對應的數(shù)字是0xFC90。當數(shù)字是0x0001的時候,那溫度就是0.0625度了。
首先根據(jù)手冊上DS18B20工作協(xié)議過程簡單介紹。
1)初始化。和I2C的尋址類似,1-Wire總線開始也需要檢測這條總線上是否存在DS18B20這個器件。如果這條總線上存在DS18B20,總線會根據(jù)時序要求返回一個低電平脈沖,如果不存在的話,也就不會返回脈沖,即總線保持為高電平,所以習慣上稱之為檢測存在脈沖。獲取存在脈沖不僅僅是檢測是否存在DS18B20,還要通過這個脈沖過程通知DS18B20準備好,單片機要對它進行操作了,如圖13-4所示。

13-4  檢測存在脈沖
注意時序圖,實粗線是單片機I/O口拉低這個引腳,虛粗線是DS18B20拉低這個引腳,細線是單片機和DS18B20釋放總線后,依靠上拉電阻的作用把I/O口引腳拉高。前邊介紹過51單片機釋放總線需要給高電平。
存在脈沖檢測過程,首先單片機要拉低這個引腳,持續(xù)大概480us960us之間的時間,程序中持續(xù)了大概500us。然后,單片機釋放總線,就是給高電平,DS18B20等待大概1560us后,會主動拉低這個引腳大概是60240us,而后DS18B20會主動釋放總線,這樣I/O口會被上拉電阻自動拉高。
由于DS18B20時序要求非常嚴格,所以在操作時序的時候,為了防止中斷干擾總線時序,先關(guān)閉總中斷。第一步,拉低DS18B20這個引腳,持續(xù)500us;第二步,延時60us;第三步,讀取存在脈沖,并且等待存在脈沖結(jié)束。
bit Get18B20Ack()
{
    bit ack;
   
    EA = 0;   //禁止總中斷
    IO_18B20 = 0;      //產(chǎn)生500us復位脈沖
    DelayX10us(50);
    IO_18B20 = 1;
    DelayX10us(6);     //延時60us
    ack = IO_18B20;    //讀取存在脈沖
    while(!IO_18B20); //等待存在脈沖結(jié)束
    EA = 1;   //重新使能總中斷
   
    return ack;
}
時序圖上明明是DS18B20等待15us60us,為什么要延時60us呢?舉個例子,媽媽在做飯,告訴你大概5分鐘到10分鐘飯就可以吃了,那么什么時候去吃,能夠絕對保證吃上飯呢?很明顯,10分鐘以后去吃肯定可以吃上飯。同樣的道理,DS18B20等待大概是15us60us,要保證讀到這個存在脈沖,那么60us以后去讀肯定可以讀到。當然,不能延時太久,太久,超過75us,就可能讀不到,為什么是75us請自己思考一下。
2ROM操作指令。學I2C總線的時候就了解到,總線上可以掛多個器件,通過不同的器件地址來訪問不同的器件。同樣,1-Wire總線也可以掛多個器件,但是它只有一條線,如何區(qū)分不同的器件呢?
在每個DS18B20內(nèi)部都有一個唯一的64位長的序列號,這個序列號值就存在DS18B20內(nèi)部的ROM中。開始的8位是產(chǎn)品類型編碼(DS18B200x10),接著的48位是每個器件唯一的序號,如同人的身份證號,最后的8位是CRC校驗碼。DS18B20可以引出去很長的線,最長可以到幾十米,測不同位置的溫度。單片機可以通過和DS18B20之間的通信,獲取每個傳感器所采集到的溫度信息,也可以同時給所有的DS18B20發(fā)送一些指令。這些指令相對來說比較復雜,而且應用較少,這里不再贅述。
Skip ROM(跳過ROM):0xCC。當總線上只有一個器件的時候,可以跳過ROM,不進行ROM檢測。
3RAM存儲器操作指令。
RAM讀取指令,只講2條,其它的有需要可以查手冊。
Read Scratchpad(讀暫存寄存器):0xBE
這里要注意的是,DS18B20的溫度數(shù)據(jù)是2個字節(jié),讀取數(shù)據(jù)的時候,先讀取到的是低字節(jié)的低位,讀完了第一個字節(jié)后,再讀高字節(jié)的低位,直到兩個字節(jié)全部讀取完畢。
Convert Temperature(啟動溫度轉(zhuǎn)換):0x44
當發(fā)送啟動溫度轉(zhuǎn)換的指令后,DS18B20開始轉(zhuǎn)換。從轉(zhuǎn)換開始到獲取溫度,DS18B20是需要時間的,而這個時間長短取決于DS18B20的精度。前邊說DS18B20最高可以用12位存儲溫度,但是也可以用11位,10位和9位共四種格式。位數(shù)越高,精度越高,9位模式最低位變化1個數(shù)字溫度變化0.5度,同時轉(zhuǎn)換速度也要快一些,如圖13-5所示。

13-5  DS18B20溫度轉(zhuǎn)換時間
其中寄存器R1R0決定了轉(zhuǎn)換的位數(shù),出廠默認值就11,也就是12位表示溫度,最大的轉(zhuǎn)換時間是750ms。當啟動轉(zhuǎn)換后,至少要再等750ms之后才能讀取溫度,否則讀到的溫度有可能是錯誤的值。
4DS18B20的位讀寫時序比較復雜,結(jié)合圖文理解清楚。寫時序圖如圖13-6所示。

13-6 DS18B20位寫入時序
當要給DS18B20寫入0的時候,單片機將引腳拉低,持續(xù)時間大于60us小于120us就可以了。圖13-6顯示的意思是,單片機先拉低15us之后,DS18B20會在從15us60us之間的時間來讀取這一位,DS18B20最早會在15us的時刻讀取,典型值是在30us的時刻讀取,最多不會超過60usDS18B20必然讀取完畢,所以持續(xù)時間超過60us但不超過120us
當要給DS18B20寫入1的時候,單片機先將這個引腳拉低,拉低時間大于1us,然后釋放總線,即拉高引腳,并且持續(xù)時間也要大于60us。和寫0類似的是,DS18B20會在15us60us之間來讀取這個1
可以看出來,DS18B20的時序比較嚴格,寫的過程中最好不要有中斷打斷,但是在兩個“位”之間的間隔,是大于1us小于無窮的,那在這個時間段,是可以開中斷來處理其它程序的。發(fā)送即寫入一個字節(jié)的數(shù)據(jù)程序如下。
void Write18B20(unsigned char dat)
{
    unsigned char mask;
   
    EA = 0;   //禁止總中斷
    for (mask=0x01; mask!=0; mask<<=1)  //低位在先,依次移出8bit
    {
        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;   //重新使能總中斷
}
讀時序圖如圖13-7所示。

13-7  DS18B20位讀取時序
當要讀取DS18B20的數(shù)據(jù)的時候,單片機首先要拉低這個引腳,并且至少保持1us的時間,然后釋放引腳,釋放完畢后要盡快讀取。從拉低這個引腳到讀取引腳狀態(tài),不能超過15us。大家從圖13-7可以看出來,主機采樣時間,也就是MASTER SAMPLES,是在15us之內(nèi)必須完成的,讀取一個字節(jié)數(shù)據(jù)的程序如下。
unsigned char Read18B20()
{
    unsigned char dat;
    unsigned char mask;
   
    EA = 0;   //禁止總中斷
    for (mask=0x01; mask!=0; mask<<=1)  //低位在先,依次采集8bit
    {
        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;   //重新使能總中斷
    return dat;
}
DS18B20所表示的溫度值中,有小數(shù)和整數(shù)兩部分。常用的帶小數(shù)的數(shù)據(jù)處理方法有兩種,一種是定義成浮點型直接處理,第二種是定義成整型,然后把小數(shù)和整數(shù)部分分離出來,在合適的位置點上小數(shù)點即可Kingst51程序中使用的是第二種方法。下面就寫一個程序,將讀到的溫度值通過數(shù)碼管顯示出來,并且保留一位小數(shù)位。
/***************************DS18B20.c文件程序源代碼****************************/
#include <reg52.h>
#include <intrins.h>
sbit IO_18B20 = P3^2;  //DS18B20通信引腳
/* 軟件延時函數(shù),延時時間(t*10)us */
void DelayX10us(unsigned char t)
{
    do {
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
    } while (--t);
}
/* 復位總線,獲取存在脈沖,以啟動一次讀寫操作 */
bit Get18B20Ack()
{
    bit ack;
   
    EA = 0;   //禁止總中斷
    IO_18B20 = 0;     //產(chǎn)生500us復位脈沖
    DelayX10us(50);
    IO_18B20 = 1;
    DelayX10us(6);    //延時60us
    ack = IO_18B20;   //讀取存在脈沖
    while(!IO_18B20); //等待存在脈沖結(jié)束
    EA = 1;   //重新使能總中斷
   
    return ack;
}
/* DS18B20寫入一個字節(jié),dat-待寫入字節(jié) */
void Write18B20(unsigned char dat)
{
    unsigned char mask;
   
    EA = 0;   //禁止總中斷
    for (mask=0x01; mask!=0; mask<<=1)  //低位在先,依次移出8bit
    {
        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;   //重新使能總中斷
}
/* DS18B20讀取一個字節(jié),返回值-讀到的字節(jié) */
unsigned char Read18B20()
{
    unsigned char dat;
    unsigned char mask;
   
    EA = 0;   //禁止總中斷
    for (mask=0x01; mask!=0; mask<<=1)  //低位在先,依次采集8bit
    {
        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;   //重新使能總中斷
    return dat;
}
/* 啟動一次18B20溫度轉(zhuǎn)換,返回值-表示是否啟動成功 */
bit Start18B20()
{
    bit ack;
   
    ack = Get18B20Ack();   //執(zhí)行總線復位,并獲取18B20應答
    if (ack == 0)          //18B20正確應答,則啟動一次轉(zhuǎn)換
    {
        Write18B20(0xCC);  //跳過ROM操作
        Write18B20(0x44);  //啟動一次溫度轉(zhuǎn)換
    }
    return ~ack;   //ack==0表示操作成功,所以返回值對其取反
}
/* 讀取DS18B20轉(zhuǎn)換的溫度值,返回值-表示是否讀取成功 */
bit Get18B20Temp(int *temp)
{
    bit ack;
    unsigned char LSB, MSB; //16bit溫度值的低字節(jié)和高字節(jié)
   
    ack = Get18B20Ack();    //執(zhí)行總線復位,并獲取18B20應答
    if (ack == 0)           //18B20正確應答,則讀取溫度值
    {
        Write18B20(0xCC);   //跳過ROM操作
        Write18B20(0xBE);   //發(fā)送讀命令
        LSB = Read18B20();  //讀溫度值的低字節(jié)
        MSB = Read18B20();  //讀溫度值的高字節(jié)
        *temp = ((int)MSB << 8) + LSB;  //合成為16bit整型數(shù)
    }
    return ~ack;  //ack==0表示操作應答,所以返回值為其取反值
}
/*****************************main.c文件程序源代碼******************************/
#include <reg52.h>
bit flag1s = 0;          //1s定時標志
unsigned char T0RH = 0;  //T0重載值的高字節(jié)
unsigned char T0RL = 0;  //T0重載值的低字節(jié)
void ConfigTimer0(unsigned int ms);
extern bit Start18B20();
extern bit Get18B20Temp(int *temp);
void InitLed();
void LedScan();
void LedNumber(unsigned char index, unsigned char num, unsigned char point);
void main()
{
    bit res;
    int temp;        //讀取到的當前溫度值
    int intT, decT;  //溫度值的整數(shù)和小數(shù)部分
    EA = 1;           //開總中斷
    InitLed();        //初始化數(shù)碼管IO
    Start18B20();     //啟動DS18B20
    ConfigTimer0(1);  //T0定時1ms
   
    while (1)
    {
        if (flag1s)  //每秒更新一次溫度
        {
            flag1s = 0;
            res = Get18B20Temp(&temp);  //讀取當前溫度
            if (res)                    //讀取成功時,刷新當前溫度顯示
            {
                intT = temp >> 4;       //分離出溫度值整數(shù)部分
                decT = temp & 0xF;      //分離出溫度值小數(shù)部分
                decT = (decT*10) / 16;  //二進制的小數(shù)部分轉(zhuǎn)換為1位十進制位
                LedNumber(0, decT, 0);        //顯示小數(shù)位
                LedNumber(1, intT%10, 1);     //顯示整數(shù)個位+小數(shù)點
                LedNumber(2, intT/10%10, 0);  //顯示整數(shù)十位
            }
            Start18B20();   //重新啟動下一次轉(zhuǎn)換
        }
    }
}
/* 配置并啟動T0ms-T0定時時間 */
void ConfigTimer0(unsigned int ms)
{
    unsigned long tmp;  //臨時變量
   
    tmp = 11059200 / 12;      //定時器計數(shù)頻率
    tmp = (tmp * ms) / 1000;  //計算所需的計數(shù)值
    tmp = 65536 - tmp;        //計算定時器重載值
    tmp = tmp + 33;           //補償中斷響應延時造成的誤差
    T0RH = (unsigned char)(tmp>>8);  //定時器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0為模式1
    TH0 = T0RH;     //加載T0重載值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中斷
    TR0 = 1;        //啟動T0
}
/* T0中斷服務函數(shù),完成1秒定時 */
void InterruptTimer0() interrupt 1
{
    static unsigned int tmr1s = 0;
   
    TH0 = T0RH;  //重新加載重載值
    TL0 = T0RL;
    LedScan();
    tmr1s++;
    if (tmr1s >= 1000)  //定時1s
    {
        tmr1s = 0;
        flag1s = 1;
    }
}
/*****************************Led.c文件程序源代碼*******************************/
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = {  //數(shù)碼管顯示字符轉(zhuǎn)換表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = {  //數(shù)碼管顯示緩沖區(qū),初值0xFF確保啟動時都不亮
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
/* LED初始化函數(shù) */
void InitLed()
{
    P0 = 0xFF;
    ENLED = 0;
    ADDR3 = 1;
    ADDR2 = 1;
    ADDR1 = 1;
    ADDR0 = 1;
}
/* LED動態(tài)掃描函數(shù),在定時中斷中調(diào)用 */
void LedScan()
{
    static unsigned char i = 0;   //動態(tài)掃描的索引,定義為局部靜態(tài)變量
   
    P0 = 0xFF;   //顯示消隱
    switch (i)
    {
        case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
        case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
        case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
        case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
        case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
        case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
        default: break;
    }
}
/* 數(shù)碼管上顯示一位數(shù)字,index-數(shù)碼管位索引(從右到左對應05)
   num-待顯示的數(shù)字,point-代表是否顯示該位上的小數(shù)點 */
void LedNumber(unsigned char index, unsigned char num, unsigned char point)
{
    LedBuff[index] = LedChar[num];  //輸入數(shù)字轉(zhuǎn)換為數(shù)碼管字符0F
    if (point != 0)
    {
        LedBuff[index] &= 0x7F;  //point不為0時點亮當前位的小數(shù)點
    }
}

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏2 分享淘帖 頂 踩
回復

使用道具 舉報

沙發(fā)
ID:105115 發(fā)表于 2026-4-30 15:16 | 只看該作者
感謝老師分享,學習了
回復

使用道具 舉報

板凳
ID:255559 發(fā)表于 2026-5-5 10:06 | 只看該作者
講的很詳細,感謝你的分享,謝謝
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機教程網(wǎng)

快速回復 返回頂部 返回列表