找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開(kāi)始

搜索
查看: 12883|回復(fù): 4
打印 上一主題 下一主題
收起左側(cè)

第19章 實(shí)踐項(xiàng)目開(kāi)發(fā)指導(dǎo)--多功能電子鐘

  [復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
#
ID:1 發(fā)表于 2013-11-28 20:08 | 只看該作者 |只看大圖 回帖獎(jiǎng)勵(lì) |正序?yàn)g覽 |閱讀模式
本教材現(xiàn)以連載的方式由網(wǎng)絡(luò)發(fā)布,并將于2014年由清華大學(xué)出版社出版最終完整版,版權(quán)歸作者和清華大學(xué)出版社所有。本著開(kāi)源、分享的理念,本教材可以自由傳播及學(xué)習(xí)使用,但是務(wù)必請(qǐng)注明出處來(lái)自金沙灘工作室 .

我們課程到了這里,基本知識(shí)介紹完畢。如果同學(xué)們能夠認(rèn)真把前邊的“降龍十八章”領(lǐng)悟透徹,那剩下的主要工作就是不斷反復(fù)練習(xí)鞏固了。本章我們首先介紹實(shí)際項(xiàng)目開(kāi)發(fā)中的一些技巧和規(guī)范性的東西,然后帶領(lǐng)大家一起來(lái)做一個(gè)真正的項(xiàng)目,把項(xiàng)目開(kāi)發(fā)的整個(gè)流程都走一遍。

19.1 類型說(shuō)明
C語(yǔ)言不僅提供了豐富的數(shù)據(jù)類型給我們使用,而且還允許用戶自己定義類型說(shuō)明符,也就是說(shuō)為了方便,給已經(jīng)存在的數(shù)據(jù)類型起個(gè)“代號(hào)”,比如“9527就是你的終身代號(hào)”,就用9527來(lái)代表某個(gè)人。在C語(yǔ)言中,使用typedef即可完成這項(xiàng)功能,定義格式如下:
typedef  原類型名   新類型名
typedef語(yǔ)句并未定義一種新的數(shù)據(jù)類型,他僅僅是給已經(jīng)有的數(shù)據(jù)類型取了一個(gè)更加簡(jiǎn)潔直觀的名字,可以用這個(gè)新的類型名字來(lái)定義變量。在實(shí)際開(kāi)發(fā)中,很多公司都會(huì)使用這個(gè)關(guān)鍵字來(lái)給變量類型取新名字,一是為了方便代碼的移植,還有就是為了代碼更加的簡(jiǎn)潔一些,比如以下的這幾種類型定義方式。
typedef  signed    char    int8;    // 8位有符號(hào)整型數(shù)
typedef  signed    int     int16;   //16位有符號(hào)整型數(shù)
typedef  signed    long    int32;   //32位有符號(hào)整型數(shù)
typedef  unsigned  char    uint8;   // 8位無(wú)符號(hào)整型數(shù)
typedef  unsigned  int     uint16;  //16位無(wú)符號(hào)整型數(shù)
typedef  unsigned  long    uint32;  //32位無(wú)符號(hào)整型數(shù)
經(jīng)過(guò)以上的這種類型說(shuō)明后,今后我們?cè)诔绦蛑芯涂梢灾苯邮褂胾int8來(lái)替代unsigned char來(lái)定義變量。聰明的你,是否發(fā)現(xiàn)我們起的這個(gè)代號(hào),無(wú)符號(hào)型的前邊帶一個(gè)u,有符號(hào)的不帶u,int表示整數(shù)的意思,后邊的數(shù)字代表的是這個(gè)變量類型占的位數(shù),這種命名方式很多公司都采用,大家也可以學(xué)著采用這種方式。
有的時(shí)候也有用宏定義代替typedef的功能,但是宏定義是由預(yù)處理完成的,而typedef則是在編譯時(shí)完成的,后者更加靈活。我發(fā)現(xiàn)有的同學(xué)用這種定義方式:
#define  uchar  unsigned char
這種方式不建議大家使用,在這種應(yīng)用下是沒(méi)問(wèn)題,但是當(dāng)用到指針的時(shí)候,就有可能出錯(cuò),在一些比較正規(guī)的公司如果寫(xiě)出這種形式可能會(huì)感覺(jué)寫(xiě)代碼的人比較初級(jí)。下面我們就介紹一下typedef#define 之間的區(qū)別。
#define是預(yù)編譯處理命令,在編譯處理時(shí)進(jìn)行簡(jiǎn)單的替換,不做任何正確性檢查,不管含義是否正確都會(huì)被代入,比如:
#define  PI  3.1415926
有了這個(gè)宏,我們今后可以直接用PI來(lái)替代3.1415926了,比如我們寫(xiě)area = PI*r*r求圓的面積就會(huì)直接替換成3.1415926*r*r。如果我們不小心寫(xiě)成了3.1415g26,編譯的時(shí)候還是會(huì)代入。
typedef是在編譯時(shí)進(jìn)行處理的,它是在自己的作用域內(nèi)給一個(gè)已經(jīng)存在的類型起一個(gè)代號(hào),如果我們把前邊的類型說(shuō)明錯(cuò)誤的寫(xiě)成:
typedef  unsinged  char    uint8;
編譯器會(huì)直接報(bào)錯(cuò)。
對(duì)于#define來(lái)說(shuō),更多的應(yīng)用是進(jìn)行一些程序可讀性、易維護(hù)的替換。比如:   
#define   LCD1602_DB  P0
#define   SYS_MCLK   (11059200/12)
在寫(xiě)1602程序的過(guò)程中,我們可以直接用LCD1602_DB表示1602的通信總線,我們也可以直接用SYS_MCLK來(lái)作為我們單片機(jī)的機(jī)器周期,這樣如果改動(dòng)一些硬件,比如出于特定需要而換了其它頻率的晶振,那么我們可以直接在程序最開(kāi)始部分改一下即可,不用到處去修改數(shù)字了。
而對(duì)于類型說(shuō)明,有的情況下typedef#define用法一樣,有的情況就不一樣了。
typedef  unsigned  char    uint8;       uint8  i, j;
#define  uchar  unsigned char           uchar  i, j;
這兩種用法是完全相同的,等價(jià)的,沒(méi)有區(qū)別,不過(guò)大家要注意typedef后邊有分號(hào),而#define后邊是沒(méi)有分號(hào)的。
typedef   int*  int_p;     int_p  i, j;
#define   int_p  int*      int_p  i,  j;
這兩種用法得到的結(jié)果是不一樣的,其中第一種無(wú)疑是定義了ij這兩個(gè)int指針變量。而第二種呢?因?yàn)?/font>define是直接替換,實(shí)際上就是int* i, j; 所以i是一個(gè)int指針變量,而j卻是一個(gè)普通的int變量。
總之,typedef是專門(mén)給類型重新起名的,而#define是純粹替換的,大家記住其用法。
19.2 頭文件
在前邊的章節(jié)中,我們多次使用過(guò)文件包含命令#include,這條指令的功能是將指定的被包含文件的全部?jī)?nèi)容插到該命令行的位置處,從而把指定文件和當(dāng)前的源程序文件連成一個(gè)源文件參與編譯,通常的寫(xiě)法如下:
#include <文件名>          或者 #include ”文件名”
使用尖括號(hào)表示預(yù)處理程序直接到系統(tǒng)指定的“包含文件目錄”去查找,使用雙引號(hào)則表示預(yù)處理程序首先在當(dāng)前文件所在的文件目錄中查找被包含的文件,如果沒(méi)有找到才會(huì)再到系統(tǒng)的“包含文件目錄”去查找。一般情況下,我們的習(xí)慣是系統(tǒng)提供的頭文件用尖括號(hào)方式,我們用戶自己編寫(xiě)的頭文件用雙引號(hào)方式。
我們?cè)谇斑呌眠^(guò)很多次#include <reg52.h>,這個(gè)文件所在的位置是keil軟件安裝目錄的\C51\INC這個(gè)路徑內(nèi),大家可以去看看,在這個(gè)文件夾內(nèi),有很多系統(tǒng)自帶的頭文件,當(dāng)然也包含了<intrins.h>這個(gè)頭文件。當(dāng)我們一旦寫(xiě)了#include <reg52.h>這條指令后,那么相當(dāng)于在我們當(dāng)前的.C文件中,寫(xiě)下了以下的代碼。
#ifndef __REG52_H__
#define __REG52_H__

/*  BYTE Registers  */
sfr P0    = 0x80;
sfr P1    = 0x90;
sfr P2    = 0xA0;
sfr P3    = 0xB0;
... ...

/*  BIT Registers  */
/*  PSW  */
sbit CY    = PSW^7;
sbit AC    = PSW^6;
sbit F0    = PSW^5;
sbit RS1   = PSW^4;
sbit RS0   = PSW^3;
sbit OV    = PSW^2;
sbit P     = PSW^0; //8052 only

/*  TCON  */
sbit TF1   = TCON^7;
sbit TR1   = TCON^6;
sbit TF0   = TCON^5;
sbit TR0   = TCON^4;
sbit IE1   = TCON^3;
sbit IT1   = TCON^2;
sbit IE0   = TCON^1;
sbit IT0   = TCON^0;
... ...

#endif
我們之前在程序中,只要寫(xiě)了#include <reg52.h>這條指令,我們就可以隨便使用P0、TCONTMOD這些寄存器和TR0、TR1、TIRI等這些寄存器的位,都是因?yàn)樗鼈円呀?jīng)在這個(gè)頭文件中定義或聲明過(guò)了。
前邊我們講過(guò),要調(diào)用某個(gè)函數(shù),必須提前進(jìn)行聲明。而Keil自己做了很多函數(shù),生成了庫(kù)文件,我們?nèi)绻褂眠@些函數(shù)的時(shí)候,不需要寫(xiě)這些函數(shù)的代碼,而直接調(diào)用這些函數(shù)即可,調(diào)用之前首先要進(jìn)行聲明一下,而這些聲明也放在頭文件當(dāng)中。比如我們所用的_nop_();函數(shù),就是在<intrins.h>這個(gè)頭文件中。
在我們前邊應(yīng)用的實(shí)例中,很多文件中的所要用到的函數(shù),都是在其他文件中定義的,我們?cè)诋?dāng)前文件要調(diào)用它的時(shí)候,提前聲明一下即可。為了讓我們程序的易維護(hù)性和可移植性提高,我們自己就可以編寫(xiě)我們所需要的頭文件。我們自己編寫(xiě)的頭文件中不僅僅可以進(jìn)行函數(shù)的聲明,變量的外部聲明,一些宏定義也可以放在其中。
舉個(gè)例子,比如我們?cè)?font face="Times New Roman">main.c這個(gè)文件中,配套寫(xiě)了一個(gè)main.h文件。新建頭文件的方式也很簡(jiǎn)單,和.c是類似的,首先點(diǎn)擊新建文件的那個(gè)圖標(biāo),或者點(diǎn)擊菜單File->New,然后點(diǎn)擊保存文件,保存的時(shí)候命名為main.h即可。為了方便我們編寫(xiě)、修改維護(hù),我們?cè)?/font>Keil編程環(huán)境中新建一個(gè)頭文件組,把所有的源文件放在一個(gè)組內(nèi),把所有的頭文件放在一個(gè)組內(nèi),如圖19-1所示。

圖19-1 工程文件分組管理
大家注意,main.h里除了要包含main.c所要使用的一些宏之外,還要在里邊對(duì)main.c文件中所定義的全局變量,進(jìn)行extern聲明,提供給其他的.c文件使用,還要把main.c內(nèi)的自定義類型進(jìn)行聲明,還要把main.c內(nèi)所使用的全局函數(shù)進(jìn)行聲明,方便給其他文件調(diào)用。比如我們把main.h文件寫(xiě)成下邊這樣。
enum eStaSystem {  //系統(tǒng)運(yùn)行狀態(tài)枚舉
    E_NORMAL, E_SET_TIME, E_SET_ALARM
};

extern enum eStaSystem staSystem;

void RefreshTemp(uint8 ops);
void ConfigTimer0(uint16 ms);
首先大家注意,對(duì)于函數(shù)的外部聲明,extern是可以省略的,但是對(duì)于外部變量的聲明是不能省略的。其次enum是一個(gè)枚舉體,前邊我們已經(jīng)提到過(guò)了,大家可以再把書(shū)翻回去了解一下枚舉體的作用和結(jié)構(gòu)。我們?cè)?/font>main.c當(dāng)中定義的staSystem其他文件中要調(diào)用,在這里就要用extern聲明一下。
頭文件這樣編寫(xiě)看似沒(méi)問(wèn)題,實(shí)際上則不然。首先第一個(gè)比較明顯的問(wèn)題,由于所有的源文件都有可能要包含這個(gè)main.h,同樣main.c也會(huì)包含它,而staSystem這個(gè)枚舉變量是在main.c中定義的,所以當(dāng)main.hmain.c包含時(shí)就不需要進(jìn)行外部聲明,而被其它文件包含時(shí)則應(yīng)進(jìn)行這個(gè)聲明。此外,在我們的程序編寫(xiě)過(guò)程中,經(jīng)常會(huì)遇到頭文件包含頭文件的用法,假設(shè)a.h包含了main.h文件,b.h文件同樣也包含了main.h文件,如果現(xiàn)在有一個(gè)c文件1602.c文件既包含了a.h又包含了b.h,這樣就會(huì)出現(xiàn)頭文件的重復(fù)包含,從而會(huì)發(fā)生變量函數(shù)等的重復(fù)聲明,因此我們C語(yǔ)言還有一個(gè)知識(shí)點(diǎn)叫做條件編譯。
19.3 條件編譯
條件編譯屬于預(yù)處理程序,包括我們之前講的宏,都是程序在編譯之前的一些必要的處理過(guò)程,這些都不是實(shí)際程序功能代碼,而僅僅是告訴編譯器需要進(jìn)行的特定操作等。
條件編譯通常有三種用法,第一種表達(dá)式:
#if  表達(dá)式
     程序段 1
#else  
     程序段 2
#endif
作用:如果表達(dá)式的值為“真”(0),則編譯程序段1,否則,編譯程序段2。在使用中,表達(dá)式通常是一個(gè)常量,我們通常事先用宏來(lái)進(jìn)行聲明,通過(guò)宏聲明的值來(lái)確定到底執(zhí)行哪段程序。
比如我們公司開(kāi)發(fā)了同類的兩款產(chǎn)品,這兩款產(chǎn)品的功能有一部分是相同的,有一部分是不同的,同樣所編寫(xiě)的程序代碼大部分的代碼是一樣的,只有一少部分有區(qū)別。這個(gè)時(shí)候?yàn)榱朔奖愠绦虻木S護(hù),可以把兩款產(chǎn)品的代碼寫(xiě)到同一個(gè)工程程序中,然后把其中有區(qū)別的功能利用條件編譯。
#define  PLAN   0
#if (PLAN == 0)
     程序段1
#else
     程序段2
#endif
這樣寫(xiě)之后,當(dāng)我們要編譯款式1的時(shí)候,把PLAN宏聲明成0即可,當(dāng)我們要編譯款式2的時(shí)候,把宏聲明的值改為1或其它值即可。
第二種表達(dá)式和第三種表達(dá)式是類似的,使用哪一種完全看個(gè)人喜好,但是所有的程序最好統(tǒng)一。
表達(dá)式二:
#ifdef  標(biāo)識(shí)符
       程序段1
#else  
        程序段2
#endif
表達(dá)式三:
#ifndef  標(biāo)識(shí)符
        程序段1
#else
        程序段2
#endif
在本章的示例中我們使用到了表達(dá)式三,表達(dá)式三的作用是:如果標(biāo)識(shí)符沒(méi)有被#define命令所聲明過(guò),則編譯程序段1,否則則編譯程序段2。此外,命令中的#else部分是可以省略的。表達(dá)式二和表達(dá)式三正好相反,大家自己看一下吧。其實(shí)#ifndef就是if no define的縮寫(xiě)。
在頭文件的編寫(xiě)過(guò)程中,為了防止命名的錯(cuò)亂,我們每個(gè).c文件對(duì)應(yīng)的.h文件,除名字一致外,進(jìn)行宏聲明的時(shí)候,也用這個(gè)頭文件的名字,并且大寫(xiě),在中間加上下劃線,比如我們這個(gè)main.h的結(jié)構(gòu),我們首先要這樣寫(xiě):
#ifndef _MAIN_H
#define _MAIN_H

    程序段1

#endif
這樣說(shuō)明的意思是,如果這個(gè)_MAIN_H沒(méi)有聲明,那么我們就聲明_MAIN_H,并且我們的程序段1是有效的,最終結(jié)束;那么如果_MAIN_H已經(jīng)聲明過(guò)了,那么我們也就不用在聲明了,同時(shí)程序段1也就不必要再有效了。這樣就可以有效的解決了a.h包含了main.h后,b.h中既包含main.h,而1602.c既包含a.h又包含b.h所帶來(lái)的尷尬。
第二個(gè)問(wèn)題是,main.c文件中定義的外部變量,在main.c中不需要進(jìn)行外部聲明。那么我們可以在我們的main.c程序中最開(kāi)始的位置加上一句:
#define  _MAIN_C
然后在main.h內(nèi)對(duì)這類變量進(jìn)行聲明的時(shí)候,再加上這樣的條件編譯語(yǔ)句:
#ifndef  _MAIN_C
     程序段2
#endif   
這樣處理之后,大家看一下,由于我們?cè)?font face="Times New Roman">main.c的程序中首先對(duì)_MAIN_C進(jìn)行宏聲明了,因此程序段2中的內(nèi)容不會(huì)參與到main.c的編譯中去,而其他所有的包含main.h的源文件則會(huì)把程序段2參與到編譯中,因此前邊我們的main.h文件的整體代碼如下所示。
#ifndef _MAIN_H
#define _MAIN_H

enum eStaSystem {  //系統(tǒng)運(yùn)行狀態(tài)枚舉
    E_NORMAL, E_SET_TIME, E_SET_ALARM
};

#ifndef _MAIN_C
extern enum eStaSystem staSystem;
#endif

void RefreshTemp(uint8 ops);
void ConfigTimer0(uint16 ms);

#endif
19.4 多功能電子鐘
本章的重頭戲就是我們要做的這個(gè)項(xiàng)目實(shí)踐開(kāi)發(fā)——多功能電子鐘。當(dāng)接到一個(gè)具體項(xiàng)目開(kāi)發(fā)任務(wù)后,要根據(jù)項(xiàng)目做出框架規(guī)劃,整理出邏輯思路,并且寫(xiě)出規(guī)范的程序,調(diào)試代碼最終完成功能。[size=14.0000pt]19.4.1
硬件布局規(guī)劃
作為電子鐘,或者說(shuō)萬(wàn)年歷,提供日期、時(shí)間的顯示是一個(gè)基本的功能,但是我們的設(shè)計(jì)要求并不滿足于基本功能,而是要提供更多的信息,并且兼容人性化設(shè)計(jì)。在我們的設(shè)計(jì)中,除了基本的走時(shí)(包括時(shí)間、日期、星期)、板載按鍵校時(shí)功能外,還提供鬧鐘、溫度測(cè)量、紅外遙控校時(shí)這幾項(xiàng)實(shí)用功能,所以稱之為多功能。
如果一個(gè)產(chǎn)品只是所需功能的雜亂堆積,而不考慮怎樣讓人用起來(lái)更舒服、更愉悅,那么這就非常的不人性化,也絕對(duì)不是一個(gè)優(yōu)秀的設(shè)計(jì)或者說(shuō)產(chǎn)品。比如電子鐘把日期和時(shí)間都顯示到液晶上,這樣看起來(lái)主次就不是很分明,顯得雜亂。人性化設(shè)計(jì)考慮的是大多數(shù)人的行為習(xí)慣,當(dāng)然最終的產(chǎn)品依靠了設(shè)計(jì)人員的經(jīng)驗(yàn)和審美等因素。比如我們KST-51開(kāi)發(fā)板的器件布局,右上方向是顯示器件,右下是按鍵輸入,有一些外圍器件比如上下拉電阻,三極管等我們可以隱藏到液晶底下,這就是大多數(shù)人的習(xí)慣。而在我們的多功能電子鐘項(xiàng)目中,如何去體現(xiàn)人性化設(shè)計(jì)呢?
我們先來(lái)觀察一下各種顯示器件,數(shù)字顯示如果采用LED點(diǎn)陣或者數(shù)碼管就會(huì)比較醒目,但是點(diǎn)陣無(wú)法同時(shí)顯示這么多數(shù)字,于是我們就把最常用的時(shí)間用數(shù)碼管來(lái)顯示,日期、鬧鐘設(shè)置、溫度等輔助信息我們顯示到液晶上。那么點(diǎn)陣呢?我們可以用它來(lái)顯示星期,這對(duì)于盼望著周末的人們來(lái)說(shuō)是不是很醒目很人性化呢?對(duì)了,還有獨(dú)立的LED,我們就用它來(lái)給電子鐘做裝飾吧,用個(gè)來(lái)回跑的流水燈增加點(diǎn)活潑氣氛。最后再來(lái)個(gè)遙控器功能,如果電子鐘掛的太高了或者放在不方便觸碰的位置,我們就可以使用遙控器來(lái)校時(shí)。大家再來(lái)想想看,整個(gè)過(guò)程是不是挺人性化的。
當(dāng)然了,我們所用的是KST-51單片機(jī)開(kāi)發(fā)板來(lái)作為我們的硬件平臺(tái),如果這個(gè)是個(gè)實(shí)際項(xiàng)目,就不需要那么多外圍器件了,首先做好單片機(jī)最小系統(tǒng),而后配備我們多功能電子鐘所需要的硬件外設(shè)就可以了。也就是說(shuō),我們?cè)谶M(jìn)行項(xiàng)目開(kāi)發(fā)時(shí),設(shè)計(jì)的硬件電路是根據(jù)我們的實(shí)際項(xiàng)目需求來(lái)設(shè)計(jì)的。

19.4.2 程序結(jié)構(gòu)組織
項(xiàng)目需求和硬件規(guī)劃已經(jīng)確定了,我們就得研究如何實(shí)現(xiàn)它們,程序結(jié)構(gòu)如何組織。一個(gè)項(xiàng)目,如果需要的部件很多,同時(shí)實(shí)現(xiàn)的功能也很多,為了方便編寫(xiě)和程序維護(hù),整個(gè)程序必須采用模塊化編程,也就是每個(gè)模塊對(duì)應(yīng)一個(gè)c文件來(lái)實(shí)現(xiàn),這種用法實(shí)際上在前面的章節(jié)已經(jīng)開(kāi)始使用了。一方面,如果所有的代碼堆到一起會(huì)顯得雜亂無(wú)章,更重要的是容易造成意外錯(cuò)誤,程序一旦有邏輯上的問(wèn)題或者更新需求,這種維護(hù)將變成一種災(zāi)難。此外,當(dāng)一個(gè)項(xiàng)目程序量很大的時(shí)候,可以由多個(gè)程序員共同參與編程,多模塊的方式也可以讓每個(gè)程序員之間的代碼最終很方便的融合到一起。
模塊的劃分并沒(méi)有什么教條可以遵循,而是根據(jù)具體需要靈活處理。那么我們就以這個(gè)多功能電子鐘項(xiàng)目為例,來(lái)給大家介紹說(shuō)明如何合理的劃分模塊。我們要實(shí)現(xiàn)的功能有:走時(shí)、校時(shí)、鬧鐘、溫度、遙控這幾個(gè)功能。要想實(shí)現(xiàn)這幾個(gè)功能,其中走時(shí)所需要的就是時(shí)鐘芯片,即DS1302;時(shí)間需要顯示給人看,就需要顯示器件,我們用到了點(diǎn)陣、數(shù)碼管、獨(dú)立LED、液晶;再來(lái)看校時(shí),校時(shí)需要輸入器件,本例中我們可以用板載按鍵和遙控器,他們各自的驅(qū)動(dòng)代碼不同,但是實(shí)現(xiàn)的功能是一樣的,都是校時(shí);還有鬧鐘設(shè)置,在校時(shí)的輸入器件的支持下,鬧鐘也就不需要額外的硬件輸入了,只需要用程序代碼讓蜂鳴器響就行了。
功能上大概列舉出來(lái)了,那么我們就可以把程序源代碼劃分為這樣幾個(gè)模塊:DS1302作為走時(shí)的核心自成一個(gè)模塊;點(diǎn)陣、數(shù)碼管、獨(dú)立LED都屬于LED的范疇,控制方式都類似,也都需要?jiǎng)討B(tài)掃描,所以把他們整體作為一個(gè)模塊;液晶是另一個(gè)顯示模塊;按鍵和遙控器的驅(qū)動(dòng)各自成為一個(gè)模塊。
模塊劃分到這里,大家就要特別注意,隨著我們程序量變大,功能變強(qiáng),對(duì)程序的劃分要分層了。前邊我們劃分的這些模塊,都屬于是底層驅(qū)動(dòng)范疇的,他們要共同為上層應(yīng)用服務(wù),那么上層應(yīng)用是什么呢?就是根據(jù)最終需要顯示的效果來(lái)調(diào)度各種顯示驅(qū)動(dòng)函數(shù),決定把時(shí)間的哪一部分顯示到哪個(gè)器件上,然后還要根據(jù)按鍵或者遙控器的輸入來(lái)具體實(shí)現(xiàn)時(shí)間的調(diào)整,還要不停的對(duì)比當(dāng)前時(shí)間和設(shè)定的鬧鐘時(shí)間來(lái)完成鬧鐘功能,那么這些功能函數(shù)自然就成為一個(gè)應(yīng)用層模塊了(當(dāng)然你也可以把它們都放在main.c文件內(nèi)實(shí)現(xiàn),但我們不推薦這樣做,如果程序還有其他應(yīng)用層代碼模塊的話,main.c仍然會(huì)變得復(fù)雜而不易維護(hù))。這個(gè)應(yīng)用層模塊在本例中我們?nèi)∶麨?/font>Time.c,即完成時(shí)間相關(guān)的應(yīng)用層功能。最后,還有一個(gè)溫度功能,除了要加入溫度傳感器的DS18B20底層驅(qū)動(dòng)模塊外,它的上層顯示功能非常簡(jiǎn)單,不值得再單獨(dú)占一個(gè)c文件,所以我們直接把它放到main.c中實(shí)現(xiàn)。
模塊劃分完畢,我們就要進(jìn)行整體程序流程的規(guī)劃。我們剛剛對(duì)程序進(jìn)行了分層,一層是硬件底層驅(qū)動(dòng),再就是上層應(yīng)用功能。底層驅(qū)動(dòng)這些是不需要什么流程圖的,流程圖的主要結(jié)構(gòu)都是上層應(yīng)用程序的流程。當(dāng)我們把上層應(yīng)用程序的流程劃分出來(lái)之后,每個(gè)上層應(yīng)用功能都會(huì)有對(duì)底層硬件操作的需求,比如按鍵實(shí)現(xiàn)的功能,必然要寫(xiě)按鍵的底層驅(qū)動(dòng)程序,這些在前邊的學(xué)習(xí)過(guò)程中我們都會(huì)寫(xiě)了。根據(jù)我們所需要的上層應(yīng)用功能,我們畫(huà)出了我們的流程圖,如圖19-2所示。

19-2 多功能電子鐘流程圖
[size=14.0000pt]19.4.3 [size=14.0000pt]程序代碼編寫(xiě)
在實(shí)際項(xiàng)目開(kāi)發(fā)中,我們不僅僅希望我們的源程序、頭文件等文件結(jié)構(gòu)規(guī)范、代碼編寫(xiě)規(guī)范,更希望我們的工程文件規(guī)整規(guī)范,方便維護(hù)。因此我們首先新建一個(gè)lesson19_1的文件夾,用來(lái)存放我們本章的工程文件。而后我們新建工程保存的時(shí)候,在lesson19-1文件夾內(nèi)再建立一個(gè)文件夾,取名為project,專門(mén)用于存放工程文件的,如圖19-3所示。

圖19-3 工程文件夾
然后我們新建文件,保存的時(shí)候,在lesson19_1目錄再建立一個(gè)文件夾,取名為source文件夾,專門(mén)用來(lái)存放我們的源代碼,如圖19-4所示。

圖19-4 文件文件夾
最后,隨便看一個(gè)之前的例子都能看到,工程編譯后會(huì)生成很多額外的文件,這些文件可以統(tǒng)稱為編譯輸出文件,輸出文件的路徑配置,進(jìn)入Options for Target->Output,點(diǎn)擊Select Folder for Objects,在lesson19_1建立一個(gè)文件夾,取名為output,專門(mén)用來(lái)存放這些輸出文件,如圖19-5所示。

圖19-5 輸出文件夾
進(jìn)行了這樣三個(gè)步驟,當(dāng)今后我們要對(duì)這個(gè)工程進(jìn)行整理編寫(xiě)的時(shí)候,文件就不再凌亂了,而是非常規(guī)整的排列在我們的文件夾內(nèi)。尤其是今后大家還可能學(xué)到編寫(xiě)程序的另外的方式,就是編譯的時(shí)候使用Keil軟件,而編寫(xiě)代碼的時(shí)候在其他更好的編輯器中進(jìn)行,那么編輯器的工程文件也可以放到project下,而不會(huì)對(duì)其它部分產(chǎn)生任何影響?傊,這是一套規(guī)范而又實(shí)用的工程文件組織方案。
工程建立完畢,文件夾也整理妥當(dāng),下面就開(kāi)始正式編寫(xiě)代碼。當(dāng)我們要進(jìn)行一個(gè)實(shí)際產(chǎn)品或者項(xiàng)目開(kāi)發(fā)的時(shí)候,首先電路原理圖是確定的,所使用的單片機(jī)的引腳也是明確的,還有一些比如類型說(shuō)明,一些特殊的全局參數(shù)及宏聲明,我們會(huì)放到一個(gè)專門(mén)的頭文件中,在這里我們命名為config.h文件。
/***********************config.h文件程序源代碼*************************/
#ifndef _CONFIG_H
#define _CONFIG_H

/* 通用頭文件 */
#include <reg52.h>
#include <intrins.h>

/* 數(shù)據(jù)類型定義 */
typedef  signed    char    int8;    // 8位有符號(hào)整型數(shù)
typedef  signed    int     int16;   //16位有符號(hào)整型數(shù)
typedef  signed    long    int32;   //32位有符號(hào)整型數(shù)
typedef  unsigned  char    uint8;   // 8位無(wú)符號(hào)整型數(shù)
typedef  unsigned  int     uint16;  //16位無(wú)符號(hào)整型數(shù)
typedef  unsigned  long    uint32;  //32位無(wú)符號(hào)整型數(shù)

/* 全局運(yùn)行參數(shù)定義 */
#define SYS_MCLK   (11059200/12)  //系統(tǒng)主時(shí)鐘頻率,即振蕩器頻率÷12

/* IO引腳分配定義 */
sbit KEY_IN_1  = P2^4;  //矩陣按鍵的掃描輸入引腳1
sbit KEY_IN_2  = P2^5;  //矩陣按鍵的掃描輸入引腳2
sbit KEY_IN_3  = P2^6;  //矩陣按鍵的掃描輸入引腳3
sbit KEY_IN_4  = P2^7;  //矩陣按鍵的掃描輸入引腳4
sbit KEY_OUT_1 = P2^3;  //矩陣按鍵的掃描輸出引腳1
sbit KEY_OUT_2 = P2^2;  //矩陣按鍵的掃描輸出引腳2
sbit KEY_OUT_3 = P2^1;  //矩陣按鍵的掃描輸出引腳3
sbit KEY_OUT_4 = P2^0;  //矩陣按鍵的掃描輸出引腳4

sbit ADDR0 = P1^0;  //LED位選譯碼地址引腳0
sbit ADDR1 = P1^1;  //LED位選譯碼地址引腳1
sbit ADDR2 = P1^2;  //LED位選譯碼地址引腳2
sbit ADDR3 = P1^3;  //LED位選譯碼地址引腳3
sbit ENLED = P1^4;  //LED顯示部件的總使能引腳

#define LCD1602_DB  P0   //1602液晶數(shù)據(jù)端口
sbit LCD1602_RS = P1^0;  //1602液晶指令/數(shù)據(jù)選擇引腳
sbit LCD1602_RW = P1^1;  //1602液晶讀寫(xiě)引腳
sbit LCD1602_E  = P1^5;  //1602液晶使能引腳

sbit DS1302_CE = P1^7;  //DS1302片選引腳
sbit DS1302_CK = P3^5;  //DS1302通信時(shí)鐘引腳
sbit DS1302_IO = P3^4;  //DS1302通信數(shù)據(jù)引腳

sbit I2C_SCL = P3^7;  //I2C總線時(shí)鐘引腳
sbit I2C_SDA = P3^6;  //I2C總線數(shù)據(jù)引腳

sbit BUZZER = P1^6;  //蜂鳴器控制引腳

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

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

#endif
這個(gè)config.h聲明包含了系統(tǒng)所共同使用的類型說(shuō)明以及宏聲明,方便使用。下邊的編程步驟,就是從main.c文件開(kāi)始,以流程圖作為主線來(lái)進(jìn)行代碼編寫(xiě)。
作為研發(fā)工程師來(lái)講,調(diào)試這樣一個(gè)程序,也得幾個(gè)小時(shí)的時(shí)間,不可能寫(xiě)出來(lái)就好用,所以我這里是無(wú)法全部把整個(gè)過(guò)程給大家還原出來(lái)。但是主要的編寫(xiě)代碼的過(guò)程我會(huì)盡可能的給大家介紹一下。
我們程序的流程雖然是從main.c開(kāi)始的,但是那是整體程序框架,而編寫(xiě)代碼,往往用流程圖來(lái)做主線,卻不是嚴(yán)格按照流程圖的順序來(lái)。比如我們這個(gè)程序,首先我們要進(jìn)行功能性調(diào)試驗(yàn)證。
習(xí)慣上,我首先要調(diào)試顯示程序,因?yàn)轱@示程序可以直觀的看到,而且調(diào)試好顯示后,如果要調(diào)試其他的模塊,可以用顯示模塊來(lái)驗(yàn)證其他模塊運(yùn)行結(jié)果正確與否。顯示設(shè)備就是1602液晶和LED,由于蜂鳴器比較簡(jiǎn)單,所以我們將蜂鳴器和LED放到一起。調(diào)試的時(shí)候,可以在main.c文件中,添加臨時(shí)的調(diào)試函數(shù),比如給1602液晶發(fā)送數(shù)據(jù),讓1602液晶顯示個(gè)字符串,保證1602液晶的底層程序是沒(méi)問(wèn)題的;調(diào)用相應(yīng)的函數(shù)讓LED進(jìn)行顯示以及刷新,保證LED部分的程序也是沒(méi)問(wèn)題的。通過(guò)這種方式,如果發(fā)現(xiàn)哪部分還有問(wèn)題就繼續(xù)調(diào)整,如果發(fā)現(xiàn)顯示部分OK了,那就可以繼續(xù)往下編寫(xiě)了。
1602液晶的底層驅(qū)動(dòng)我們之前都已經(jīng)寫(xiě)過(guò)了,直接拿過(guò)來(lái)用就行了。而對(duì)于LED的動(dòng)態(tài)刷新問(wèn)題,在講紅外的時(shí)候已經(jīng)闡述過(guò),用于LED刷新的定時(shí)器應(yīng)該采用高優(yōu)先級(jí)以避免紅外接收中斷動(dòng)輒上百ms的執(zhí)行時(shí)間影響視覺(jué)效果,我們選擇T1用來(lái)作為紅外接收的計(jì)時(shí),按理說(shuō)再用T0設(shè)置成高優(yōu)先級(jí)來(lái)處理LED刷新即可,但是,本例中我們還啟用了矩陣按鍵,而矩陣按鍵的掃描也采用T0而對(duì)紅外中斷實(shí)現(xiàn)嵌套的話,由于按鍵掃描的時(shí)間會(huì)達(dá)到幾百us,這幾百us的延時(shí)則足以使紅外對(duì)碼位的解析產(chǎn)生誤判了。怎么辦呢?是不是會(huì)很自然的想到:再增加一個(gè)定時(shí)器用來(lái)做LED掃描并實(shí)現(xiàn)對(duì)紅外中斷的嵌套,而按鍵掃描和紅外處于相同的低優(yōu)先級(jí)而不能彼此嵌套,按鍵遲后上百ms再響應(yīng)不會(huì)感覺(jué)到問(wèn)題,同樣幾百us的延時(shí)對(duì)紅外起始引導(dǎo)碼的9ms來(lái)說(shuō)也完全可以容忍。那么還有沒(méi)有定時(shí)器可用了呢,好在我們的STC89C52還有一個(gè)定時(shí)器T2(標(biāo)準(zhǔn)的8051是沒(méi)有T2的,它是8052的擴(kuò)充外設(shè),現(xiàn)在絕大多數(shù)的51系列單片機(jī)都是有這個(gè)T2的),于是問(wèn)題解決。此外還有一個(gè)問(wèn)題,就是由于操作液晶的時(shí)候要對(duì)P1.0P1.1進(jìn)行操作,而刷新LED是中斷,優(yōu)先級(jí)是高于液晶的。如果我們當(dāng)前正在操作液晶,對(duì)P1.0P1.1操作了,數(shù)碼管刷新的中斷又來(lái)了,也要對(duì)P1.0P1.1進(jìn)行操作,就會(huì)導(dǎo)致邏輯錯(cuò)誤。雖然這種錯(cuò)誤出現(xiàn)機(jī)率極小,但是邏輯必須要嚴(yán)謹(jǐn),必需避免它。那么當(dāng)我們進(jìn)行液晶操作的時(shí)候,如果數(shù)碼管的定時(shí)中斷來(lái)了,我們?cè)诒敬沃袛嘀芯头艞墝?duì)數(shù)碼管的刷新,不對(duì)那幾個(gè)口線進(jìn)行操作,因?yàn)橐壕У淖x寫(xiě)操作都很快,所以對(duì)實(shí)際顯示效果并沒(méi)有太大的影響。
這部分代碼除了定時(shí)器2的寄存器配置外,其他的內(nèi)容我們之前幾乎都用到過(guò),大家可以通過(guò)分析程序?qū)W明白。而定時(shí)器2的寄存器配置,相信學(xué)到這里的同學(xué)也可以通過(guò)查閱數(shù)據(jù)手冊(cè)自己看明白,這里要求同學(xué)們自學(xué)一下。我直接把代碼貼出來(lái),大家研究一下。
/***********************Lcd1602.h文件程序源代碼*************************/
#ifndef _LCD1602_H
#define _LCD1602_H


#ifndef _LCD1602_C

#endif

void InitLcd1602();
void LcdClearScreen();
void LcdOpenCursor();
void LcdCloseCursor();
void LcdSetCursor(uint8 x, uint8 y);
void LcdShowStr(uint8 x, uint8 y, uint8 *str);
void LcdShowChar(uint8 x, uint8 y, uint8 chr);

#endif
/***********************Lcd1602.c文件程序源代碼*************************/
#define  _LCD1602_C
#include "config.h"
#include "Lcd1602.h"

bit tmpADDR0;  //暫存LED位選譯碼地址0的值
bit tmpADDR1;  //暫存LED位選譯碼地址1的值

/* 暫停LED動(dòng)態(tài)掃描,暫存相關(guān)引腳的值 */
void LedScanPause()
{
    ENLED = 1;
    tmpADDR0 = ADDR0;
    tmpADDR1 = ADDR1;
}
/* 恢復(fù)LED動(dòng)態(tài)掃描,恢復(fù)相關(guān)引腳的值 */
void LedScanContinue()
{
    ADDR0 = tmpADDR0;
    ADDR1 = tmpADDR1;
    ENLED = 0;
}
/* 等待液晶準(zhǔn)備好 */
void LcdWaitReady()
{
    uint8 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ù)檢測(cè)直到其等于0為止
}
/* LCD1602液晶寫(xiě)入一字節(jié)命令,cmd-待寫(xiě)入命令值 */
void LcdWriteCmd(uint8 cmd)
{
    LedScanPause();
    LcdWaitReady();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_DB = cmd;
    LCD1602_E  = 1;
    LCD1602_E  = 0;
    LedScanContinue();
}
/* LCD1602液晶寫(xiě)入一字節(jié)數(shù)據(jù),dat-待寫(xiě)入數(shù)據(jù)值 */
void LcdWriteDat(uint8 dat)
{
    LedScanPause();
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DB = dat;
    LCD1602_E  = 1;
    LCD1602_E  = 0;
    LedScanContinue();
}
/* 清屏 */
void LcdClearScreen()
{
    LcdWriteCmd(0x01);
}
/* 打開(kāi)光標(biāo)的閃爍效果 */
void LcdOpenCursor()
{
    LcdWriteCmd(0x0F);
}
/* 關(guān)閉光標(biāo)顯示 */
void LcdCloseCursor()
{
    LcdWriteCmd(0x0C);
}
/* 設(shè)置顯示RAM起始地址,亦即光標(biāo)位置,(x,y)-對(duì)應(yīng)屏幕上的字符坐標(biāo) */
void LcdSetCursor(uint8 x, uint8 y)
{
    uint8 addr;

    if (y == 0)  //由輸入的屏幕坐標(biāo)計(jì)算顯示RAM的地址
        addr = 0x00 + x;  //第一行字符地址從0x00起始
    else
        addr = 0x40 + x;  //第二行字符地址從0x40起始
    LcdWriteCmd(addr | 0x80);  //設(shè)置RAM地址
}
/* 在液晶上顯示字符串,(x,y)-對(duì)應(yīng)屏幕上的起始坐標(biāo),str-字符串指針 */
void LcdShowStr(uint8 x, uint8 y, uint8 *str)
{
    LcdSetCursor(x, y);   //設(shè)置起始地址
    while (*str != '\0')  //連續(xù)寫(xiě)入字符串?dāng)?shù)據(jù),直到檢測(cè)到結(jié)束符
    {
        LcdWriteDat(*str++);
    }
}
/* 在液晶上顯示一個(gè)字符,(x,y)-對(duì)應(yīng)屏幕上的起始坐標(biāo),chr-字符ASCII*/
void LcdShowChar(uint8 x, uint8 y, uint8 chr)
{
    LcdSetCursor(x, y);  //設(shè)置起始地址
    LcdWriteDat(chr);    //寫(xiě)入ASCII字符
}
/* 初始化1602液晶 */
void InitLcd1602()
{
    LcdWriteCmd(0x38);  //16*2顯示,5*7點(diǎn)陣,8位數(shù)據(jù)接口
    LcdWriteCmd(0x0C);  //顯示器開(kāi),光標(biāo)關(guān)閉
    LcdWriteCmd(0x06);  //文字不動(dòng),地址自動(dòng)+1
    LcdWriteCmd(0x01);  //清屏
}
/***********************LedBuzzer.h文件程序源代碼*************************/
#ifndef _LED_BUZZER_H
#define _LED_BUZZER_H

struct sLedBuff {  //LED顯示緩沖區(qū)結(jié)構(gòu)
    uint8 array[8];   //點(diǎn)陣緩沖區(qū)
    uint8 number[6];  //數(shù)碼管緩沖區(qū)
    uint8 alone;      //獨(dú)立LED緩沖區(qū)
};

#ifndef _LED_BUZZER_C
extern bit staBuzzer;
extern struct sLedBuff ledBuff;
#endif

void InitLed();
void FlowingLight();
void ShowLedNumber(uint8 index, uint8 num, uint8 point);
void ShowLedArray(uint8 *ptr);

#endif
/***********************LedBuzzer.c文件程序源代碼*************************/
#define  _LED_BUZZER_C
#include "config.h"
#include "LedBuzzer.h"

uint8 code LedChar[] = {  //數(shù)碼管顯示字符轉(zhuǎn)換表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};

bit staBuzzer = 0; //蜂鳴器狀態(tài)控制位,1-鳴叫、0-關(guān)閉
struct sLedBuff ledBuff; //LED顯示緩沖區(qū),默認(rèn)初值全0,正好達(dá)到上電全亮的效果

/* LED初始化函數(shù),初始化IO、配置定時(shí)器 */
void InitLed()
{
    //初始化IO
    P0 = 0xFF;
    ENLED = 0;
    //配置T2作為動(dòng)態(tài)掃描定時(shí)
    T2CON = 0x00;  //配置T2工作在16位自動(dòng)重載定時(shí)器模式
    RCAP2H = ((65536-SYS_MCLK/1500)>>8);  //配置重載值,每秒產(chǎn)生1500次中斷,
    RCAP2L = (65536-SYS_MCLK/1500);       //以使刷新率達(dá)到100Hz無(wú)閃爍的效果
    TH2 = RCAP2H;  //設(shè)置初值等于重載值
    TL2 = RCAP2L;
    ET2 = 1;       //使能T2中斷
    PT2 = 1;       //設(shè)置T2中斷為高優(yōu)先級(jí)
    TR2 = 1;       //啟動(dòng)T2
}
/* 流水燈實(shí)現(xiàn)函數(shù),間隔調(diào)用實(shí)現(xiàn)流動(dòng)效果 */
void FlowingLight()
{
    static uint8 i = 0;
    const uint8 code tab[] = {  //流動(dòng)表
        0x7F, 0x3F, 0x1F, 0x0F, 0x87, 0xC3, 0xE1, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF
    };

    ledBuff.alone = tab[ i];   //表中對(duì)應(yīng)值送到獨(dú)立LED的顯示緩沖區(qū)
    if (i < (sizeof(tab)-1))  //索引遞增循環(huán),遍歷整個(gè)流動(dòng)表
        i++;
    else
        i = 0;
}
/* 數(shù)碼管上顯示一位數(shù)字,index-數(shù)碼管位索引(從右到左對(duì)應(yīng)05),
**     num-待顯示的數(shù)字,point-代表是否顯示此位上的小數(shù)點(diǎn) */
void ShowLedNumber(uint8 index, uint8 num, uint8 point)
{
    ledBuff.number[ index] = LedChar[num];  //輸入數(shù)字轉(zhuǎn)換為數(shù)碼管字符0F
    if (point != 0)
    {
        ledBuff.number[ index] &= 0x7F;  //point不為0時(shí)點(diǎn)亮當(dāng)前位的小數(shù)點(diǎn)
    }
}
/* 點(diǎn)陣上顯示一幀圖片,ptr-待顯示圖片指針 */
void ShowLedArray(uint8 *ptr)
{
    uint8 i;

    for (i=0; i<sizeof(ledBuff.array); i++)
    {
        ledBuff.array[ i] = *ptr++;
    }
}
/* T2中斷服務(wù)函數(shù),LED動(dòng)態(tài)掃描、蜂鳴器控制 */
void InterruptTimer2() interrupt 5
{
    static uint8 i = 0;  //LED位選索引

    TF2 = 0;  //清零T2中斷標(biāo)志
    //全部LED動(dòng)態(tài)掃描顯示
    if (ENLED == 0)  //LED使能時(shí)才進(jìn)行動(dòng)態(tài)掃描
    {
        P0 = 0xFF;                       //關(guān)閉所有段選位,顯示消隱
        P1 = (P1 & 0xF0) | i;            //位選索引值賦值到P1口低4
        P0 = *((uint8 data*)&ledBuff+i); //緩沖區(qū)中索引位置的數(shù)據(jù)送到P0
        if (i < (sizeof(ledBuff)-1))     //索引遞增循環(huán),遍歷整個(gè)緩沖區(qū)
            i++;
        else
            i = 0;
    }
    //由蜂鳴器狀態(tài)位控制蜂鳴器
    if (staBuzzer == 1)
        BUZZER = ~BUZZER;  //蜂鳴器鳴叫
    else
        BUZZER = 1;        //蜂鳴器靜音
}
第二個(gè)部分,我們就要調(diào)試時(shí)鐘DS1302的程序代碼了,這部分代碼,我們首先可以把前邊在1602液晶上顯示時(shí)間的代碼拿過(guò)來(lái)當(dāng)作調(diào)試手段,當(dāng)可以成功顯示到1602液晶上后,我們就可以寫(xiě)進(jìn)去一個(gè)初始時(shí)間,再讀出來(lái),把星期顯示在LED點(diǎn)陣上,時(shí)間顯示到數(shù)碼管上,日期顯示到液晶上,并且讓流水燈流動(dòng)起來(lái)。這塊功能調(diào)試好以后,就是一個(gè)簡(jiǎn)單的電子鐘了。
/***********************DS1302.h文件程序源代碼*************************/
#ifndef _DS1302_H
#define _DS1302_H

struct sTime {  //日期時(shí)間結(jié)構(gòu)
    uint16 year; //
    uint8 mon;   //
    uint8 day;   //
    uint8 hour;  //時(shí)
    uint8 min;   //
    uint8 sec;   //
    uint8 week;  //星期
};

#ifndef _DS1302_C

#endif

void InitDS1302();
void GetRealTime(struct sTime *time);
void SetRealTime(struct sTime *time);

#endif
/***********************Ds1302.c文件程序源代碼*************************/
#define  _DS1302_C
#include "config.h"
#include "DS1302.h"

/* 發(fā)送一個(gè)字節(jié)到DS1302通信總線上 */
void DS1302ByteWrite(uint8 dat)
{
    uint8 mask;

    for (mask=0x01; mask!=0; mask<<=1)  //低位在前,逐位移出
    {
        if ((mask&dat) != 0) //首先輸出該位數(shù)據(jù)
            DS1302_IO = 1;
        else
            DS1302_IO = 0;
        DS1302_CK = 1;       //然后拉高時(shí)鐘
        DS1302_CK = 0;       //再拉低時(shí)鐘,完成一個(gè)位的操作
    }
    DS1302_IO = 1;           //最后確保釋放IO引腳
}
/* DS1302通信總線上讀取一個(gè)字節(jié) */
uint8 DS1302ByteRead()
{
    uint8 mask;
    uint8 dat = 0;

    for (mask=0x01; mask!=0; mask<<=1)  //低位在前,逐位讀取
    {
        if (DS1302_IO != 0)  //首先讀取此時(shí)的IO引腳,并設(shè)置dat中的對(duì)應(yīng)位
        {
            dat |= mask;
        }
        DS1302_CK = 1;       //然后拉高時(shí)鐘
        DS1302_CK = 0;       //再拉低時(shí)鐘,完成一個(gè)位的操作
    }
    return dat;              //最后返回讀到的字節(jié)數(shù)據(jù)
}
/* 用單次寫(xiě)操作向某一寄存器寫(xiě)入一個(gè)字節(jié),reg-寄存器地址,dat-待寫(xiě)入字節(jié) */
void DS1302SingleWrite(uint8 reg, uint8 dat)
{
    DS1302_CE = 1;                   //使能片選信號(hào)
    DS1302ByteWrite((reg<<1)|0x80);  //發(fā)送寫(xiě)寄存器指令
    DS1302ByteWrite(dat);            //寫(xiě)入字節(jié)數(shù)據(jù)
    DS1302_CE = 0;                   //除能片選信號(hào)
}
/* 用單次讀操作從某一寄存器讀取一個(gè)字節(jié),reg-寄存器地址,返回值-讀到的字節(jié) */
uint8 DS1302SingleRead(uint8 reg)
{
    uint8 dat;

    DS1302_CE = 1;                   //使能片選信號(hào)
    DS1302ByteWrite((reg<<1)|0x81);  //發(fā)送讀寄存器指令
    dat = DS1302ByteRead();          //讀取字節(jié)數(shù)據(jù)
    DS1302_CE = 0;                   //除能片選信號(hào)

    return dat;
}
/* 用突發(fā)模式連續(xù)寫(xiě)入8個(gè)寄存器數(shù)據(jù),dat-待寫(xiě)入數(shù)據(jù)指針 */
void DS1302BurstWrite(uint8 *dat)
{
    uint8 i;

    DS1302_CE = 1;
    DS1302ByteWrite(0xBE);  //發(fā)送突發(fā)寫(xiě)寄存器指令
    for (i=0; i<8; i++)     //連續(xù)寫(xiě)入8字節(jié)數(shù)據(jù)
    {
        DS1302ByteWrite(dat[ i]);
    }
    DS1302_CE = 0;
}
/* 用突發(fā)模式連續(xù)讀取8個(gè)寄存器的數(shù)據(jù),dat-讀取數(shù)據(jù)的接收指針 */
void DS1302BurstRead(uint8 *dat)
{
    uint8 i;

    DS1302_CE = 1;
    DS1302ByteWrite(0xBF);  //發(fā)送突發(fā)讀寄存器指令
    for (i=0; i<8; i++)     //連續(xù)讀取8個(gè)字節(jié)
    {
        dat[ i] = DS1302ByteRead();
    }
    DS1302_CE = 0;
}
/* 獲取實(shí)時(shí)時(shí)間,即讀取DS1302當(dāng)前時(shí)間并轉(zhuǎn)換為時(shí)間結(jié)構(gòu)體格式 */
void GetRealTime(struct sTime *time)
{
    uint8 buf[8];

    DS1302BurstRead(buf);
    time->year = buf[6] + 0x2000;
    time->mon  = buf[4];
    time->day  = buf[3];
    time->hour = buf[2];
    time->min  = buf[1];
    time->sec  = buf[0];
    time->week = buf[5];
}
/* 設(shè)定實(shí)時(shí)時(shí)間,時(shí)間結(jié)構(gòu)體格式的設(shè)定時(shí)間轉(zhuǎn)換為數(shù)組并寫(xiě)入DS1302 */
void SetRealTime(struct sTime *time)
{
    uint8 buf[8];

    buf[7] = 0;
    buf[6] = time->year;
    buf[5] = time->week;
    buf[4] = time->mon;
    buf[3] = time->day;
    buf[2] = time->hour;
    buf[1] = time->min;
    buf[0] = time->sec;
    DS1302BurstWrite(buf);
}
/* DS1302初始化,如發(fā)生掉電則重新設(shè)置初始時(shí)間 */
void InitDS1302()
{
    uint8 dat;
    struct sTime code InitTime[] = {  //默認(rèn)初始值:2014-01-01 12:30:00 星期3
        0x2014,0x01,0x01, 0x12,0x30,0x00, 0x03
    };

    DS1302_CE = 0;  //初始化DS1302通信引腳
    DS1302_CK = 0;
    dat = DS1302SingleRead(0);  //讀取秒寄存器
    if ((dat & 0x80) != 0)      //由秒寄存器最高位CH的值判斷DS1302是否已停止
    {
        DS1302SingleWrite(7, 0x00);  //撤銷寫(xiě)保護(hù)以允許寫(xiě)入數(shù)據(jù)
        SetRealTime(&InitTime);      //設(shè)置DS1302為默認(rèn)的初始時(shí)間
    }
}
時(shí)鐘顯示調(diào)試完畢后,下一步就可以開(kāi)始編寫(xiě)按鍵代碼,使用按鍵可以調(diào)整時(shí)鐘,調(diào)整鬧鐘的時(shí)間。當(dāng)然,我們?cè)谡{(diào)試按鍵底層驅(qū)動(dòng)的時(shí)候,不一定要把所有想要的功能都羅列出來(lái),可以先進(jìn)行按鍵底層功能程序的調(diào)試,按下按鍵讓蜂鳴器響一下,或者閃爍個(gè)小燈等都可以用來(lái)檢驗(yàn)按鍵底層代碼工作的正確性。隨著我們程序量的加大,有些功能也可以進(jìn)行綜合了,可以在Time.c文件中和main.c文件中添加程序了,一邊添加一邊調(diào)試,而不是把所有的程序代碼都寫(xiě)完后,像無(wú)頭蒼蠅一樣到處找漏洞。

/***********************keyboard.h文件程序源代碼*************************/
#ifndef _KEY_BOARD_H
#define _KEY_BOARD_H


#ifndef _KEY_BOARD_C

#endif

void KeyScan();
void KeyDriver();
void KeyAction(uint8 keycode);

#endif
/***********************keyboard.c文件程序源代碼*************************/
#define  _KEY_BOARD_C
#include "config.h"
#include "keyboard.h"
#include "Time.h"
#include "main.h"

const uint8 code KeyCodeMap[4][4] = {  //矩陣按鍵到PC標(biāo)準(zhǔn)鍵碼的映射表
    { '1',  '2',  '3', 0x26 },  //數(shù)字鍵1、數(shù)字鍵2、數(shù)字鍵3、向上鍵
    { '4',  '5',  '6', 0x25 },  //數(shù)字鍵4、數(shù)字鍵5、數(shù)字鍵6、向左鍵
    { '7',  '8',  '9', 0x28 },  //數(shù)字鍵7、數(shù)字鍵8、數(shù)字鍵9、向下鍵
    { '0', 0x1B, 0x0D, 0x27 }   //數(shù)字鍵0、ESC鍵、  回車(chē)鍵、 向右鍵
};
uint8 pdata KeySta[4][4] = {  //全部矩陣按鍵的當(dāng)前狀態(tài)
    {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
};

/* 按鍵動(dòng)作函數(shù),根據(jù)鍵碼執(zhí)行相應(yīng)的操作 */
void KeyAction(uint8 keycode)
{
    if  ((keycode>='0') && (keycode<='9'))  //數(shù)字鍵輸入當(dāng)前位設(shè)定值
    {
        InputSetNumber(keycode);
    }
    else if (keycode == 0x25)  //向左鍵,向左切換設(shè)置位
    {
        SetLeftShift();
    }
    else if (keycode == 0x27)  //向右鍵,向右切換設(shè)置位
    {
        SetRightShift();
    }
    else if (keycode == 0x0D)  //回車(chē)鍵,切換運(yùn)行狀態(tài)/保存設(shè)置
    {
        SwitchSystemSta();
    }
    else if (keycode == 0x1B)  //Esc鍵,靜音/取消當(dāng)前設(shè)置
    {
        if (staSystem == E_NORMAL) //處于正常運(yùn)行狀態(tài)時(shí)鬧鈴靜音
        {
            staMute = 1;
        }
        else                       //處于設(shè)置狀態(tài)時(shí)退出設(shè)置
        {
            CancelCurSet();
        }
    }
}
/* 按鍵驅(qū)動(dòng)函數(shù),檢測(cè)按鍵動(dòng)作,調(diào)度相應(yīng)動(dòng)作函數(shù),需在主循環(huán)中調(diào)用 */
void KeyDriver()
{
    uint8 i, j;
    static uint8 pdata backup[4][4] = {  //按鍵值備份,保存前一次的值
        {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
    };
   
    for (i=0; i<4; i++)  //循環(huán)掃描4*4的矩陣按鍵
    {
        for (j=0; j<4; j++)
        {
            if (backup[ i][j] != KeySta[ i][j])    //檢測(cè)按鍵動(dòng)作
            {
                if (backup[ i][j] != 0)           //按鍵按下時(shí)執(zhí)行動(dòng)作
                {
                    KeyAction(KeyCodeMap[ i][j]); //調(diào)用按鍵動(dòng)作函數(shù)
                }
                backup[ i][j] = KeySta[  i][j];     //刷新前一次的備份值
            }
        }
    }
}
/* 按鍵掃描函數(shù),需在定時(shí)中斷中調(diào)用,推薦調(diào)用間隔1ms */
void KeyScan()
{
    uint8 i;
    static uint8 keyout = 0;   //矩陣按鍵掃描輸出索引
    static uint8 keybuf[4][4] = {  //矩陣按鍵掃描緩沖區(qū)
        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF},
        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF}
    };

    //將一行的4個(gè)按鍵值移入緩沖區(qū)
    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
    //消抖后更新按鍵狀態(tài)
    for (i=0; i<4; i++)  //每行4個(gè)按鍵,所以循環(huán)4次
    {
        if ((keybuf[keyout][ i] & 0x0F) == 0x00)
        {   //連續(xù)4次掃描值為0,即4*4ms內(nèi)都是按下?tīng)顟B(tài)時(shí),可認(rèn)為按鍵已穩(wěn)定的按下
            KeySta[keyout][ i] = 0;
        }
        else if ((keybuf[keyout][ i] & 0x0F) == 0x0F)
        {   //連續(xù)4次掃描值為1,即4*4ms內(nèi)都是彈起狀態(tài)時(shí),可認(rèn)為按鍵已穩(wěn)定的彈起
            KeySta[keyout][ i] = 1;
        }
    }
    //執(zhí)行下一次的掃描輸出
    keyout++;        //輸出索引遞增
    keyout &= 0x03;  //索引值加到4即歸零
    switch (keyout)  //根據(jù)索引值,釋放當(dāng)前輸出引腳,拉低下次的輸出引腳
    {
        case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
        case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
        case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
        case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
        default: break;
    }
}
   按鍵程序調(diào)試完畢后,下一步毫無(wú)疑問(wèn)就是紅外的代碼了。紅外所要實(shí)現(xiàn)的功能是和按鍵完全一樣的,但是如果說(shuō)我們把紅外按鍵的代碼解析出來(lái)后,再去做相應(yīng)的操作顯得有點(diǎn)多余了。我們的處理方式是,把紅外的按鍵代碼解析出來(lái),和我們板載按鍵進(jìn)行映射對(duì)應(yīng)關(guān)系,不同的紅外按鍵映射為板子上的不同的板載按鍵值就可以了,這樣只需要寫(xiě)一套按鍵驅(qū)動(dòng)程序,紅外的代碼只做解析和映射功能即可。
/***********************Infrared.h文件程序源代碼*************************/
#ifndef _INFRARED_H
#define _INFRARED_H


#ifndef _INFRARED_C

#endif

void InitInfrared();
void InfraredDriver();

#endif
/***********************Infrared.c文件程序源代碼*************************/
#define  _INFRARED_C
#include "config.h"
#include "Infrared.h"
#include "keyboard.h"

const uint8 code IrCodeMap[][2] = {  //紅外鍵碼到標(biāo)準(zhǔn)PC鍵碼的映射表
    {0x45, 0x00}, {0x46, 0x00}, {0x47, 0x1B}, //開(kāi)關(guān)->無(wú)  Mode->無(wú)   靜音->ESC
    {0x44, 0x00}, {0x40, 0x25}, {0x43, 0x27}, //播放->無(wú)  后退->向左 前進(jìn)->向右
    {0x07, 0x00}, {0x15, 0x28}, {0x09, 0x26}, // EQ->無(wú)   減號(hào)->向下 加號(hào)->向上
    {0x16, 0x30}, {0x19, 0x1B}, {0x0D, 0x0D}, //'0'->'0'  箭頭->ESC  U/SD->回車(chē)
    {0x0C, 0x31}, {0x18, 0x32}, {0x5E, 0x33}, //'1'->'1'  '2'->'2'   '3'->'3'
    {0x08, 0x34}, {0x1C, 0x35}, {0x5A, 0x36}, //'4'->'4'  '5'->'5'   '6'->'6'
  {0x42, 0x37}, {0x52, 0x38}, {0x4A, 0x39}, //'7'->'7'  '6'->'8'   '9'->'9'
};

bit irflag = 0;  //紅外接收標(biāo)志,收到一幀正確數(shù)據(jù)后置1
uint8 ircode[4];  //紅外代碼接收緩沖區(qū)

/* 紅外接收驅(qū)動(dòng),檢測(cè)接收到的鍵碼,調(diào)度相應(yīng)動(dòng)作函數(shù) */
void InfraredDriver()
{
    uint8 i;
   
    if (irflag)
    {
        irflag = 0;
        for (i=0; i<sizeof(IrCodeMap)/sizeof(IrCodeMap[0]); i++) //遍歷映射表
        {
            if (ircode[2] == IrCodeMap[ i][0])  //在表中找到當(dāng)前接收的鍵碼后,
            {                                  //用對(duì)應(yīng)的映射碼執(zhí)行函數(shù)調(diào)度,
                KeyAction(IrCodeMap[ i][1]);    //直接調(diào)用按鍵動(dòng)作函數(shù)即可。
                break;
            }
        }
    }
}
/* 初始化紅外接收功能 */
void InitInfrared()
{
    IR_INPUT = 1;  //確保紅外接收引腳被釋放
    TMOD &= 0x0F;  //清零T1的控制位
    TMOD |= 0x10;  //配置T1為模式1
    TR1 = 0;       //停止T1計(jì)數(shù)
            ET1 = 0;       //禁止T1中斷
    IT1 = 1;       //設(shè)置INT1為負(fù)邊沿觸發(fā)
    EX1 = 1;       //使能INT1中斷
}
/* 獲取當(dāng)前高電平的持續(xù)時(shí)間 */
uint16 GetHighTime()
{
    TH1 = 0;  //清零T1計(jì)數(shù)初值
    TL1 = 0;
    TR1 = 1;  //啟動(dòng)T1計(jì)數(shù)
    while (IR_INPUT)  //紅外輸入引腳為1時(shí)循環(huán)檢測(cè)等待,變?yōu)?時(shí)則結(jié)束本循環(huán)
    {
        if (TH1 >= 0x40)
        {            //當(dāng)T1計(jì)數(shù)值大于0x4000,即高電平持續(xù)時(shí)間超過(guò)約18ms時(shí),
            break;   //強(qiáng)制退出循環(huán),是為了避免信號(hào)異常時(shí),程序假死在這里。
        }
    }
    TR1 = 0;  //停止T1計(jì)數(shù)

    return (TH1*256 + TL1);  //T1計(jì)數(shù)值合成為16bit整型數(shù),并返回該數(shù)
}
/* 獲取當(dāng)前低電平的持續(xù)時(shí)間 */
uint16 GetLowTime()
{
    TH1 = 0;  //清零T1計(jì)數(shù)初值
    TL1 = 0;
    TR1 = 1;  //啟動(dòng)T1計(jì)數(shù)
    while (!IR_INPUT)  //紅外輸入引腳為0時(shí)循環(huán)檢測(cè)等待,變?yōu)?時(shí)則結(jié)束本循環(huán)
    {
        if (TH1 >= 0x40)
        {            //當(dāng)T1計(jì)數(shù)值大于0x4000,即低電平持續(xù)時(shí)間超過(guò)約18ms時(shí),
            break;   //強(qiáng)制退出循環(huán),是為了避免信號(hào)異常時(shí),程序假死在這里。
        }
    }
    TR1 = 0;  //停止T1計(jì)數(shù)

    return (TH1*256 + TL1);  //T1計(jì)數(shù)值合成為16bit整型數(shù),并返回該數(shù)
}
/* INT1中斷服務(wù)函數(shù),執(zhí)行紅外接收及解碼 */
void EXINT1_ISR() interrupt 2
{
    uint8 i, j;
    uint8 byt;
    uint16 time;
   
    //接收并判定引導(dǎo)碼的9ms低電平
    time = GetLowTime();
    if ((time<7833) || (time>8755))  //時(shí)間判定范圍為8.5~9.5ms,
    {                                //超過(guò)此范圍則說(shuō)明為誤碼,直接退出
        IE1 = 0;   //退出前清零INT1中斷標(biāo)志
        return;
    }
    //接收并判定引導(dǎo)碼的4.5ms高電平
    time = GetHighTime();
    if ((time<3686) || (time>4608))  //時(shí)間判定范圍為4.0~5.0ms,
    {                                //超過(guò)此范圍則說(shuō)明為誤碼,直接退出
        IE1 = 0;
        return;
    }
    //接收并判定后續(xù)的4字節(jié)數(shù)據(jù)
    for (i=0; i<4; i++)  //循環(huán)接收4個(gè)字節(jié)
    {
        for (j=0; j<8; j++)  //循環(huán)接收判定每字節(jié)的8個(gè)bit
        {
            //接收判定每bit的560us低電平
            time = GetLowTime();
            if ((time<313) || (time>718)) //時(shí)間判定范圍為340~780us,
            {                             //超過(guò)此范圍則說(shuō)明為誤碼,直接退出
                IE1 = 0;
                return;
            }
            //接收每bit高電平時(shí)間,判定該bit的值
            time = GetHighTime();
            if ((time>313) && (time<718)) //時(shí)間判定范圍為340~780us,
            {                             //在此范圍內(nèi)說(shuō)明該bit值為0
                byt >>= 1;   //因低位在先,所以數(shù)據(jù)右移,高位為0
            }
            else if ((time>1345) && (time<1751)) //時(shí)間范圍1460~1900us,
            {                                    //在此范圍內(nèi)說(shuō)明該bit值為1
                byt >>= 1;   //因低位在先,所以數(shù)據(jù)右移,
                byt |= 0x80; //高位置1
            }
            else  //不在上述范圍內(nèi)則說(shuō)明為誤碼,直接退出
            {
                IE1 = 0;
                return;
            }
        }
        ircode[ i] = byt;  //接收完一個(gè)字節(jié)后保存到緩沖區(qū)
    }
    irflag = 1;  //接收完畢后設(shè)置標(biāo)志
    IE1 = 0;     //退出前清零INT1中斷標(biāo)志
}
   這一切底層的驅(qū)動(dòng)完成之后,我們就可以整理調(diào)試main.c和Time.c內(nèi)的功能代碼了。一邊添加功能一邊調(diào)試,把最終的功能代碼調(diào)試出來(lái),在KST-51開(kāi)發(fā)板上做驗(yàn)證。這一切都完事之后,我們可以添加一項(xiàng)新功能,就是DS18B20溫度傳感器顯示,這個(gè)是個(gè)獨(dú)立功能,直接寫(xiě)好代碼,添加進(jìn)去就可以了。
/***********************DS18B20.h文件程序源代碼*************************/
#ifndef _DS18B20_H
#define _DS18B20_H


#ifndef _DS18B20_C

#endif

bit Start18B20();
bit Get18B20Temp(int16 *temp);

#endif
/***********************DS18B20.c文件程序源代碼*************************/
#define  _DS18B20_C
#include "config.h"
#include "DS18B20.h"

/* 軟件延時(shí)函數(shù),延時(shí)時(shí)間(t*10)us */
void DelayX10us(uint8 t)
{
    do {
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
    } while (--t);
}
/* 復(fù)位總線,獲取存在脈沖,以啟動(dòng)一次讀寫(xiě)操作 */
bit Get18B20Ack()
{
    bit ack;
   
    EA = 0;   //禁止總中斷
    IO_18B20 = 0;     //產(chǎn)生500us復(fù)位脈沖
    DelayX10us(50);
    IO_18B20 = 1;
    DelayX10us(6);    //延時(shí)60us
    ack = IO_18B20;   //讀取存在脈沖
    while(!IO_18B20); //等待存在脈沖結(jié)束
    EA = 1;   //重新使能總中斷
   
    return ack;
}
/* 向DS18B20寫(xiě)入一個(gè)字節(jié),dat-待寫(xiě)入字節(jié) */
void Write18B20(uint8 dat)
{
    uint8 mask;
   
    EA = 0;   //禁止總中斷
            for (mask=0x01; mask!=0; mask<<=1)  //低位在先,依次移出8個(gè)bit
    {
                        IO_18B20 = 0;         //產(chǎn)生2us低電平脈沖
                        _nop_();
                        _nop_();
        if ((mask&dat) == 0)  //輸出該bit值
            IO_18B20 = 0;
        else
            IO_18B20 = 1;
                        DelayX10us(6);        //延時(shí)60us
                        IO_18B20 = 1;         //拉高通信引腳
            }
    EA = 1;   //重新使能總中斷
}
/* 從DS18B20讀取一個(gè)字節(jié),返回值-讀到的字節(jié) */
uint8 Read18B20()
{
    uint8 dat;
    uint8 mask;
   
    EA = 0;   //禁止總中斷
            for (mask=0x01; mask!=0; mask<<=1)  //低位在先,依次采集8個(gè)bit
    {
                        IO_18B20 = 0;         //產(chǎn)生2us低電平脈沖
                        _nop_();
                        _nop_();
                        IO_18B20 = 1;         //結(jié)束低電平脈沖,等待18B20輸出數(shù)據(jù)
                        _nop_();              //延時(shí)2us
                        _nop_();
                        if (!IO_18B20)        //讀取通信引腳上的值
                                dat &= ~mask;
        else
            dat |= mask;
                        DelayX10us(6);        //再延時(shí)60us
            }
    EA = 1;   //重新使能總中斷

    return dat;
}
/* 啟動(dòng)一次18B20溫度轉(zhuǎn)換,返回值-表示是否啟動(dòng)成功 */
bit Start18B20()
{
    bit ack;
   
    ack = Get18B20Ack();   //執(zhí)行總線復(fù)位,并獲取18B20應(yīng)答
    if (ack == 0)          //如18B20正確應(yīng)答,則啟動(dòng)一次轉(zhuǎn)換
    {
            Write18B20(0xCC);  //跳過(guò)ROM操作
            Write18B20(0x44);  //啟動(dòng)一次溫度轉(zhuǎn)換
    }
    return ~ack;   //ack==0表示操作成功,所以返回值對(duì)其取反
}
/* 讀取DS18B20轉(zhuǎn)換的溫度值,返回值-表示是否讀取成功 */
bit Get18B20Temp(int16 *temp)
{
    bit ack;
    uint8 LSB, MSB; //16bit溫度值的低字節(jié)和高字節(jié)
   
    ack = Get18B20Ack();    //執(zhí)行總線復(fù)位,并獲取18B20應(yīng)答
    if (ack == 0)           //如18B20正確應(yīng)答,則讀取溫度值
    {
                Write18B20(0xCC);   //跳過(guò)ROM操作
                Write18B20(0xBE);   //發(fā)送讀命令
                LSB = Read18B20();  //讀溫度值的低字節(jié)
                MSB = Read18B20();  //讀溫度值的高字節(jié)
        *temp = ((int16)MSB << 8) + LSB;  //合成為16bit整型數(shù)
    }
            return ~ack;  //ack==0表示操作應(yīng)答,所以返回值為其取反值
}
/***********************Time.h文件程序源代碼*************************/
#ifndef _TIME_H
#define _TIME_H


#ifndef _TIME_C
extern bit staMute;
#endif

void RefreshTime();
void RefreshDate(uint8 ops);
void RefreshAlarm();
void AlarmMonitor();
void SwitchSystemSta();
void CancelCurSet();
void SetRightShift();
void SetLeftShift();
void InputSetNumber(uint8 ascii);

#endif
/***********************Time.c文件程序源代碼*************************/
#define  _TIME_C
#include "config.h"
#include "DS1302.h"
#include "LedBuzzer.h"
#include "Lcd1602.h"
#include "Time.h"
#include "main.h"

uint8 code WeekMod[] = {  //星期X字符圖片表
    0xFF, 0x99, 0x00, 0x00, 0x00, 0x81, 0xC3, 0xE7,  //星期日(紅心)
    0xEF, 0xE7, 0xE3, 0xE7, 0xE7, 0xE7, 0xE7, 0xC3,  //星期1
    0xC3, 0x81, 0x9D, 0x87, 0xC3, 0xF9, 0xC1, 0x81,  //星期2
    0xC3, 0x81, 0x9D, 0xC7, 0xC7, 0x9D, 0x81, 0xC3,  //星期3
    0xCF, 0xC7, 0xC3, 0xC9, 0xC9, 0x81, 0xCF, 0xCF,  //星期4
    0x81, 0xC1, 0xF9, 0xC3, 0x87, 0x9D, 0x81, 0xC3,  //星期5
    0xC3, 0x81, 0xF9, 0xC3, 0x81, 0x99, 0x81, 0xC3,  //星期6
};

bit staMute = 0;  //靜音標(biāo)志位
uint8 AlarmHour = 0x07;  //鬧鐘時(shí)間的小時(shí)數(shù)
uint8 AlarmMin  = 0x30;  //鬧鐘時(shí)間的分鐘數(shù)
struct sTime CurTime;    //當(dāng)前日期時(shí)間

uint8 SetIndex = 0;  //設(shè)置位索引
uint8 pdata SetAlarmHour;    //鬧鐘小時(shí)數(shù)設(shè)置緩沖
uint8 pdata SetAlarmMin;     //鬧鐘分鐘數(shù)設(shè)置緩沖
struct sTime pdata SetTime;  //日期時(shí)間設(shè)置緩沖區(qū)

/* 獲取當(dāng)前日期時(shí)間,并刷新時(shí)間和星期的顯示 */
void RefreshTime()
{
    GetRealTime(&CurTime);                  //獲取當(dāng)前日期時(shí)間
    ShowLedNumber(5, CurTime.hour>>4, 0);   //時(shí)
    ShowLedNumber(4, CurTime.hour&0xF,1);
    ShowLedNumber(3, CurTime.min>>4,  0);   //分
    ShowLedNumber(2, CurTime.min&0xF, 1);
    ShowLedNumber(1, CurTime.sec>>4,  0);   //秒
    ShowLedNumber(0, CurTime.sec&0xF, 0);
    ShowLedArray(WeekMod + CurTime.week*8); //星期
}
/* 日期刷新函數(shù),ops-刷新選項(xiàng):為0時(shí)只當(dāng)日期變化才刷新,非0則立即刷新 */
void RefreshDate(uint8 ops)
{
    uint8 pdata str[12];
    static uint8 backup = 0;
   
    if ((backup!=CurTime.day) || (ops!=0))
    {
        str[0] = ((CurTime.year>>12) & 0xF) + '0';  //4位數(shù)年份
        str[1] = ((CurTime.year>>8) & 0xF) + '0';
        str[2] = ((CurTime.year>>4) & 0xF) + '0';
        str[3] = (CurTime.year & 0xF) + '0';
        str[4] = '-';                        //分隔符
        str[5] = (CurTime.mon >> 4) + '0';   //月份
        str[6] = (CurTime.mon & 0xF) + '0';
        str[7] = '-';                        //分隔符
        str[8] = (CurTime.day >> 4) + '0';   //日期
        str[9] = (CurTime.day & 0xF) + '0';
        str[10] = '\0';         //字符串結(jié)束符
        LcdShowStr(0, 0, str);  //顯示到液晶上
        backup = CurTime.day;   //刷新上次日期值
    }
}
/* 刷新鬧鐘時(shí)間的顯示 */
void RefreshAlarm()
{
    uint8 pdata str[8];
   
    LcdShowStr(0, 1, "Alarm at ");     //顯示提示標(biāo)題
    str[0] = (AlarmHour >> 4) + '0';   //鬧鐘小時(shí)數(shù)
    str[1] = (AlarmHour & 0xF) + '0';
    str[2] = ':';                      //分隔符
    str[3] = (AlarmMin >> 4) + '0';    //鬧鐘分鐘數(shù)
    str[4] = (AlarmMin & 0xF) + '0';
    str[5] = '\0';                     //字符串結(jié)束符
    LcdShowStr(9, 1, str);             //顯示到液晶上
}
/* 鬧鐘監(jiān)控函數(shù),抵達(dá)設(shè)定的鬧鐘時(shí)間時(shí)執(zhí)行鬧鈴 */
void AlarmMonitor()
{
    if ((CurTime.hour==AlarmHour) && (CurTime.min==AlarmMin)) //檢查時(shí)間匹配
    {
        if (!staMute)  //檢查是否靜音
            staBuzzer = ~staBuzzer;  //實(shí)現(xiàn)蜂鳴器斷續(xù)鳴叫
        else
            staBuzzer = 0;
    }
    else
    {
        staMute = 0;
        staBuzzer = 0;
    }
}
/* 將設(shè)置時(shí)間及標(biāo)題提示顯示到液晶上 */
void ShowSetTime()
{
    uint8 pdata str[18];
   
    str[0]  = ((SetTime.year>>4) & 0xF) + '0';  //2位數(shù)年份
    str[1]  = (SetTime.year & 0xF) + '0';
    str[2]  = '-';
    str[3]  = (SetTime.mon >> 4) + '0';   //月份
    str[4]  = (SetTime.mon & 0xF) + '0';
    str[5]  = '-';
    str[6]  = (SetTime.day >> 4) + '0';   //日期
    str[7]  = (SetTime.day & 0xF) + '0';
    str[8]  = '-';
    str[9]  = (SetTime.week & 0xF) + '0'; //星期
    str[10] = ' ';
    str[11] = (SetTime.hour >> 4) + '0';  //小時(shí)
    str[12] = (SetTime.hour & 0xF) + '0';
    str[13] = ':';
    str[14] = (SetTime.min >> 4) + '0';   //分鐘
    str[15] = (SetTime.min & 0xF) + '0';
    str[16] = '\0';
    LcdShowStr(0, 0, "Set Date Time");  //顯示提示標(biāo)題
    LcdShowStr(0, 1, str);              //顯示設(shè)置時(shí)間值
}
/* 將設(shè)置鬧鐘及標(biāo)題提示顯示到液晶上 */
void ShowSetAlarm()
{
    uint8 pdata str[8];
   
    str[0] = (SetAlarmHour >> 4) + '0';   //小時(shí)
    str[1] = (SetAlarmHour & 0xF) + '0';
    str[2] = ':';
    str[3] = (SetAlarmMin >> 4) + '0';    //分鐘
    str[4] = (SetAlarmMin & 0xF) + '0';
    str[5] = '\0';
    LcdShowStr(0, 0, "Set Alarm");  //顯示提示標(biāo)題
    LcdShowStr(0, 1, str);          //顯示設(shè)定鬧鐘值
}
/* 切換系統(tǒng)運(yùn)行狀態(tài) */
void SwitchSystemSta()
{
    if (staSystem == E_NORMAL)  //正常運(yùn)行切換到時(shí)間設(shè)置
    {
        staSystem = E_SET_TIME;
        SetTime.year = CurTime.year;  //當(dāng)前時(shí)間拷貝到時(shí)間設(shè)置緩沖區(qū)中
        SetTime.mon  = CurTime.mon;
        SetTime.day  = CurTime.day;
        SetTime.hour = CurTime.hour;
        SetTime.min  = CurTime.min;
        SetTime.sec  = CurTime.sec;
        SetTime.week = CurTime.week;
        LcdClearScreen();  //液晶清屏
        ShowSetTime();     //顯示設(shè)置時(shí)間
        SetIndex = 255;    //與接下來(lái)的右移一起將光標(biāo)設(shè)在最左邊的位置上
        SetRightShift();
        LcdOpenCursor();   //開(kāi)啟光標(biāo)
    }
    else if (staSystem == E_SET_TIME)  //時(shí)間設(shè)置切換到鬧鐘設(shè)置
    {
        staSystem = E_SET_ALARM;
        SetTime.sec = 0;          //秒清零,即當(dāng)設(shè)置時(shí)間后從0秒開(kāi)始走時(shí)
        SetRealTime(&SetTime);    //設(shè)定時(shí)間寫(xiě)入實(shí)時(shí)時(shí)鐘
        SetAlarmHour = AlarmHour; //當(dāng)前鬧鐘值拷貝到設(shè)置緩沖區(qū)
        SetAlarmMin  = AlarmMin;
        LcdClearScreen();  //液晶清屏
        ShowSetAlarm();    //顯示設(shè)置鬧鐘
        SetIndex = 255;    //與接下來(lái)的右移一起將光標(biāo)設(shè)在最左邊的位置上
        SetRightShift();
    }
    else  //鬧鐘設(shè)置切換會(huì)正常運(yùn)行
    {
        staSystem = E_NORMAL;
        AlarmHour = SetAlarmHour;  //設(shè)定的鬧鐘值寫(xiě)入鬧鐘時(shí)間
        AlarmMin  = SetAlarmMin;
        LcdCloseCursor();  //關(guān)閉光標(biāo)
        LcdClearScreen();  //液晶清屏
        RefreshTime();   //刷新當(dāng)前時(shí)間
        RefreshDate(1);  //立即刷新日期顯示
        RefreshTemp(1);  //立即刷新溫度顯示
        RefreshAlarm();  //鬧鐘設(shè)定值顯示
    }
}
/* 取消當(dāng)前設(shè)置,返回正常運(yùn)行狀態(tài) */
void CancelCurSet()
{
    staSystem = E_NORMAL;
    LcdCloseCursor();  //關(guān)閉光標(biāo)
    LcdClearScreen();  //液晶清屏
    RefreshTime();   //刷新當(dāng)前時(shí)間
    RefreshDate(1);  //立即刷新日期顯示
    RefreshTemp(1);  //立即刷新溫度顯示
    RefreshAlarm();  //鬧鐘設(shè)定值顯示
}
/* 時(shí)間或鬧鐘設(shè)置時(shí),設(shè)置位右移一位,到頭后折回 */
void SetRightShift()
{
    if (staSystem == E_SET_TIME)
    {
        switch (SetIndex)
        {
            case 0: SetIndex=1;  LcdSetCursor(1, 1); break;
            case 1: SetIndex=2;  LcdSetCursor(3, 1); break;
            case 2: SetIndex=3;  LcdSetCursor(4, 1); break;
            case 3: SetIndex=4;  LcdSetCursor(6, 1); break;
            case 4: SetIndex=5;  LcdSetCursor(7, 1); break;
            case 5: SetIndex=6;  LcdSetCursor(9, 1); break;
            case 6: SetIndex=7;  LcdSetCursor(11,1); break;
            case 7: SetIndex=8;  LcdSetCursor(12,1); break;
            case 8: SetIndex=9;  LcdSetCursor(14,1); break;
            case 9: SetIndex=10; LcdSetCursor(15,1); break;
            default: SetIndex=0; LcdSetCursor(0, 1); break;
        }
    }
    else if (staSystem == E_SET_ALARM)
    {
        switch (SetIndex)
        {
            case 0: SetIndex=1;  LcdSetCursor(1,1); break;
            case 1: SetIndex=2;  LcdSetCursor(3,1); break;
            case 2: SetIndex=3;  LcdSetCursor(4,1); break;
            default: SetIndex=0; LcdSetCursor(0,1); break;
        }
    }
}
/* 時(shí)間或鬧鐘設(shè)置時(shí),設(shè)置位左移一位,到頭后折回 */
void SetLeftShift()
{
    if (staSystem == E_SET_TIME)
    {
        switch (SetIndex)
        {
            case 0: SetIndex=10; LcdSetCursor(15,1); break;
            case 1: SetIndex=0;  LcdSetCursor(0, 1); break;
            case 2: SetIndex=1;  LcdSetCursor(1, 1); break;
            case 3: SetIndex=2;  LcdSetCursor(3, 1); break;
            case 4: SetIndex=3;  LcdSetCursor(4, 1); break;
            case 5: SetIndex=4;  LcdSetCursor(6, 1); break;
            case 6: SetIndex=5;  LcdSetCursor(7, 1); break;
            case 7: SetIndex=6;  LcdSetCursor(9, 1); break;
            case 8: SetIndex=7;  LcdSetCursor(11,1); break;
            case 9: SetIndex=8;  LcdSetCursor(12,1); break;
            default: SetIndex=9; LcdSetCursor(14,1); break;
        }
    }
    else if (staSystem == E_SET_ALARM)
    {
        switch (SetIndex)
        {
            case 0: SetIndex=3;  LcdSetCursor(4,1); break;
            case 1: SetIndex=0;  LcdSetCursor(0,1); break;
            case 2: SetIndex=1;  LcdSetCursor(1,1); break;
            default: SetIndex=2; LcdSetCursor(3,1); break;
        }
    }
}
/* 輸入設(shè)置數(shù)字,修改對(duì)應(yīng)的設(shè)置位,并顯示該數(shù)字,ascii-輸入數(shù)字的ASCII碼 */
void InputSetNumber(uint8 ascii)
{
    uint8 num;
   
    num = ascii - '0';
    if (num <= 9)  //只響應(yīng)0~9的數(shù)字
    {
        if (staSystem == E_SET_TIME)
        {
            switch (SetIndex)
            {
                case 0: SetTime.year = (SetTime.year&0xFF0F)|(num<<4);
                        LcdShowChar(0, 1, ascii);  break;      //年份高位數(shù)字
                case 1: SetTime.year = (SetTime.year&0xFFF0)|(num);
                        LcdShowChar(1, 1, ascii);  break;      //年份低位數(shù)字
                case 2: SetTime.mon = (SetTime.mon&0x0F)|(num<<4);
                        LcdShowChar(3, 1, ascii);  break;      //月份高位數(shù)字
                case 3: SetTime.mon = (SetTime.mon&0xF0)|(num);
                        LcdShowChar(4, 1, ascii);  break;      //月份低位數(shù)字
                case 4: SetTime.day = (SetTime.day&0x0F)|(num<<4);
                        LcdShowChar(6, 1, ascii);  break;      //日期高位數(shù)字
                case 5: SetTime.day = (SetTime.day&0xF0)|(num);
                        LcdShowChar(7, 1, ascii);  break;      //日期低位數(shù)字
                case 6: SetTime.week = (SetTime.week&0xF0)|(num);
                        LcdShowChar(9, 1, ascii);  break;      //星期數(shù)字
                case 7: SetTime.hour = (SetTime.hour&0x0F)|(num<<4);
                        LcdShowChar(11,1, ascii);  break;      //小時(shí)高位數(shù)字
                case 8: SetTime.hour = (SetTime.hour&0xF0)|(num);
                        LcdShowChar(12,1, ascii);  break;      //小時(shí)低位數(shù)字
                case 9: SetTime.min = (SetTime.min&0x0F)|(num<<4);
                        LcdShowChar(14,1, ascii);  break;      //分鐘高位數(shù)字
                default:SetTime.min = (SetTime.min&0xF0)|(num);
                        LcdShowChar(15,1, ascii);  break;      //分鐘低位數(shù)字
            }
            SetRightShift();  //完成該位設(shè)置后自動(dòng)右移
        }
        else if (staSystem == E_SET_ALARM)
        {
            switch (SetIndex)
            {
                case 0: SetAlarmHour = (SetAlarmHour&0x0F) | (num<<4);
                        LcdShowChar(0,1, ascii); break;      //小時(shí)高位數(shù)字
                case 1: SetAlarmHour = (SetAlarmHour&0xF0) | (num);
                        LcdShowChar(1,1, ascii); break;      //小時(shí)低位數(shù)字
                case 2: SetAlarmMin = (SetAlarmMin&0x0F) | (num<<4);
                        LcdShowChar(3,1, ascii); break;      //分鐘高位數(shù)字
                default:SetAlarmMin = (SetAlarmMin&0xF0) | (num);
                        LcdShowChar(4,1, ascii); break;      //分鐘低位數(shù)字
            }
            SetRightShift();  //完成該位設(shè)置后自動(dòng)右移
        }
    }
}
/***********************main.h文件程序源代碼*************************/
#ifndef _MAIN_H
#define _MAIN_H

enum eStaSystem {  //系統(tǒng)運(yùn)行狀態(tài)枚舉
    E_NORMAL, E_SET_TIME, E_SET_ALARM
};

#ifndef _MAIN_C
extern enum eStaSystem staSystem;
#endif

void RefreshTemp(uint8 ops);
void ConfigTimer0(uint16 ms);

#endif
/***********************main.c文件程序源代碼*************************/
#define  _MAIN_C
#include "config.h"
#include "Lcd1602.h"
#include "LedBuzzer.h"
#include "keyboard.h"
#include "DS1302.h"
#include "DS18B20.h"
#include "Infrared.h"
#include "Time.h"
#include "main.h"

bit flag2s = 0;    //2s定時(shí)標(biāo)志位
bit flag200ms = 0; //200ms定時(shí)標(biāo)志
uint8 T0RH = 0;    //T0重載值的高字節(jié)
uint8 T0RL = 0;    //T0重載值的低字節(jié)
enum eStaSystem staSystem = E_NORMAL;  //系統(tǒng)運(yùn)行狀態(tài)

void main()
{
    EA = 1;           //開(kāi)總中斷
    ConfigTimer0(1);  //配置T0定時(shí)1ms
    InitLed();        //初始化LED模塊
    InitDS1302();     //初始化實(shí)時(shí)時(shí)鐘模塊
    InitInfrared();   //初始化紅外接收模塊
    InitLcd1602();    //初始化液晶模塊
    Start18B20();     //啟動(dòng)首次溫度轉(zhuǎn)換
   
    while (!flag2s);  //上電后延時(shí)2秒
    flag2s = 0;
    RefreshTime();    //刷新當(dāng)前時(shí)間
    RefreshDate(1);   //立即刷新日期顯示
    RefreshTemp(1);   //立即刷新溫度顯示
    RefreshAlarm();   //鬧鐘設(shè)定值顯示
   
    while (1)  //進(jìn)入主循環(huán)
    {
        KeyDriver();      //執(zhí)行按鍵驅(qū)動(dòng)
        InfraredDriver(); //執(zhí)行紅外接收驅(qū)動(dòng)
        if (flag200ms)    //每隔200ms執(zhí)行以下分支
        {
            flag200ms = 0;
            FlowingLight();  //流水燈效果實(shí)現(xiàn)
            RefreshTime();   //刷新當(dāng)前時(shí)間
            AlarmMonitor();  //監(jiān)控鬧鐘
            if (staSystem == E_NORMAL)  //正常運(yùn)行時(shí)刷新日期顯示
            {
                RefreshDate(0);
            }
        }
        if (flag2s)  //每隔2s執(zhí)行以下分支
        {
            flag2s = 0;
            if (staSystem == E_NORMAL)  //正常運(yùn)行時(shí)刷新溫度顯示
            {
                RefreshTemp(0);
            }
        }
    }
}
/* 溫度刷新函數(shù),讀取當(dāng)前溫度并根據(jù)需要刷新液晶顯示,
** ops-刷新選項(xiàng):為0時(shí)只當(dāng)溫度變化才刷新,非0則立即刷新 */
void RefreshTemp(uint8 ops)
{
    int16 temp;
    uint8 pdata str[8];
    static int16 backup = 0;
   
    Get18B20Temp(&temp); //獲取當(dāng)前溫度值
    Start18B20();        //啟動(dòng)下一次轉(zhuǎn)換
    temp >>= 4;          //舍棄4bit小數(shù)位
    if ((backup!=temp) || (ops!=0)) //按需要刷新液晶顯示
    {
        str[0] = (temp/10) + '0';  //十位轉(zhuǎn)為ASCII碼
        str[1] = (temp%10) + '0';  //個(gè)位轉(zhuǎn)為ASCII碼
        str[2] = '\'';             //用'C代替℃
        str[3] = 'C';
        str[4] = '\0';             //字符串結(jié)束符
        LcdShowStr(12, 0, str);    //顯示到液晶上
        backup = temp;             //刷新上次溫度值
    }
}
/* 配置并啟動(dòng)T0,ms-T0定時(shí)時(shí)間 */
void ConfigTimer0(uint16 ms)
{
    uint32 tmp;
   
    tmp = (SYS_MCLK*ms)/1000; //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp;        //計(jì)算定時(shí)器重載值
    tmp = tmp + 34;           //補(bǔ)償中斷響應(yīng)延時(shí)造成的誤差   
    T0RH = (uint8)(tmp>>8);   //定時(shí)器重載值拆分為高低字節(jié)
    T0RL = (uint8)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0為模式1
    TH0 = T0RH;     //加載T0重載值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中斷
    TR0 = 1;        //啟動(dòng)T0
}
/* T0中斷服務(wù)函數(shù),實(shí)現(xiàn)系統(tǒng)定時(shí)和按鍵掃描 */
void InterruptTimer0() interrupt 1
{
    static uint8 tmr2s = 0;
    static uint8 tmr200ms = 0;
   
    TH0 = T0RH;  //定時(shí)器重新加載重載值
    TL0 = T0RL;
    tmr200ms++;  //定時(shí)200ms
    if (tmr200ms >= 200)
    {
        tmr200ms = 0;
        flag200ms = 1;
        tmr2s++;  //定時(shí)2s
        if (tmr2s >= 10)
        {
            tmr2s = 0;
            flag2s = 1;
        }
    }
    KeyScan();   //執(zhí)行按鍵掃描
}
   程序代碼已經(jīng)完成了,但是大家的學(xué)習(xí)還得繼續(xù),把思路學(xué)差不多之后,要自己能夠不看源代碼,獨(dú)立把這個(gè)程序編寫(xiě)出來(lái),那么我就可以很高興的告訴你,你的單片機(jī)已經(jīng)合格了,你可以動(dòng)手開(kāi)發(fā)一些小產(chǎn)品,進(jìn)入下一個(gè)層次的歷練了。
   當(dāng)然了,同學(xué)們不要指望這樣的代碼一下子寫(xiě)出來(lái)就好用,包括我們研發(fā)工程師,調(diào)試這種代碼也是一步步來(lái)的,在調(diào)試的過(guò)程中,可能還要穿插修改很多之前寫(xiě)好的代碼,協(xié)調(diào)功能工作等等。同學(xué)們?nèi)绻?dú)立寫(xiě)這種代碼,3天到一個(gè)周調(diào)試出來(lái)還是比較正常的。學(xué)到這里,相信同學(xué)們對(duì)于做技術(shù)的基本耐性已經(jīng)具備了。做技術(shù),耐心、細(xì)心、恒心,缺一不可。不要像初學(xué)那樣遇到一個(gè)問(wèn)題動(dòng)不動(dòng)就浮躁了,慢慢來(lái),最終把這個(gè)功能實(shí)現(xiàn)出來(lái),完成你單片機(jī)之路的第一個(gè)項(xiàng)目。
作業(yè)
   1、學(xué)會(huì)使用類型說(shuō)明定義新類型,能夠區(qū)別typedef和#define。
   2、學(xué)會(huì)建立編寫(xiě)頭文件,并且掌握頭文件的格式。
   3、掌握條件編譯的用法
   4、獨(dú)立將多功能電子鐘項(xiàng)目開(kāi)發(fā)的代碼完成。
上一課: 第18章 RS485通信和Modbus協(xié)議
下一課: 第20章 單片機(jī)開(kāi)發(fā)常用工具的使用

評(píng)分

參與人數(shù) 2黑幣 +15 收起 理由
send + 10 贊一個(gè)!
ssfc + 5 贊一個(gè)!

查看全部評(píng)分

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

相關(guān)帖子

回復(fù)

使用道具 舉報(bào)

地板
ID:93625 發(fā)表于 2018-8-8 12:38 | 只看該作者
學(xué)習(xí)一定會(huì)堅(jiān)持下去的,就是時(shí)間的問(wèn)題
回復(fù)

使用道具 舉報(bào)

板凳
ID:113327 發(fā)表于 2016-5-19 08:15 來(lái)自手機(jī) | 只看該作者
沒(méi)有想象的那么簡(jiǎn)單
回復(fù)

使用道具 舉報(bào)

沙發(fā)
ID:78515 發(fā)表于 2015-8-13 11:23 | 只看該作者
這個(gè)程序感覺(jué)還是有一定的難度了。這套教材是轉(zhuǎn)載的,上面有出處的
回復(fù)

使用道具 舉報(bào)

樓主
ID:58108 發(fā)表于 2015-6-26 10:46 | 只看該作者
你是宋老師嗎
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

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

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

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