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

標題: 第13章 DS18B20溫度傳感器和Flash存儲器13.1 [打印本頁]

作者: 卓然塵世間    時間: 2026-4-30 13:12
標題: 第13章 DS18B20溫度傳感器和Flash存儲器13.1
DS18B20是一種常用的溫度傳感器,提供了感知周圍溫度的手段。Flash存儲器是一款常用的數據存儲器件,相比較于EEPROMFLASH的存儲容量更大、單位成本更低。
本章除了學習這兩個器件外,還要學習控制這兩種器件的兩種通信協議--1-wire總線協議(一般常用)SPI總線協議(重要且常用)。
13.1 溫度傳感器DS18B20
DS18B20是美信公司的一款溫度傳感器,單片機可以通過1-Wire協議與DS18B20進行通信,最終將溫度讀出。1-Wire總線的硬件接口很簡單,只需要DS18B20的數據引腳和單片機的一個I/O口接上就可以了。先來看一下DS18B20的硬件原理圖,如圖13-1所示。

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

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

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

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

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

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


作者: hqboy    時間: 2026-4-30 15:16
感謝老師分享,學習了

作者: hurbocn    時間: 2026-5-5 10:06
講的很詳細,感謝你的分享,謝謝




歡迎光臨 (http://www.denmoz.com/bbs/) Powered by Discuz! X3.1