1. 基于proteus的51單片機(jī)開發(fā)實(shí)例30-模塊化程序設(shè)計(jì)1.1. 實(shí)驗(yàn)?zāi)康?/strong>模塊化程序設(shè)計(jì) 不知不覺我們的51單片機(jī)開發(fā)實(shí)例已經(jīng)進(jìn)行到第三十篇了,是時(shí)候進(jìn)行一個總結(jié)和反思了,總結(jié)什么?反思什么呢?我們先從程序結(jié)構(gòu)開始吧。 總結(jié)在前面的29個例子中的程序,我們會發(fā)現(xiàn):所有的程序都在一個main.c文件中,第一個程序只有不到20行,電腦屏幕上直接能夠從頭看到尾,這個程序看起來非常直觀,所有程序語句都是一目了然的。隨著學(xué)習(xí)的深入,我們編寫的程序是越來越復(fù)雜了,程序的長度是瘋狂地增加。到了第29個實(shí)例,我們的程序量已經(jīng)接近500行了,假設(shè)我們要找最后一個函數(shù)的內(nèi)容,那就要用鼠標(biāo)撥拉好一會才能找到這個函數(shù),是不是會有“好長啊”的感慨? 更為致命的是,我們這個程序中既有延時(shí)程序、又有液晶顯示程序、還有DS18B20的讀寫程序。所有這個程序都擠在一個main.c文件中,即使我們已經(jīng)按照延時(shí)、顯示、讀寫進(jìn)行了劃分,整體感覺是不是有些亂? 現(xiàn)在我們看看DS18B20學(xué)習(xí)實(shí)驗(yàn)的第三個程序,這個程序里面有很多函數(shù),這些函數(shù)的聲明和定義在整個程序里面占了很大的比例。我們在編寫和使用這些函數(shù)的時(shí)候,需要不停的翻找相關(guān)的程序部分。顯得很麻煩。而且整個程序顯得有些亂。那么能不能把程序精簡一下,能不能把具有相關(guān)功能的函數(shù)放在一起,能不能向我們使用頭文件的時(shí)候,直接使用一個包含命令就把一些相關(guān)功能包含到程序里,而我們在程序里只要調(diào)用我們用到的函數(shù)就能夠?qū)崿F(xiàn)我們想要達(dá)到的目的呢? 答案是肯定的! 那么該怎么辦?今天我們就學(xué)習(xí)一下模塊化程序設(shè)計(jì),把程序進(jìn)行合理的規(guī)劃! 本實(shí)例的目的:了解模塊化程序設(shè)計(jì)的思路和方法。 1.2. 設(shè)計(jì)思路本實(shí)例的設(shè)計(jì)思路是:將《基于proteus的51單片機(jī)開發(fā)實(shí)例29-單總線DS18B20的讀寫》中的程序代碼按照延時(shí)功能、LCD1602液晶顯示功能、DS18B20的讀寫控制功能這三個部分,使用模塊化程序設(shè)計(jì)的方法,將這三個部分分別封裝為三個.c和.h文件,然后在main.c文件中使用宏定義語句調(diào)用。從而完成模塊化程序設(shè)計(jì)。 模塊化程序設(shè)計(jì) 1.3. 基礎(chǔ)知識現(xiàn)在我們看看《基于proteus的51單片機(jī)開發(fā)實(shí)例29-單總線DS18B20的讀寫》中的程序代碼,這個程序里面有很多函數(shù),這些函數(shù)的聲明和定義在整個程序里面占了很大的比例。我們在編寫和使用這些函數(shù)的時(shí)候,因?yàn)楹芏嗪瘮?shù)都不是獨(dú)立,需要不停的翻找相關(guān)的程序部分。顯得很麻煩。而且整個程序顯得有些亂。 那么能不能把程序精簡一下,能不能把具有相關(guān)功能的函數(shù)放在一起,能不能像我們使用頭文件的時(shí)候,直接使用一個包含命令就把一些相關(guān)功能包含到程序里,而我們在程序里只要調(diào)用我們用到的函數(shù)就能夠?qū)崿F(xiàn)我們想要達(dá)到的目的呢? 答案是肯定的! 解決方法是:化整為零,把負(fù)載的程序按照功能分解成不同的小模塊,分別實(shí)現(xiàn)。 1.3.1. 模塊化程序設(shè)計(jì)簡介模塊化程序設(shè)計(jì)是指將實(shí)現(xiàn)同一功能的程序整合起來,封裝到一個程序模塊中,這樣在使用該功能的時(shí)候,可以直接調(diào)用該模塊中的相關(guān)函數(shù)進(jìn)行操作。 在進(jìn)行程序設(shè)計(jì)時(shí)把一個大的程序按照功能劃分為若干小的程序,每個小的程序完成一個確定的功能,在這些小的程序之間建立必要的聯(lián)系,互相協(xié)作完成整個程序要完成的功能。通常我們稱這些小的程序?yàn)槌绦虻哪K。 模塊化程序設(shè)計(jì) 具體到程序來說,模塊通常是指可以用一個名字調(diào)用的一個程序段。對于不同的程序設(shè)計(jì)語言,模塊的實(shí)現(xiàn)和名稱也不相同,在BASIC,F(xiàn)ORTRAN語言中的模塊稱作子程序;C語言中的模塊叫函數(shù) 1.3.2. 2、模塊化程序設(shè)計(jì)思路模塊化程序設(shè)計(jì)的思路是這樣的:將一個大的程序按功能分割成一些小模塊。 即:把具有相同功能的函數(shù)放在一個文件中,然后在主程序里面用#include指令把這個文件包含到主程序文件中,那么在主程序中就可以直接調(diào)用這個文件中定義好的函數(shù)來實(shí)現(xiàn)特定的功能,而在主程序中不用聲明和定義這些函數(shù)。這樣就使主程序顯得更加精煉,可讀性也會增強(qiáng)。同時(shí),我們把具有相同功能的函數(shù)放在同一個文件中,這樣有一個很大的優(yōu)點(diǎn)是便于移植,可以將這個模塊化的函數(shù)文件很輕松的移植到別的程序中。 綜合上述,模塊化程序設(shè)計(jì)的優(yōu)點(diǎn)是: ●各模塊相對獨(dú)立,功能單一,結(jié)構(gòu)清晰,接口簡單. ●控制了程序設(shè)計(jì)的復(fù)雜性. ●縮短了開發(fā)周期. ●避免程序開發(fā)的重復(fù)勞動. ●易于維護(hù)和功能擴(kuò)充. 模塊化程序設(shè)計(jì) 3、模塊化程序設(shè)計(jì)的實(shí)現(xiàn) 模塊化程序的實(shí)現(xiàn)是: 一般的做法是:將不同模塊(如LCD1602,DS18B20等)都封裝成一個文件,然后在主程序中包含這些文件。 將具有相同功能的函數(shù)聲明以及該模塊涉及的端口定義、初始化設(shè)置、宏定義等內(nèi)容編寫成一個xxx.h文件文件,將函數(shù)的定義、變量的聲明及初始化等內(nèi)容編寫為一個xxx.c文件;注意最好把xxx.c文件和xxx.h文件的名稱定義成相同的名稱。然后在主程序文件中使用#include預(yù)編譯命令包含xxx.h文件,這樣在主程序中就可以調(diào)用這個文件中的函數(shù)了,同樣的,在其它.c文件中,也可以使用#include預(yù)編譯命令包含xxx.h文件。(就像我們調(diào)用編譯器中的各種應(yīng)用庫文件一樣,比如在keil c51中要調(diào)用I/O定義頭文件是我們要使用“#include <at89x51.h”這條指令將I/O頭文件包含到主程序中)。同時(shí)將“.c”文件添加到主程序所在的項(xiàng)目組中。這樣就初步完成了模塊化程序設(shè)計(jì)框架。 這樣做的優(yōu)點(diǎn)是,我們可以直接在“.h”文件中查找到我們需要的函數(shù)名稱,從而在主程序里面直接調(diào)用,而不用去關(guān)心“.c”文件中的具體內(nèi)容。如果我們要將該程序移植到不同型號的單片機(jī)上,我們同樣只需在“.h”文件中修改相應(yīng)的端口定義即可。 1.3.3. 不同模塊之間的函數(shù)調(diào)用一般情況下,程序中定義的函數(shù)和變量是有一定的作用域的,也就是說,我們在一個模塊中定義的變量和函數(shù),它的作用域只限于本模塊文件和調(diào)用它的程序文件范圍內(nèi),而在沒有調(diào)用它的模塊程序里面,如果調(diào)用它,編譯器是會提示錯誤的,它的函數(shù)是不能被使用的。 在對較為復(fù)雜的程序進(jìn)行模塊化設(shè)計(jì)的時(shí)候,經(jīng)常會遇到這樣一種情況:一個函數(shù)在不同的模塊之間都會用到,舉例來說,幾乎每一個程序中都會用到延時(shí)函數(shù),也就是說一般的程序中都需要調(diào)用延時(shí)函數(shù)。出現(xiàn)這種情況該怎么辦?難道需要在每個模塊中都定義相同的函數(shù)?但是我們都知道,程序中,不同的變量和函數(shù)只能使用不同的名稱,如果一個程序中有兩個名稱相同的變量或者函數(shù)的話,那程序編譯的時(shí)候會提示我們有重復(fù)定義的函數(shù)。難道要在不同的模塊中為相同功能的函數(shù)(功能相同,函數(shù)體的內(nèi)容也相同)起不同的名字,這樣豈不是做了很多重復(fù)勞動,這樣的重復(fù)勞動還會造成程序的可讀性變得很差。怎么辦? 模塊化程序設(shè)計(jì) 同樣的情況也會出現(xiàn)在不同模塊程序之間傳遞數(shù)據(jù)變量的時(shí)候。 在這樣的情況下,一種比較好的解決辦法是:使用文件包含命令“#include”將一個模塊的文件包含到另一個模塊文件中,這種方法在只包含很少的模塊文件的時(shí)候是很方便的,對于比較大的、很復(fù)雜的包含很多模塊文件的單片機(jī)應(yīng)用程序中,在每一個模塊里面都使用包含命令就很麻煩了,并且很容易出錯。 出現(xiàn)這種情況的原因是我們在編寫單片機(jī)程序的時(shí)候,我們所定義的函數(shù)和變量都被默認(rèn)為是局部函數(shù)和變量,那么它們的作用范圍當(dāng)然是在調(diào)用他們的程序之間了。如果我們將這些函數(shù)和變量定義為全局的函數(shù)和變量,那么,在整個單片機(jī)系統(tǒng)程序中,所有的模塊之間都可以使用這些函數(shù)和變量。 所以,針對這種情況,最好的解決方法是:將需要在不同模塊之間互相調(diào)用的文件聲明為外部函數(shù)、變量(或者全局函數(shù)、變量)。 將函數(shù)和變量聲明為全局函數(shù)和變量的方法是:在該函數(shù)和變量前面加“extern”修飾符?!癳xtern”的英文意思就是“全局”,這樣我們就可以將加了“extern”修飾符的函數(shù)和變量聲明為全局函數(shù)和變量,那么在整個單片機(jī)系統(tǒng)程序的任何地方,我們都可以隨意調(diào)用這些全局函數(shù)和變量。 例如: extern void Usart0_Init(void); //USART寄存器設(shè)置 extern void Usart0_PutString(unsigned char *pcString); 在這里,我們將這4個函數(shù)都定義成為全局函數(shù),那么,在一個單片機(jī)系統(tǒng)中,在整個程序的任何地方,我們都可以直接調(diào)用這些函數(shù)。 注意:在調(diào)用的時(shí)候直接使用函數(shù)名稱即可,“extern”這個修飾符不必再調(diào)用的時(shí)候?qū)懮稀?/p> 同樣的,對于變量的定義我們可以使用同樣的方法: extern int second; 在這里,我們定義了一個全局的整形變量second; 1.4. 電路設(shè)計(jì)本實(shí)例沿用實(shí)例29的電路。 1.5. 程序設(shè)計(jì)本實(shí)例中,我們分別按照功能劃分,將程序中的延時(shí)程序中的函數(shù)聲明封裝在Delay.h文件中,變量聲明和函數(shù)定義封裝在Delay.c文件中; 液晶顯示程序中的端口定義、函數(shù)聲明封裝在LCD1602.h文件中,變量聲明和函數(shù)定義封裝在LCD1602.c文件中,DS18B20讀寫程序中的端口定義、函數(shù)聲明封裝在DS18B20.h文件中,變量聲明和函數(shù)定義封裝在DS18B20.c文件中。 按照我們說的步驟建好上面程序文件后,按照下圖進(jìn)行操作。 添加.c文件的方法 在主程序以及各個.c文件中,使用下面的預(yù)編譯命令將不同程序模塊中的函數(shù)“包含”進(jìn)來。 #include'LCD1602.h' //#include'DS18B20.h' //#include'Delay.h' // 也許有細(xì)心的朋友會發(fā)現(xiàn),不同的#include與編譯指令還有細(xì)微的差別,例如,調(diào)用51單片機(jī)頭文件和C語言庫函數(shù)使用的是下面的語句
而針對我們自己編寫的.h文件,使用的是下面的語句 #include'LCD1602.h' //#include'DS18B20.h' //#include'Delay.h' // 看出來了吧?調(diào)用keil里面自帶的頭文件時(shí),文件名是用尖括號<>括起來的,而調(diào)用我們自己編寫的頭文件時(shí),文件名是用''括起來的。這是為什么呢?簡單說,使用尖括號<>括起來的頭文件,編譯器在編譯時(shí),先從編譯器安裝的目錄下開始搜索這個文件,而用''括起來的頭文件,編譯的時(shí)候,先從我們建立的單片機(jī)項(xiàng)目文件中搜索該文件。 下面是Delay.h文件的代碼。
下面是Delay.c文件的代碼。//Delay.c#include<reg51.h> //包含單片機(jī)寄存器的頭文件#include<intrins.h> //包含_nop_()函數(shù)定義的頭文件#include'LCD1602.h' //#include'DS18B20.h' //#include'Delay.h' ///*****************************************************函數(shù)功能:延時(shí)1ms(3j+2)*i=(3×33+2)×10=1010(微秒),可以認(rèn)為是1毫秒***************************************************/void delay1ms(){ unsigned char i,j; for(i=0;i<10;i++) for(j=0;j<33;j++) ; }/*****************************************************函數(shù)功能:延時(shí)若干毫秒入口參數(shù):n***************************************************/ void delaynms(unsigned int n) { unsigned int i;for(i=0;i<n;i++) delay1ms(); } 下面是LCD1602.h文件的代碼。
下面是LCD1602.c文件的代碼。//LCD1602.c#include<reg51.h> //包含單片機(jī)寄存器的頭文件#include<intrins.h> //包含_nop_()函數(shù)定義的頭文件#include'LCD1602.h' //#include'DS18B20.h' //#include'Delay.h' //unsigned char code digit[10]={'0123456789'}; //定義字符數(shù)組顯示數(shù)字unsigned char code Str[]={'laomashitu MCU'}; //說明顯示的是溫度unsigned char code Error[]={'Check Error!'}; //說明沒有檢測到DS18B20unsigned char code Temp[]={'wendu:'}; //說明顯示的是溫度unsigned char code Cent[]={'du'}; //溫度單位/*******************************************************************************以下是對液晶模塊的操作程序*******************************************************************************/sbit RS=P2^4; //寄存器選擇位,將RS位定義為P2.0引腳sbit RW=P2^5; //讀寫選擇位,將RW位定義為P2.1引腳sbit E=P2^6; //使能信號位,將E位定義為P2.2引腳sbit BF=P0^7; //忙碌標(biāo)志位,,將BF位定義為P0.7引腳/*****************************************************函數(shù)功能:判斷液晶模塊的忙碌狀態(tài)返回值:result。result=1,忙碌;result=0,不忙***************************************************/bit BusyTest(void) { bit result;RS=0; //根據(jù)規(guī)定,RS為低電平,RW為高電平時(shí),可以讀狀態(tài) RW=1; E=1; //E=1,才允許讀寫 _nop_(); //空操作 _nop_(); _nop_(); _nop_(); //空操作四個機(jī)器周期,給硬件反應(yīng)時(shí)間 result=BF; //將忙碌標(biāo)志電平賦給result E=0; //將E恢復(fù)低電平 return result; }/*****************************************************函數(shù)功能:將模式設(shè)置指令或顯示地址寫入液晶模塊入口參數(shù):dictate***************************************************/void WriteInstruction (unsigned char dictate){ while(BusyTest()==1); //如果忙就等待 RS=0; //根據(jù)規(guī)定,RS和R/W同時(shí)為低電平時(shí),可以寫入指令 RW=0; E=0; //E置低電平(根據(jù)表8-6,寫指令時(shí),E為高脈沖, // 就是讓E從0到1發(fā)生正跳變,所以應(yīng)先置'0' _nop_(); _nop_(); //空操作兩個機(jī)器周期,給硬件反應(yīng)時(shí)間 P0=dictate; //將數(shù)據(jù)送入P0口,即寫入指令或地址 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四個機(jī)器周期,給硬件反應(yīng)時(shí)間 E=1; //E置高電平 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四個機(jī)器周期,給硬件反應(yīng)時(shí)間 E=0; //當(dāng)E由高電平跳變成低電平時(shí),液晶模塊開始執(zhí)行命令 }/*****************************************************函數(shù)功能:指定字符顯示的實(shí)際地址入口參數(shù):x***************************************************/ void WriteAddress(unsigned char x) { WriteInstruction(x|0x80); //顯示位置的確定方法規(guī)定為'80H+地址碼x' }/*****************************************************函數(shù)功能:將數(shù)據(jù)(字符的標(biāo)準(zhǔn)ASCII碼)寫入液晶模塊入口參數(shù):y(為字符常量)***************************************************/ void WriteData(unsigned char y) { while(BusyTest()==1); RS=1; //RS為高電平,RW為低電平時(shí),可以寫入數(shù)據(jù) RW=0; E=0; //E置低電平(根據(jù)表8-6,寫指令時(shí),E為高脈沖, // 就是讓E從0到1發(fā)生正跳變,所以應(yīng)先置'0' P0=y; //將數(shù)據(jù)送入P0口,即將數(shù)據(jù)寫入液晶模塊 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四個機(jī)器周期,給硬件反應(yīng)時(shí)間 E=1; //E置高電平 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四個機(jī)器周期,給硬件反應(yīng)時(shí)間 E=0; //當(dāng)E由高電平跳變成低電平時(shí),液晶模塊開始執(zhí)行命令 }/*****************************************************函數(shù)功能:對LCD的顯示模式進(jìn)行初始化設(shè)置***************************************************/void LcdInitiate(void){ delaynms(15); //延時(shí)15ms,首次寫指令時(shí)應(yīng)給LCD一段較長的反應(yīng)時(shí)間 WriteInstruction(0x38); //顯示模式設(shè)置:16×2顯示,5×7點(diǎn)陣,8位數(shù)據(jù)接口delaynms(5); //延時(shí)5ms ,給硬件一點(diǎn)反應(yīng)時(shí)間 WriteInstruction(0x38);delaynms(5); //延時(shí)5ms ,給硬件一點(diǎn)反應(yīng)時(shí)間WriteInstruction(0x38); //連續(xù)三次,確保初始化成功delaynms(5); //延時(shí)5ms ,給硬件一點(diǎn)反應(yīng)時(shí)間WriteInstruction(0x0c); //顯示模式設(shè)置:顯示開,無光標(biāo),光標(biāo)不閃爍delaynms(5); //延時(shí)5ms ,給硬件一點(diǎn)反應(yīng)時(shí)間WriteInstruction(0x06); //顯示模式設(shè)置:光標(biāo)右移,字符不移delaynms(5); //延時(shí)5ms ,給硬件一點(diǎn)反應(yīng)時(shí)間WriteInstruction(0x01); //清屏幕指令,將以前的顯示內(nèi)容清除delaynms(5); //延時(shí)5ms ,給硬件一點(diǎn)反應(yīng)時(shí)間 } /******************************************************************************以下是與溫度有關(guān)的顯示設(shè)置 ******************************************************************************/ /*****************************************************函數(shù)功能:顯示沒有檢測到DS18B20***************************************************/ void display_error(void) { unsigned char i; WriteAddress(0x00); //寫顯示地址,將在第1行第1列開始顯示 i = 0; //從第一個字符開始顯示while(Error[i] != '\0') //只要沒有寫到結(jié)束標(biāo)志,就繼續(xù)寫{WriteData(Error[i]); //將字符常量寫入LCDi++; //指向下一個字符delaynms(100); //延時(shí)100ms較長時(shí)間,以看清關(guān)于顯示的說明}while(1) //進(jìn)入死循環(huán),等待查明原因 ;}/*****************************************************函數(shù)功能:顯示說明信息***************************************************/ void display_explain(void) { unsigned char i; WriteAddress(0x00); //寫顯示地址,將在第1行第1列開始顯示 i = 0; //從第一個字符開始顯示while(Str[i] != '\0') //只要沒有寫到結(jié)束標(biāo)志,就繼續(xù)寫{WriteData(Str[i]); //將字符常量寫入LCDi++; //指向下一個字符delaynms(100); //延時(shí)100ms較長時(shí)間,以看清關(guān)于顯示的說明}}/*****************************************************函數(shù)功能:顯示溫度符號***************************************************/ void display_symbol(void) { unsigned char i; WriteAddress(0x40); //寫顯示地址,將在第2行第1列開始顯示 i = 0; //從第一個字符開始顯示while(Temp[i] != '\0') //只要沒有寫到結(jié)束標(biāo)志,就繼續(xù)寫{WriteData(Temp[i]); //將字符常量寫入LCDi++; //指向下一個字符delaynms(50); //延時(shí)1ms給硬件一點(diǎn)反應(yīng)時(shí)間}}/*****************************************************函數(shù)功能:顯示溫度的小數(shù)點(diǎn)***************************************************/ void display_dot(void){ WriteAddress(0x49); //寫顯示地址,將在第2行第10列開始顯示 WriteData('.'); //將小數(shù)點(diǎn)的字符常量寫入LCD delaynms(50); //延時(shí)1ms給硬件一點(diǎn)反應(yīng)時(shí)間}/*****************************************************函數(shù)功能:顯示溫度的單位(Cent)***************************************************/ void display_cent(void){ unsigned char i; WriteAddress(0x4c); //寫顯示地址,將在第2行第13列開始顯示 i = 0; //從第一個字符開始顯示 while(Cent[i] != '\0') //只要沒有寫到結(jié)束標(biāo)志,就繼續(xù)寫{WriteData(Cent[i]); //將字符常量寫入LCDi++; //指向下一個字符delaynms(50); //延時(shí)1ms給硬件一點(diǎn)反應(yīng)時(shí)間}}/*****************************************************函數(shù)功能:顯示溫度的整數(shù)部分入口參數(shù):x***************************************************/ void display_temp1(unsigned char x){ unsigned char j,k,l; //j,k,l分別儲存溫度的百位、十位和個位j=x/100; //取百位k=(x%100)/10; //取十位l=x%10; //取個位 WriteAddress(0x46); //寫顯示地址,將在第2行第7列開始顯示W(wǎng)riteData(digit[j]); //將百位數(shù)字的字符常量寫入LCDWriteData(digit[k]); //將十位數(shù)字的字符常量寫入LCDWriteData(digit[l]); //將個位數(shù)字的字符常量寫入LCDdelaynms(50); //延時(shí)1ms給硬件一點(diǎn)反應(yīng)時(shí)間 } /*****************************************************函數(shù)功能:顯示溫度的小數(shù)數(shù)部分入口參數(shù):x***************************************************/ void display_temp2(unsigned char x){ WriteAddress(0x4a); //寫顯示地址,將在第2行第11列開始顯示W(wǎng)riteData(digit[x]); //將小數(shù)部分的第一位數(shù)字字符常量寫入LCDdelaynms(50); //延時(shí)1ms給硬件一點(diǎn)反應(yīng)時(shí)間} 下面是DS18B20.h文件的代碼。
下面是DS18B20.c文件的代碼。//DS18B20.c#include<reg51.h> //包含單片機(jī)寄存器的頭文件#include<intrins.h> //包含_nop_()函數(shù)定義的頭文件#include'LCD1602.h' //#include'DS18B20.h' //#include'Delay.h' //extern unsigned char time; //設(shè)置全局變量,專門用于嚴(yán)格延時(shí)/*****************************************************函數(shù)功能:將DS18B20傳感器初始化,讀取應(yīng)答信號出口參數(shù):flag ***************************************************/bit Init_DS18B20(void){ bit flag; //儲存DS18B20是否存在的標(biāo)志,flag=0,表示存在;flag=1,表示不存在 DQ = 1; //先將數(shù)據(jù)線拉高 for(time=0;time<2;time++) //略微延時(shí)約6微秒 ; DQ = 0; //再將數(shù)據(jù)線從高拉低,要求保持480~960us for(time=0;time<200;time++) //略微延時(shí)約600微秒 ; //以向DS18B20發(fā)出一持續(xù)480~960us的低電平復(fù)位脈沖 DQ = 1; //釋放數(shù)據(jù)線(將數(shù)據(jù)線拉高) for(time=0;time<10;time++) ; //延時(shí)約30us(釋放總線后需等待15~60us讓DS18B20輸出存在脈沖) flag=DQ; //讓單片機(jī)檢測是否輸出了存在脈沖(DQ=0表示存在) for(time=0;time<200;time++) //延時(shí)足夠長時(shí)間,等待存在脈沖輸出完畢 ; return (flag); //返回檢測成功標(biāo)志}/*****************************************************函數(shù)功能:從DS18B20讀取一個字節(jié)數(shù)據(jù)出口參數(shù):dat***************************************************/ unsigned char ReadOneChar(void) {unsigned char i=0;unsigned char dat; //儲存讀出的一個字節(jié)數(shù)據(jù)for (i=0;i<8;i++) { DQ =1; // 先將數(shù)據(jù)線拉高 _nop_(); //等待一個機(jī)器周期 DQ = 0; //單片機(jī)從DS18B20讀書據(jù)時(shí),將數(shù)據(jù)線從高拉低即啟動讀時(shí)序dat>>=1; _nop_(); //等待一個機(jī)器周期 DQ = 1; //將數(shù)據(jù)線'人為'拉高,為單片機(jī)檢測DS18B20的輸出電平作準(zhǔn)備 for(time=0;time<2;time++) ; //延時(shí)約6us,使主機(jī)在15us內(nèi)采樣 if(DQ==1) dat|=0x80; //如果讀到的數(shù)據(jù)是1,則將1存入datelsedat|=0x00;//如果讀到的數(shù)據(jù)是0,則將0存入dat //將單片機(jī)檢測到的電平信號DQ存入r[i] for(time=0;time<8;time++) ; //延時(shí)3us,兩個讀時(shí)序之間必須有大于1us的恢復(fù)期 } return(dat); //返回讀出的十進(jìn)制數(shù)據(jù)}/*****************************************************函數(shù)功能:向DS18B20寫入一個字節(jié)數(shù)據(jù)入口參數(shù):dat***************************************************/ WriteOneChar(unsigned char dat){unsigned char i=0;for (i=0; i<8; i++) { DQ =1; // 先將數(shù)據(jù)線拉高 _nop_(); //等待一個機(jī)器周期 DQ=0; //將數(shù)據(jù)線從高拉低時(shí)即啟動寫時(shí)序 DQ=dat&0x01; //利用與運(yùn)算取出要寫的某位二進(jìn)制數(shù)據(jù), //并將其送到數(shù)據(jù)線上等待DS18B20采樣 for(time=0;time<10;time++) ;//延時(shí)約30us,DS18B20在拉低后的約15~60us期間從數(shù)據(jù)線上采樣 DQ=1; //釋放數(shù)據(jù)線 for(time=0;time<1;time++) ;//延時(shí)3us,兩個寫時(shí)序間至少需要1us的恢復(fù)期 dat>>=1; //將dat中的各二進(jìn)制位數(shù)據(jù)右移1位 } for(time=0;time<4;time++) ; //稍作延時(shí),給硬件一點(diǎn)反應(yīng)時(shí)間}/************************************************函數(shù)功能:做好讀溫度的準(zhǔn)備***************************************************/ void ReadyReadTemp(void){ Init_DS18B20(); //將DS18B20初始化WriteOneChar(0xCC); // 跳過讀序號列號的操作WriteOneChar(0x44); // 啟動溫度轉(zhuǎn)換 for(time=0;time<100;time++) ; //溫度轉(zhuǎn)換需要一點(diǎn)時(shí)間Init_DS18B20(); //將DS18B20初始化WriteOneChar(0xCC); //跳過讀序號列號的操作WriteOneChar(0xBE); //讀取溫度寄存器,前兩個分別是溫度的低位和高位} 下面是主程序文件的代碼。
1.6. 實(shí)例仿真根據(jù)前面的說明,按照步驟建立起模塊化程序結(jié)構(gòu)。編譯后,按照上一實(shí)例的方法進(jìn)行仿真,觀察非模塊化方法編寫的程序,以及用模塊化方法編寫的程序在運(yùn)行結(jié)果上是完全一樣的。 1.7. 總結(jié)通過本實(shí)例,我們學(xué)習(xí)了如何將功能眾多,程序量大的單片機(jī)程序分成不同的模塊,從而使單片機(jī)程序看起來結(jié)構(gòu)清晰,可讀性強(qiáng),可移植性強(qiáng)。 |
|