找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 30758|回復(fù): 19
收起左側(cè)

第16章 紅外通信和DS18B20溫度傳感器的學(xué)習(xí)

  [復(fù)制鏈接]
ID:1 發(fā)表于 2013-10-31 23:23 | 顯示全部樓層 |閱讀模式

  本教材現(xiàn)以連載的方式由網(wǎng)絡(luò)發(fā)布,并將于2014年由清華大學(xué)出版社出版最終完整版,版權(quán)歸作者和清華大學(xué)出版社所有。本著開源、分享的理念,本教材可以自由傳播及學(xué)習(xí)使用,但是務(wù)必請注明出處來自金沙灘工作室

16.1
紅外光的基本原理

紅外線是波長介于微波和可見光之間的電磁波,波長在760納米到1毫米之間,是波形比紅光長的非可見光。自然界中的一切物體,只要它的溫度高于絕對零度(-273)就存在分子和原子的無規(guī)則運(yùn)動,其表面就會不停的輻射紅外線。當(dāng)然了,雖然是都輻射紅外線,但是不同的物體輻射的紅外強(qiáng)度是不一樣的,而我們正是利用了這一點把紅外技術(shù)應(yīng)用到我們實際開發(fā)中。

紅外發(fā)射管很常用,在我們的遙控器上都可以看到,他類似發(fā)光二極管,但是他發(fā)射出來的是紅外光,是我們?nèi)庋鬯床坏降�。第二課我們學(xué)過發(fā)光二極管會隨著電流的增大亮度逐漸增加,同樣的道理,紅外發(fā)射管會隨著電流的增大,紅外線的強(qiáng)度越來越強(qiáng),常見的紅外發(fā)射管如圖16-1所示。

1.JPG

圖16-1 紅外發(fā)射管

紅外接收管內(nèi)部帶了一個具有紅外光敏感特征的PN節(jié),屬于光敏二極管,但是它只對紅外光有反應(yīng)。無紅外光時,光敏管不導(dǎo)通,有紅外光時,光敏管導(dǎo)通形成光電流,并且在一定范圍內(nèi)電流隨著紅外光的強(qiáng)度的增強(qiáng)而增大。典型的紅外接收管如圖16-2所示。

2.JPG

圖16-2 紅外接收管

這種紅外發(fā)射和接收對管在小車、機(jī)器人避障以及紅外循跡小車中有所應(yīng)用,這部分內(nèi)容在我們的KST-51開發(fā)板上并沒有實現(xiàn),但是屬于紅外部分的內(nèi)容,所以我提供一個原理圖給大家作為學(xué)習(xí)之用,如圖16-3所示。

3.JPG

16-3 紅外避障、循跡原理圖

在圖16-3這個原理圖中,發(fā)射控制和接收檢測都是接到我們單片機(jī)的IO口上。

發(fā)射部分:當(dāng)發(fā)射控制輸出高電平時,三極管Q1不導(dǎo)通,紅外發(fā)射管L1不會發(fā)射紅外信號;當(dāng)發(fā)射控制輸出低電平的時候,通過三極管Q1導(dǎo)通讓L1發(fā)出紅外光。

接收部分:R4是一個電位器,也就是“傳說”中的滑動變阻器。我們通過調(diào)整這個滑動變阻器給LM3932腳一個閾值電壓,這個電壓值大小可以根據(jù)實際情況來確定。而紅外光敏二極管L2收到紅外光的時候,會產(chǎn)生電流,并且隨著紅外光的從弱變強(qiáng),電流會從小變大。當(dāng)沒有紅外光或者說紅外光很弱的時候,3腳的電壓就會接近VCC,如果3腳比2腳的電壓高的話,通過LM393比較器后,接收檢測引腳輸出一個高電平。當(dāng)隨著光強(qiáng)變大,電流變大,3腳的電壓值等于VCC-I*R3,電壓就會越來越小,當(dāng)小到一定程度,比2腳的電壓還小的時候,接收檢測引腳就會變?yōu)榈碗娖健?/font>

這個電路用于避障的時候,發(fā)射管先發(fā)送紅外信號,紅外信號會隨著傳送距離的加大逐漸衰減,如果遇到障礙物,就會形成紅外反射。當(dāng)反射回來的信號比較弱時,光敏二極管L2接收的紅外光較弱,比較器LM3933腳電壓高于2腳電壓,接收檢測引腳輸出高電平,說明障礙物比較遠(yuǎn);當(dāng)反射回來的信號比較強(qiáng),接收檢測引腳輸出低電平,說明障礙物比較近了。

用于小車循跡的時候,必須要有黑色和白色的軌道。當(dāng)紅外信號發(fā)送到黑色軌道時,黑色因為吸光能力比較強(qiáng),紅外信號發(fā)送出去后就會被吸收掉,反射部分很微弱。白色軌道就會把大部分紅外信號返回來。通常情況下的循跡小車,需要應(yīng)用多個紅外模塊同時檢測,從多個角度判斷軌道,根據(jù)判斷的結(jié)果來調(diào)整小車使其按照正常循跡前行。

16.2 紅外遙控通信原理

在實際的通信領(lǐng)域,發(fā)出來的信號一般有較寬的頻譜,而且都是在比較低的頻率段分布大量的能量,所以稱之為基帶信號,這種信號是不適合直接在信道中傳輸?shù)摹楸阌趥鬏�、提高抗干擾能力和有效的利用帶寬,通常需要將信號調(diào)制到適合信道和噪聲特性的頻率范圍內(nèi)進(jìn)行傳輸,這就叫做信號調(diào)制。在通信系統(tǒng)的接收端要對接收到的信號進(jìn)行解調(diào),恢復(fù)出原來的基帶信號。這部分通信原理的內(nèi)容,大家了解一下即可。如需了解更多可上 51hei.com搜索“紅外”會有很多詳細(xì)的資料.

我們平時用到的紅外遙控器里的紅外通信,通常是使用38K左右的載波進(jìn)行調(diào)制的,下面我把原理大概給大家介紹一下,了解一下,先看發(fā)送部分原理。

調(diào)制:就是用待傳送信號去控制某個高頻信號的幅度、相位、頻率等參量變化的過程,即用一個信號去裝載另一個信號。比如我們的紅外遙控信號要發(fā)送的時候,先經(jīng)過38K調(diào)制,如圖16-4所示。

4.JPG

16-4  紅外信號調(diào)制  

    原始信號就是我們要發(fā)送的一個數(shù)據(jù)“0”位或者一位數(shù)據(jù)“1”位,而所謂38K載波就是頻率為38K的方波信號,調(diào)制后信號就是最終我們發(fā)射出去的波形。我們使用原始信號來控制38K載波,當(dāng)信號是數(shù)據(jù)“0”的時候,38K載波毫無保留的全部發(fā)送出去,當(dāng)信號是數(shù)據(jù)“1”的時候,不發(fā)送任何載波信號。

那在原理上,我們?nèi)绾螐碾娐返慕嵌热崿F(xiàn)這個功能呢?如圖16-5所示。

5.JPG

16-5 紅外發(fā)射原理圖

38K載波,我們可以用455K晶振,經(jīng)過12分頻得到37.91K,也可以由時基電路NE555來產(chǎn)生,或者使用單片機(jī)的PWM來產(chǎn)生。當(dāng)信號輸出引腳輸出高電平時,Q2截止,不管38K載波信號如何控制Q1,右側(cè)的豎向支路都不會導(dǎo)通,紅外管L1不會發(fā)送任何信息。當(dāng)信號輸出是低電平的時候,那么38K載波就會通過Q1釋放出來,在L1上產(chǎn)生38K的載波信號。這里要說明的是,大多數(shù)家電遙控器的38K的占空比是1/3,也有1/2的,但是相對少一些。

正常的通信來講,接收端要首先對信號通過監(jiān)測、放大、濾波、解調(diào)等等一系列電路處理,然后輸出基帶信號。但是紅外通信的一體化接收頭HS0038B,已經(jīng)把這些電路全部集成到一起了,我們只需要把這個電路接上去,就可以直接輸出我們所要的基帶信號了,如圖16-6所示。

6.JPG

圖16-6 紅外接收原理圖

由于紅外接收頭內(nèi)部放大器的增益很大,很容易引起干擾,因此在接收頭供電引腳上必須加上濾波電容,官方手冊給的值是4.7uF,我們這里直接用的10uF,手冊里還要求在供電引腳和電源之間串聯(lián)100歐的電阻,進(jìn)一步降低干擾。

16-6所示的電路,用來接收圖16-5電路發(fā)送出來的波形,當(dāng)HS0038監(jiān)測到有38K的紅外信號時,就會在OUT引腳輸出低電平,當(dāng)沒有38K的時候,OUT引腳就會輸出高電平。那我們把OUT引腳接到單片機(jī)的IO口上,通過編程,就可以獲取紅外通信發(fā)過來的數(shù)據(jù)了。

大家想想,OUT引腳輸出的數(shù)據(jù)是不是又恢復(fù)成為基帶信號數(shù)據(jù)了呢?那我們單片機(jī)在接收這個基帶信號數(shù)據(jù)的時候,如何判斷接收到的是什么數(shù)據(jù),應(yīng)該遵循什么協(xié)議呢?像我們前邊學(xué)到的UARTI2C、SPI等通信協(xié)議都是基帶通信的通信協(xié)議,而紅外的38K僅僅是對基帶信號進(jìn)行調(diào)制解調(diào),讓信號更適合在信號中傳輸。

由于我們的紅外調(diào)制信號是半雙工的,而且同時空間只能允許一個信號源,所以我們紅外的基帶信號不適合在I2C或者SPI通信協(xié)議中進(jìn)行的,我們前邊提到過UART雖然是2條線,但是通信的時候,實際上一條線即可,所以紅外可以在UART中進(jìn)行通信。當(dāng)然,這個通信也不是沒有限制的,比如在HS0038B的數(shù)據(jù)手冊中標(biāo)明,要想讓HS0038B識別到38K的紅外信號,那么這個38K的載波必須要大于10個周期,這就限定了我們紅外通信的基帶信號的比特率必須不能高于3800,那如果把串口輸出的信號直接用38K調(diào)制的話,波特率也就不能高于3800。當(dāng)然還有很多其他基帶協(xié)議可以利用紅外來調(diào)制,下面我們介紹一種遙控器常用的紅外通信協(xié)議——NEC協(xié)議。

16.3 NEC協(xié)議紅外遙控器

家電遙控器通信距離往往要求不高,而紅外的成本比其他無線設(shè)備要低的多,所以家電遙控器應(yīng)用中紅外始終占據(jù)著一席之地。遙控器的基帶通信協(xié)議很多,大概有幾十種,常用的就有ITT協(xié)議、NEC協(xié)議、Sharp協(xié)議、Philips RC-5協(xié)議、Sony SIRC協(xié)議等。用的最多的就是NEC協(xié)議了,因此我們KST-51開發(fā)板隨板的遙控器直接采用NEC協(xié)議,我們這節(jié)課也以NEC協(xié)議標(biāo)準(zhǔn)來講解一下。

NEC協(xié)議的數(shù)據(jù)格式包括了引導(dǎo)碼、用戶碼、用戶碼(或者用戶碼反碼)、按鍵鍵碼和鍵碼反碼,最后一個停止位,停止位主要起隔離作用,一般不進(jìn)行判斷,編程時我們也不予理會。其中數(shù)據(jù)編碼總共是4個字節(jié)32位,如圖16-7所示。第一個字節(jié)是用戶碼,第二個字節(jié)可能也是用戶碼,或者是用戶碼的反碼,具體由生產(chǎn)商決定,第三個字節(jié)就是當(dāng)前按鍵的鍵數(shù)據(jù)碼,而第四個字節(jié)是鍵數(shù)據(jù)碼的反碼,可用于對數(shù)據(jù)的糾錯。

                                                                                                             7.JPG

圖16-7 NEC協(xié)議數(shù)據(jù)格式

這個NEC協(xié)議,表示數(shù)據(jù)的方式不像我們之前學(xué)過的比如uart那樣直觀,而是每一位數(shù)據(jù)本身也需要進(jìn)行編碼,編碼后再進(jìn)行載波調(diào)制。

引導(dǎo)碼:9ms的載波+4.5ms的空閑。

比特值“0”:560us的載波+560us的空閑。

比特值“1”:560us的載波+1.68ms的空閑。

結(jié)合圖16-7我們就能看明白了,最前面黑乎乎的一段,是引導(dǎo)碼的9ms載波,緊接著是引導(dǎo)碼的4.5ms的空閑,而后邊的數(shù)據(jù)碼,是眾多載波和空閑交叉,它們的長短就由其要傳遞的具體數(shù)據(jù)來決定。我們的HS0038B這個紅外一體化接收頭,當(dāng)收到有載波的信號的時候,會輸出一個低電平,空閑的時候會輸出高電平,我們用邏輯分析儀抓出來一個紅外按鍵通過HS0038解碼后的圖形來了解一下,如圖16-8所示。

8.JPG

16-8 紅外遙控器按鍵編碼

從圖上可以看出,先是9ms載波加4.5ms空閑的起始碼,數(shù)據(jù)碼是低位在前,高位在后,數(shù)據(jù)碼第一個字節(jié)是8560us的載波加560us的空閑,也就是0x00,第二個字節(jié)是8560us的載波加1.68ms的空閑,可以看出來是0xFF,這兩個字節(jié)就是用戶碼和用戶碼的反碼。按鍵的鍵碼二進(jìn)制是0x0B,反碼就是0xF3,最后跟了一個560us載波停止位。對于我們的遙控器來說,不同的按鍵,就是鍵碼和鍵碼反碼的區(qū)分,用戶碼是一樣的。這樣我們就可以通過單片機(jī)的程序,把當(dāng)前的按鍵的鍵碼給解出來。

我們前邊學(xué)習(xí)中斷的時候,學(xué)到51單片機(jī)有外部中斷0和外部中斷1這兩個外部中斷。我們的紅外接收引腳接到了P3.3引腳上,這個引腳的第二功能就是外部中斷1。在寄存器TCON中的bit3bit2這兩位,是和外部中斷1相關(guān)的兩位。其中IE1是外部中斷標(biāo)志位,當(dāng)外部中斷發(fā)生后,這一位被自動置1,和定時器中斷標(biāo)志位TF相似,進(jìn)入中斷后會自動清零,也可以軟件清零。bit2位是設(shè)置外部中斷類型的,如果bit2位為0,那么只要P3.3為低電平就可以觸發(fā)中斷,如果bit2位為1,那么P3.3從高電平到低電平的下降沿發(fā)生才可以觸發(fā)中斷。此外,外部中斷1使能位是EX1。那下面我們就把程序?qū)懗鰜�,使用�?shù)碼管把遙控器的用戶碼和鍵碼顯示出來。

Infrared.c文件主要是用來檢測紅外通信的,當(dāng)發(fā)生外部中斷后,進(jìn)入外部中斷,通過定時器1定時,首先對引導(dǎo)碼判斷,而后對數(shù)據(jù)碼的每個位逐位獲取高低電平的時間,從而得知每一位是0還是1,最終把數(shù)據(jù)碼解出來。

/***********************infrared.c文件程序源代碼*************************/

#include <reg52.h>

sbit IR_INPUT = P3^3;  //紅外接收引腳

bit irflag = 0;  //紅外接收標(biāo)志,收到一幀正確數(shù)據(jù)后置1

unsigned char ircode[4];  //紅外代碼接收緩沖區(qū)

void InitInfrared(void)  //紅外功能的初始化函數(shù)

{

    TMOD &= 0x0F;  //清零T1的控制位

    TMOD |= 0x10;  //配置T1為模式1

    TR1 = 0;       //停止T1計數(shù)

    ET1 = 0;       //禁止T1中斷

    IT1 = 1;       //設(shè)置INT1為負(fù)邊沿觸發(fā)

    EX1 = 1;       //使能INT1中斷

}

unsigned int GetHighTime(void)  //獲取高電平時間

{

    TH1 = 0;  //清零T1計數(shù)初值

    TL1 = 0;

    TR1 = 1;  //啟動T1計數(shù)

    while (IR_INPUT)  //紅外輸入引腳為1時循環(huán)檢測等待,變?yōu)?/font>0時則結(jié)束本循環(huán)

    {

        if (TH1 >= 0x40)

        {           //當(dāng)T1計數(shù)值大于0x4000,即高電平持續(xù)時間超過約18ms時,

            break;  //強(qiáng)制退出循環(huán),是為了避免信號異常時,程序假死在這里。

        }

    }

    TR1 = 0;  //停止T1計數(shù)

    return (TH1*256 + TL1);  //返回T1的計數(shù)值

}

unsigned int GetLowTime(void)  //獲取低電平時間

{

    TH1 = 0;  //清零T1計數(shù)初值

    TL1 = 0;

    TR1 = 1;  //啟動T1計數(shù)

    while (!IR_INPUT)  //紅外輸入引腳為0時循環(huán)檢測等待,變?yōu)?/font>1時則結(jié)束本循環(huán)

    {

        if (TH1 >= 0x40)

        {           //當(dāng)T1計數(shù)值大于0x4000,即低電平持續(xù)時間超過約18ms時,

            break;  //強(qiáng)制退出循環(huán),是為了避免信號異常時,程序假死在這里。

        }

    }

    TR1 = 0;  //停止T1計數(shù)

    return (TH1*256 + TL1);  //返回T1的計數(shù)值

}

void EXINT1_ISR() interrupt 2  //INT1中斷服務(wù)函數(shù),執(zhí)行紅外接收及解碼

{

    unsigned char i, j;

    unsigned char byt;

    unsigned int time;

   

    //接收并判定引導(dǎo)碼的9ms低電平

    time = GetLowTime();

    if ((time<7833) || (time>8755))  //時間判定范圍為8.59.5ms,

    {                                //超過此范圍則說明為誤碼,直接退出

        IE1 = 0;   //退出前清零INT1中斷標(biāo)志

        return;

    }

    //接收并判定引導(dǎo)碼的4.5ms高電平

    time = GetHighTime();

    if ((time<3686) || (time>4608))  //時間判定范圍為4.05.0ms,

    {                                //超過此范圍則說明為誤碼,直接退出

        IE1 = 0;

        return;

    }

    //接收并判定后續(xù)的4字節(jié)數(shù)據(jù)

    for (i=0; i<4; i++)  //循環(huán)接收4個字節(jié)

    {

        for (j=0; j<8; j++)  //循環(huán)接收判定每字節(jié)的8bit

        {

            //接收判定每bit560us低電平

            time = GetLowTime();

            if ((time<313) || (time>718))  //時間判定范圍為340780us,

            {                              //超過此范圍則說明為誤碼,直接退出

                IE1 = 0;

                return;

            }

            //接收每bit高電平時間,判定該bit的值

            time = GetHighTime();

            if ((time>313) && (time<718))  //時間判定范圍為340780us,

            {                              //在此范圍內(nèi)說明該bit值為0

                byt >>= 1;   //因低位在先,所以數(shù)據(jù)左移,高位為0

            }

            else if ((time>1345) && (time<1751))  //時間判定范圍為14601900us

            {                                     //在此范圍內(nèi)說明該bit值為1

                byt >>= 1;   //因低位在先,所以數(shù)據(jù)左移,

                byt |= 0x80; //高位置1

            }

            else  //不在上述范圍內(nèi)則說明為誤碼,直接退出

            {

                IE1 = 0;

                return;

            }

        }

        ircode[ i] = byt;  //接收完一個字節(jié)后保存到緩沖區(qū)

    }

    irflag = 1;  //接收完畢后設(shè)置標(biāo)志

    IE1 = 0;     //退出前清零INT1中斷標(biāo)志

}

大家在閱讀這個文件里的代碼時,會發(fā)現(xiàn)我們在獲取高低電平時間的時候做了超時判斷if (TH1 >= 0x40),這個超時判斷一方面是應(yīng)對空間突發(fā)的紅外干擾信號,如果我們不做超時判斷,程序有可能會一直等待下一個跳變才會停止檢測,造成程序假死。另外一個方面,遙控器的單按按鍵和持續(xù)按住按鍵發(fā)出來的信號是不同的。我們先來對比一下兩種按鍵方式的信號狀態(tài),如圖16-916-10所示。

1.JPG

圖16-9 紅外單次按鍵時序圖

2.JPG

圖16-10 紅外持續(xù)按鍵時序圖

    單次按鍵的結(jié)果16-9和我們之前的圖16-8是一樣的,這個不需要再解釋。而持續(xù)按鍵,首先會發(fā)出一個和單次按鍵一樣的波形出來,經(jīng)過大概40ms后,會產(chǎn)生一個9ms載波加2.25ms空閑,再跟一個停止位的波形,而后只要你還在按住按鍵,每經(jīng)過大概96ms就會產(chǎn)生9ms載波加2.25ms空閑加停止位這樣的重復(fù)波形。我們?nèi)藶榘聪掳存I的時候,很難控制按下的時間,因此后邊的很容易出現(xiàn)這種延續(xù)波形,我們加上超時判斷也可以有效的避免進(jìn)入延續(xù)波形的死循環(huán)中去。

/***********************main.c文件程序源代碼*************************/

#include <reg52.h>

sbit ADDR3 = P1^3;      //LED選擇地址線3

sbit ENLED = P1^4;      //LED總使能引腳

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

};

unsigned char T0RH = 0;  //T0重載值的高字節(jié)

unsigned char T0RL = 0;  //T0重載值的低字節(jié)

extern bit irflag;

extern unsigned char ircode[4];

void ConfigTimer0(unsigned int ms);

extern void InitInfrared(void);

void main ()

{

    P0 = 0xFF;       //P0口初始化

    ADDR3 = 1;       //選擇數(shù)碼管

    ENLED = 0;       //LED總使能

    InitInfrared();  //初始化紅外功能

    ConfigTimer0(1); //配置T0定時1ms

    EA = 1;          //開總中斷

    //PT0 = 1;         //配置T0中斷為高優(yōu)先級

   

    while(1)

    {

        if (irflag)  //接收到紅外數(shù)據(jù)時刷新顯示

        {

            irflag = 0;

            LedBuff[5] = LedChar[ ircode[0] >> 4];  //用戶碼顯示

            LedBuff[4] = LedChar[ ircode[0]&0x0F];

            LedBuff[1] = LedChar[ ircode[2] >> 4];  //鍵碼顯示

            LedBuff[0] = LedChar[ ircode[2]&0x0F];

        }

    }

}

void ConfigTimer0(unsigned int ms)  //T0配置函數(shù)

{

    unsigned long tmp;

   

    tmp = 11059200 / 12;      //定時器計數(shù)頻率

    tmp = (tmp * ms) / 1000;  //計算所需的計數(shù)值

    tmp = 65536 - tmp;        //計算定時器重載值

    tmp = tmp + 15;           //修正中斷響應(yīng)延時造成的誤差

   

    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

}

void InterruptTimer0() interrupt 1  //T0中斷服務(wù)函數(shù)

{

    static unsigned char iled = 0;

   

    TH0 = T0RH;  //定時器重新加載重載值

    TL0 = T0RL;

   

    //LED數(shù)碼管動態(tài)掃描

    P0 = 0xFF;                //關(guān)閉所有段選位,顯示消隱

    P1 = (P1 & 0xF8) | iled;  //位選索引值賦值到P1口低3

    P0 = LedBuff[ iled];       //相應(yīng)顯示緩沖區(qū)的值賦值到P0

    if (iled < 5)             //位選索引0-5循環(huán),因有6個數(shù)碼管

        iled++;

    else

        iled = 0;

}

main.c文件程序的主要功能就是把獲取到的紅外遙控器的用戶碼和鍵碼信息,傳送到數(shù)碼管上顯示出來,并且通過定時器01ms中斷進(jìn)行數(shù)碼管的動態(tài)刷新。不知道大家經(jīng)過試驗發(fā)現(xiàn)沒有,當(dāng)我們按下遙控器按鍵的時候,數(shù)碼管顯示的數(shù)字會閃爍,這是什么原因呢?單片機(jī)的程序都是順序執(zhí)行的,一旦我們按下遙控器按鍵,我們的程序就會進(jìn)入遙控器解碼段,而這個解碼段的時間比較長,要幾十個毫秒,而我們的數(shù)碼管動態(tài)刷新間隔超過了10ms后就會有閃爍的感覺了,因此這個閃爍主要是由于我們程序執(zhí)行紅外解碼時,延誤了數(shù)碼管動態(tài)刷新造成的。

如何解決?前邊我們講過中斷優(yōu)先級問題,如果設(shè)置了中斷優(yōu)先級,就會產(chǎn)生中斷嵌套。中斷嵌套的原理,我們在前邊講中斷的時候已經(jīng)講過一次了,大家可以回頭再復(fù)習(xí)一下。那么這個程序中,有2個中斷程序,一個是外部中斷程序,一個是定時器中斷程序。如果設(shè)置外部中斷優(yōu)先級比較高的話,由于在外部中斷中要接收紅外信號,耗時幾十毫秒,會耽誤數(shù)碼管的動態(tài)刷新。而在定時器中斷程序中,執(zhí)行時間只有幾十個us,即使進(jìn)入中斷,也不會干擾到紅外信號的正常接收,因此這個地方我們把定時器0的中斷優(yōu)先級設(shè)置為高優(yōu)先級。在主程序main函數(shù)中,大家把這句注釋掉的程序 “//PT0 = 1;”取消注釋,再編譯一下,下載到單片機(jī)里,然后再試試發(fā)送按鍵,是不是沒有任何閃爍了呢?而中斷嵌套的意義也有所體會了吧。

16.4 溫度傳感器DS18B20

DS18B20是美信公司的一款溫度傳感器,單片機(jī)可以通過1-WireDS18B20進(jìn)行通信,最終將溫度讀出。1-Wire總線的硬件接口很簡單,只需要把18B20的數(shù)據(jù)引腳和單片機(jī)的一個IO口接上就可以通信。硬件的簡單,隨之而來的,就是軟件時序的復(fù)雜。1-Wire總線的時序比較復(fù)雜,很多同學(xué)在這里獨立看時序圖都看不明白,所以這里還要帶著大家來研究18B20的時序圖。我們先來看一下DS18B20的硬件原理圖,如圖16-11所示。

3.JPG

圖16-11 DS18B20

DS18B20通過編程,可以實現(xiàn)最高12位的溫度存儲值,在寄存器中,以補(bǔ)碼的格式存儲,如圖16-12所示。

4.JPG

圖16-12 DS18B20溫度表示

一共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ù)字如同卡尺的刻度一樣分布,如圖16-13所示。

5.JPG

圖16-13 DS18B20溫度顯示

二進(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)行操作它了,如圖16-14所示。

6.JPG   

16-14 獲取存在脈沖

大家注意看圖,實粗線是我們單片機(jī)IO口拉低這個引腳,虛粗線是DS18B20拉低這個引腳,細(xì)線是單片機(jī)和DS18B20釋放總線后,依靠上拉電阻的作用把IO口引腳拉上去的。這個我們前邊提到過了,51單片機(jī)釋放總線就是給高電平即可。

存在脈沖檢測過程,首先我們單片機(jī)要拉低這個引腳,持續(xù)大概480us960us之間的時間即可,我們的程序中持續(xù)了500us。然后,單片機(jī)釋放總線,就是給高電平,DS18B20等待大概1560us后,會主動拉低這個引腳大概是60240us,而后DS18B20會主動釋放總線,這樣IO口會被上拉電阻自動拉高。

有的同學(xué)還是不能夠徹底理解,程序列出來逐句解釋。首先,由于DS18B20時序要求非常嚴(yán)格,所以在操作時序的時候,為了防止中斷干擾總線時序,先關(guān)閉總中斷。然后第一步,拉低DS18B20這個引腳,持續(xù)500us;第二步,延時60us;第三步,讀取存在脈沖,并且等待存在脈沖結(jié)束。

bit Get18B20Ack(void)  //復(fù)位總線,獲取存在脈沖,以啟動一次讀寫操作

{

    bit ack;

   

    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;   //重新使能總中斷

   

    return ack;

}

很多同學(xué)對第二步不理解,時序圖上明明是DS18B20等待15us60us,為什么要延時60us呢?舉個例子,媽媽在做飯,告訴你大概5分鐘到10分鐘飯就可以吃了,那么我們什么時候去吃,能夠絕對保證吃上飯呢?很明顯,10分鐘以后去吃肯定可以吃上飯。同樣的道理,DS18B20等待大概是15us60us,我們要保證讀到這個存在脈沖,那么60us以后去讀肯定可以讀到。當(dāng)然,不能延時太久,太久,超過75us,就可能讀不到了,為什么是75us,大家自己思考一下。

2、ROM操作指令。我們學(xué)I2C總線的時候,總線上可以掛多個器件,通過不同的器件地址來訪問不同的器件。同樣,1-Wire總線也可以掛多個器件,但是他只有一條線,如何區(qū)分不同的器件呢?

    在每個DS18B20內(nèi)部都有一個唯一的64位長的序列號,這個序列號值就存在DS18B20內(nèi)部的ROM中。開始的8位是產(chǎn)品類型編碼(DS18B2010H),接著的48位是每個器件唯一的序號,最后的8位是CRC校驗碼。DS18B20可以引出去很長的線,最長可以到幾十米,測不同位置的溫度。單片機(jī)可以通過和DS18B20之間的通信,獲取每個傳感器所采集到的溫度信息,也可以同時給所有的DS18B20發(fā)送一些指令。這些指令相對來說比較復(fù)雜,而且應(yīng)用很少,所以這里大家有興趣自己查手冊自己完成,我們這里只講一條總線上只接一個器件的指令和程序。

    Skip ROM(跳過ROM)0xCC。當(dāng)總線上只有一個器件的時候,可以跳過ROM,不進(jìn)行ROM檢測。                 

3、RAM存儲器操作指令。

RAM讀取指令,只講2條,其他的大家有需要可以隨時去查資料。

Read Scratchpad(讀暫存寄存器)0xBE

這里要注意的是,我們的DS18B20的溫度數(shù)據(jù)是2個字節(jié),我們讀取數(shù)據(jù)的時候,先讀取到的是低字節(jié)的低位,讀完了第一個字節(jié)后,再讀高字節(jié)的低位,一直到兩個字節(jié)全部讀取完畢。

Convert Temperature(啟動溫度轉(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)換速度也要快一些,如圖16-15所示。

7.JPG

圖16-15 DS18B20溫度轉(zhuǎn)換時間

其中寄存器R1R0決定了轉(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é)明白。寫時序圖如圖16-16所示。

8.JPG

16-16 DS18B20位寫入時序

當(dāng)要給DS18B20寫入‘0’的時候,單片機(jī)直接將引腳拉低,持續(xù)時間大于60us小于120us就可以了。圖上顯示的意思是,單片機(jī)先拉低15us之后,DS18B20會在從15us60us之間的時間來讀取這一位,DS18B20最早會15us的時刻讀取,典型值是30us的時刻讀取,最多不會超過60us,DS18B20必然讀取完畢,所以持續(xù)時間超過60us即可。

當(dāng)要給DS18B20寫入‘1’的時候,單片機(jī)先將這個引腳拉低,拉低時間大于1us,然后馬上釋放總線,即拉高引腳,并且持續(xù)時間也要大于60us。和寫‘0’類似的是,DS18B20會在1560us之間來讀取這個‘1’。

可以看出來,DS18B20的時序比較嚴(yán)格,寫的過程中最好不要有中斷打斷,但是在兩個“位”之間的間隔,是大于1小于無窮的,那在這個時間段,我們是可以開中斷來處理其他程序的。發(fā)送一個字節(jié)的數(shù)據(jù)程序如下。

void Write18B20(unsigned char dat)  //DS18B20寫入一個字節(jié)數(shù)據(jù)

{

    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;   //重新使能總中斷

}

讀時序圖如圖16-17所示。

9.JPG

圖16-17 DS18B20位讀取時序

當(dāng)要讀取DS18B20的數(shù)據(jù)的時候,我們的單片機(jī)首先要拉低這個引腳,并且至少保持1us的時間,然后釋放引腳,釋放完畢后要盡快讀取。從拉低這個引腳到讀取引腳狀態(tài),不能超過15us。大家從圖16-17可以看出來,主機(jī)采樣時間,也就是MASTER SAMPLES,是在15us之內(nèi)必須完成的,讀取一個字節(jié)數(shù)據(jù)的程序如下。

unsigned char Read18B20(void)  //DS18B20讀取一個字節(jié)數(shù)據(jù)

{

    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ù)和整數(shù)部分分離出來,在合適的位置點上小數(shù)點即可。我們在程序中使用的是第二種方法,下面我們就寫一個程序,將我們讀到的溫度值顯示在1602液晶上,并且保留一位小數(shù)數(shù)字。

/***********************lcd1602.c文件程序源代碼*************************/

#include <reg52.h>

#define LCD1602_DB   P0

sbit LCD1602_RS = P1^0;

sbit LCD1602_RW = P1^1;

sbit LCD1602_E  = P1^5;

void LcdWaitReady()  //等待液晶準(zhǔn)備好

{

    unsigned char sta;

   

    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為止

}

void LcdWriteCmd(unsigned char cmd)  //寫入命令函數(shù)

{

    LcdWaitReady();

    LCD1602_RS = 0;

    LCD1602_RW = 0;

    LCD1602_DB = cmd;

    LCD1602_E  = 1;

    LCD1602_E  = 0;

}

void LcdWriteDat(unsigned char dat)  //寫入數(shù)據(jù)函數(shù)

{

    LcdWaitReady();

    LCD1602_RS = 1;

    LCD1602_RW = 0;

    LCD1602_DB = dat;

    LCD1602_E  = 1;

    LCD1602_E  = 0;

}

void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str)  //顯示字符串,屏幕起始坐標(biāo)(x,y),字符串指針str

{

    unsigned char addr;

   

    //由輸入的顯示坐標(biāo)計算顯示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++;

    }

}

void LcdInit()  //液晶初始化函數(shù)

{

    LcdWriteCmd(0x38);  //16*2顯示,5*7點陣,8位數(shù)據(jù)接口

    LcdWriteCmd(0x0C);  //顯示器開,光標(biāo)關(guān)閉

    LcdWriteCmd(0x06);  //文字不動,地址自動+1

    LcdWriteCmd(0x01);  //清屏

}

/***********************DS18B20.c文件程序源代碼*************************/

#include <reg52.h>

#include <intrins.h>

sbit IO_18B20 = P3^2;  //DS18B20通信引腳

void DelayX10us(unsigned char t)  //軟件延時函數(shù),延時時間(t*10)us

{

    do {

        _nop_();

        _nop_();

        _nop_();

        _nop_();

        _nop_();

        _nop_();

        _nop_();

        _nop_();

    } while (--t);

}

bit Get18B20Ack(void)  //復(fù)位總線,獲取存在脈沖,以啟動一次讀寫操作

{

    bit ack;

   

    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;   //重新使能總中斷

   

    return ack;

}

void Write18B20(unsigned char dat)  //DS18B20寫入一個字節(jié)數(shù)據(jù)

{

    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;   //重新使能總中斷

}

unsigned char Read18B20(void)  //DS18B20讀取一個字節(jié)數(shù)據(jù)

{

    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;

}

bit Start18B20()  //啟動一次18B20溫度轉(zhuǎn)換,返回值代表是否啟動成功

{

    bit ack;

   

    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表示操作成功,所以返回值為其取反值

}

bit Get18B20Temp(int *temp)  //讀取DS18B20溫度值,返回值代表是否讀取成功

{

    bit ack;

    unsigned char LSB, 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>

bit flag1s = 0;          //1s定時標(biāo)志

unsigned char T0RH = 0;  //T0重載值的高字節(jié)

unsigned char T0RL = 0;  //T0重載值的低字節(jié)

void ConfigTimer0(unsigned int ms);

unsigned char IntToString(unsigned char *str, int dat);

extern bit Start18B20();

extern bit Get18B20Temp(int *temp);

extern void LcdInit();

extern void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str);

void main ()

{

    bit res;

    int temp;        //讀取到的當(dāng)前溫度值

    int intT, decT;  //溫度值的整數(shù)和小數(shù)部分

    unsigned char len;

    unsigned char str[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ù)點

                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)換

        }

    }

}

unsigned char IntToString(unsigned char *str, int dat)  //整型數(shù)轉(zhuǎn)換為十進(jìn)制字符串,返回值為轉(zhuǎn)換后的字符串長度

{

    signed char i;

    unsigned char len = 0;

    unsigned char buf[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é)束符

   

    return len;  //返回字符串長度

}

void ConfigTimer0(unsigned int ms)  //T0配置函數(shù)

{

    unsigned long tmp;

   

    tmp = 11059200 / 12;      //定時器計數(shù)頻率

    tmp = (tmp * ms) / 1000;  //計算所需的計數(shù)值

    tmp = 65536 - tmp;        //計算定時器重載值

    tmp = tmp + 12;           //修正中斷響應(yīng)延時造成的誤差

   

    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

}

void InterruptTimer0() interrupt 1  //T0中斷服務(wù)函數(shù)

{

    static unsigned char tmr1s = 0;

   

    TH0 = T0RH;  //定時器重新加載重載值

    TL0 = T0RL;

    tmr1s++;

    if (tmr1s >= 100)  //定時1s

    {

        tmr1s = 0;

        flag1s = 1;

    }

}

16.5 作業(yè)

1、理解紅外通信調(diào)制解調(diào)的原理,掌握NEC紅外通信編碼的原理。

2、將顯示跳線帽調(diào)到左側(cè)控制步進(jìn)電機(jī),使用紅外遙控器控制電機(jī)的正反轉(zhuǎn)。

3、掌握DS18B20的時序過程,能夠理解每一位讀寫的時序。

4、結(jié)合DS1302的可調(diào)萬年歷程序,將溫度顯示加入進(jìn)去,做一個萬年歷加溫度顯示,并且實現(xiàn)按鍵可調(diào)時間,按鍵可調(diào)溫度報警值,當(dāng)溫度超過一預(yù)定值,蜂鳴器報警。

上一章:
第15章 實時時鐘DS1302


評分

參與人數(shù) 1黑幣 +8 收起 理由
xiou + 8 贊一個!

查看全部評分

相關(guān)帖子

回復(fù)

使用道具 舉報

ID:56194 發(fā)表于 2014-2-6 11:45 | 顯示全部樓層
資料很詳細(xì),很有用。
回復(fù)

使用道具 舉報

ID:61771 發(fā)表于 2014-5-29 12:51 | 顯示全部樓層
強(qiáng)悍!!
回復(fù)

使用道具 舉報

ID:62214 發(fā)表于 2014-5-29 13:54 | 顯示全部樓層
牛~~
回復(fù)

使用道具 舉報

ID:77205 發(fā)表于 2015-4-16 22:26 | 顯示全部樓層
多謝分享^^^^^^^^^^^^^^^^^
回復(fù)

使用道具 舉報

ID:95857 發(fā)表于 2015-11-25 16:37 | 顯示全部樓層
很好,很詳細(xì)
回復(fù)

使用道具 舉報

ID:100738 發(fā)表于 2015-12-27 21:27 | 顯示全部樓層
挺厲害的,又見到了一種紅外通信的寫法~
回復(fù)

使用道具 舉報

ID:48140 發(fā)表于 2016-11-28 21:30 | 顯示全部樓層
很詳細(xì)
回復(fù)

使用道具 舉報

ID:157049 發(fā)表于 2016-12-23 08:54 | 顯示全部樓層
厲害厲害~~~~~~~~~~~~學(xué)習(xí)了
回復(fù)

使用道具 舉報

ID:149167 發(fā)表于 2017-3-19 15:08 | 顯示全部樓層
51黑有你更精彩!
回復(fù)

使用道具 舉報

ID:72047 發(fā)表于 2017-8-12 09:08 | 顯示全部樓層
授教了,很好的資料
回復(fù)

使用道具 舉報

ID:334972 發(fā)表于 2018-6-4 14:13 | 顯示全部樓層
樓主你好:圖16-8 的鍵碼和鍵碼反碼分別應(yīng)該是:0x30和0xCF吧,而不是上面寫的:0x0B和0xF3。
回復(fù)

使用道具 舉報

ID:334972 發(fā)表于 2018-6-4 14:13 | 顯示全部樓層
不錯呀
回復(fù)

使用道具 舉報

ID:93625 發(fā)表于 2018-7-20 08:20 | 顯示全部樓層
繼續(xù)學(xué)習(xí),要是有完整教程下載保存起來就好了
回復(fù)

使用道具 舉報

ID:378745 發(fā)表于 2018-7-25 12:09 | 顯示全部樓層
發(fā)射不用載波可以的不?
回復(fù)

使用道具 舉報

ID:251401 發(fā)表于 2018-10-21 17:49 | 顯示全部樓層
數(shù)據(jù)右移寫成了左移。
回復(fù)

使用道具 舉報

ID:418269 發(fā)表于 2019-2-17 21:07 | 顯示全部樓層
厲害,望其項背!
回復(fù)

使用道具 舉報

ID:708589 發(fā)表于 2020-3-16 10:49 | 顯示全部樓層
這個適用,夠清楚,而且還比較詳細(xì),可以試試
回復(fù)

使用道具 舉報

ID:1043756 發(fā)表于 2022-9-3 22:52 | 顯示全部樓層
我想問一下ds18b20液晶顯示的程序lys51開發(fā)板上的器件應(yīng)該怎么連接,可以教一下杜邦線連的位置嘛
回復(fù)

使用道具 舉報

ID:1106361 發(fā)表于 2023-12-26 21:15 | 顯示全部樓層
很詳細(xì),很有用
回復(fù)

使用道具 舉報

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

本版積分規(guī)則

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

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

快速回復(fù) 返回頂部 返回列表