要包含一個(gè)單片機(jī)硬件的資源頭文件。
各模塊使用了定義在Common.h中的一些數(shù)據(jù)類(lèi)型。如:BIT(bit) BYTE(unsigned char)等,具體請(qǐng)參見(jiàn)源程序。
時(shí)鐘模塊
在單片機(jī)軟件設(shè)計(jì)中, 時(shí)鐘是重要資源, 為了充分利用時(shí)鐘資源, 故設(shè)計(jì)本時(shí)鐘模塊。 本模塊使用定時(shí)器0,在完成用戶指定功能的同時(shí), 還能夠自動(dòng)處理一些其它模塊中與時(shí)鐘相關(guān)的信息。
時(shí)鐘模塊由聲明文件Timer.h以及實(shí)現(xiàn)文件Timer.c組成。
用戶應(yīng)該在Config.h中定義宏TIMER_RELOAD來(lái)設(shè)定定時(shí)器0的重裝載初值。推薦的定時(shí)器0的中斷時(shí)間大于1毫秒。
在程序的初始化階段調(diào)用時(shí)鐘模塊的初始化函數(shù)InitTimerModule()之后,就可以使用時(shí)鐘模塊所以支持的各種功能。具體描述如下:
延時(shí):當(dāng)用戶需要進(jìn)行一定時(shí)間的延時(shí)時(shí),可以通過(guò)調(diào)用Delay()來(lái)進(jìn)行,參數(shù)為時(shí)鐘中斷的次數(shù)。如時(shí)鐘中斷周期為1ms, 想進(jìn)行100ms的延時(shí), 則可以調(diào)用Delay(100)。
注意:
如果延時(shí)的絕對(duì)時(shí)間小于時(shí)鐘中斷的周期,則不能夠用本方法做到延時(shí)。
定時(shí):當(dāng)程序中需要使用定時(shí)功能時(shí),如等待某外部事件,如果在一定時(shí)間內(nèi)發(fā)生則繼續(xù)執(zhí)行,如果在這段時(shí)間內(nèi)發(fā)生,則認(rèn)為出現(xiàn)錯(cuò)誤,轉(zhuǎn)向錯(cuò)誤處理機(jī)制。
在此推薦一種編程模式,但用戶可以用自己認(rèn)為更合理的方式處理此類(lèi)問(wèn)題。
這里簡(jiǎn)單說(shuō)明一下關(guān)于阻塞式函數(shù)及非阻塞式函數(shù)。簡(jiǎn)單說(shuō),阻塞式函數(shù)就是當(dāng)檢測(cè)完成條件,如果不能夠完成則等待,如:
void CheckSomething()
{
// gbitSuccessFlag is a global variable
while(gbitSuccessFlag == FALSE)
{
// do nothing but waiting
}
}
可以看到,當(dāng)bitSuccessFlag沒(méi)有被設(shè)置為T(mén)RUE時(shí),函數(shù)保持等待狀態(tài)不返回,這樣就是阻塞式的函數(shù)。
另外一種情況:
BIT CheckSomething()
{
if(gbitSuccessFlag == TRUE)
{
// …
return TRUE;
}
return FALSE;
}
在這里,如果所檢測(cè)的事件有沒(méi)有完成,函數(shù)進(jìn)行檢測(cè)之后,立刻返回,通過(guò)返回值報(bào)告完成情況,如果沒(méi)有完成,則等待調(diào)用者分配再次執(zhí)行的機(jī)會(huì)。這樣的函數(shù)就是非阻塞函數(shù)。
在應(yīng)用定時(shí)功能時(shí),首先要將檢測(cè)函數(shù)定義成非阻塞函數(shù)。如上面的第二個(gè)版本的CheckSomething。
然后下面模式:
BIT bitDone = FALSE;
ResetClock(); // clear timer interrupt times counter
while(GetClock() < MAX_WAITINGTIME)
{
if(CheckSomething() == TRUE)
{
bitDone = TRUE;
break;
}
}
if(bitDone == FALSE)
{
// process time out
}
或者簡(jiǎn)單寫(xiě)成:
BIT bitDone = FALSE;
ResetClock();
while(GetClock() < MAX_WAITINGTIME && (bitDone = CheckSomething));
if(bitDone == FLASE)
{
// …
}
軟件看門(mén)狗:實(shí)現(xiàn)具有局限性的看門(mén)狗功能。在程序中合適的地方加入對(duì)軟件看門(mén)狗的復(fù)位函數(shù)ResetWatchDog(),在Config.h中加入宏 TIMER_WATCHDOGTIMEOUT。當(dāng)程序運(yùn)行時(shí),如果在發(fā)生TIMER_WATCHDOGTIMEOUT次時(shí)鐘中斷之內(nèi)沒(méi)有復(fù)位軟件看門(mén)狗,則系統(tǒng)復(fù)位。
注意:
如果沒(méi)有加入TIMER_WATCHDOGTIMEOUT宏,程序中的ResetWatchDog沒(méi)有任何用處,不用刪除。
如果系統(tǒng)不能實(shí)現(xiàn)時(shí)鐘中斷,則軟件看門(mén)狗也同時(shí)失去功能。
目前版本的的時(shí)鐘模塊的復(fù)位功能并不是完全復(fù)位,主要表現(xiàn)在當(dāng)復(fù)位之后,系統(tǒng)將不再響應(yīng)任何中斷。所以軟件看門(mén)狗只是一個(gè)程序的調(diào)試功能,不應(yīng)該將它用于正式工作的程序,此時(shí)應(yīng)該使用硬件看門(mén)狗。
用戶自定義任務(wù):如果想在時(shí)鐘中斷內(nèi)執(zhí)行一些耗時(shí)較短的任務(wù),可以定義回調(diào)函數(shù)OnTimerInterrupt。函數(shù)原形為:void OnTimerInterrupt();
如果想在發(fā)生時(shí)鐘中斷時(shí)執(zhí)行一些功能,而這些功能又耗時(shí)相對(duì)較長(zhǎng),不合適放在中斷響應(yīng)函數(shù)內(nèi)部,則可以在程序中的主循環(huán)中的任意地方添加: ImpTimerService(),同時(shí)提供原形為void OnTimerEvent()的回調(diào)函數(shù)。具體的程序如下所示:
void main()
{
Initialize();
while(TRUE)
{
// … working
ImpTimerService();
// … working
}
}
void OnTimerEvent()
{
// do some task
}
對(duì)通訊模塊提供支持:如通訊中的各種超時(shí)等,見(jiàn)通訊模塊中的詳細(xì)說(shuō)明。
對(duì)鍵盤(pán)掃描模塊提供支持:可以自動(dòng)調(diào)用鍵盤(pán)掃描模塊,見(jiàn)鍵盤(pán)掃描模塊中的詳細(xì)說(shuō)明。
對(duì)程序調(diào)試提供支持:在程序開(kāi)發(fā)過(guò)程中,有時(shí)為了判斷程序是不是在工作,常用利用單片機(jī)系統(tǒng)的某一空閑引腳通過(guò)一個(gè)限流電阻接一個(gè)發(fā)光二極管,在程序中間隔固定時(shí)間交替控制發(fā)光管的明暗。實(shí)現(xiàn)這個(gè)功能只要在Config.h文件中定義TIMER_FLASHLED宏,如:
#define TIMER_FLASHLED P1_0
則當(dāng)時(shí)鐘中斷發(fā)生256次之后,改變發(fā)光管的狀態(tài)。
通訊模塊
串口資源做為單片機(jī)與外界通信的常用手段,通訊模塊提供了完全緩沖的串口通訊底層機(jī)制,適用于長(zhǎng)度不大的數(shù)據(jù)包的發(fā)送及接收。如果處理關(guān)鍵數(shù)據(jù),需要用戶自己提供糾錯(cuò)協(xié)議。
通訊模塊由聲明文件SComm.h及實(shí)現(xiàn)文件SComm.c組成。
初始化:調(diào)用函數(shù)InitSCommModule()來(lái)初始化通訊模塊:
void InitSCommModule(BYTE byTimerReload, BIT bitTurbo)
參數(shù)說(shuō)明:
byTimerReload: 定時(shí)器1的重裝載初始值。
bitTurob: 當(dāng)此參數(shù)為T(mén)RUE時(shí),串行通訊在定時(shí)器1的溢出速率基礎(chǔ)上加倍。為FALSE時(shí),串行通訊速率為定時(shí)器1的溢出速率。
緩沖區(qū):模塊使用了由宏SCOMM_SENDBUFSIZE、SCOMM_RECEBUFSIZE及SCOMM_PKGBUFSIZE所指定長(zhǎng)度的三個(gè)緩沖區(qū),分別為發(fā)送、接收及數(shù)據(jù)包(用于處理接收到的數(shù)據(jù))緩沖區(qū)(如果沒(méi)有使用異步接收功能,則不需要使用數(shù)據(jù)包緩沖區(qū))。
在缺省時(shí),這三個(gè)宏都被定義為10,但用戶可以自已按照系統(tǒng)的RAM資源占用情況在Config.h中重定義緩沖區(qū)的大小。需要注意的是,如果緩沖的長(zhǎng)度不夠,當(dāng)發(fā)送或接收長(zhǎng)數(shù)據(jù)包的時(shí)候可能會(huì)發(fā)生問(wèn)題,關(guān)于數(shù)據(jù)緩沖區(qū)的最小值的設(shè)置可以參考下面的說(shuō)明。
注意:需要盡快取出接收緩沖區(qū)中的數(shù)據(jù),否則當(dāng)緩沖區(qū)滿之后,新的數(shù)據(jù)將被簡(jiǎn)單的丟掉。
字節(jié)級(jí)服務(wù)函數(shù): 在Config.h文件中定義了宏SCOMM_DriverInterface(如:#define SCOMM_DriverInterface),則可以使用字節(jié)級(jí)服務(wù)函數(shù),即通訊模塊的底層函數(shù)。
共有兩個(gè)函數(shù)可以使用:
void SendByte(BYTE byData);
發(fā)送一個(gè)字節(jié),如果當(dāng)前緩沖區(qū)滿,則等待。參數(shù)byData為要發(fā)送的數(shù)據(jù)。
BYTE ReceByte();
接收一個(gè)字節(jié),如果當(dāng)前緩沖區(qū)中沒(méi)有數(shù)據(jù),則此函數(shù)阻塞,直到接收到數(shù)據(jù)為止。接收到數(shù)據(jù)通過(guò)返回值返回。
可以通過(guò)調(diào)用IsSendBufEmpty() IsSendBufFull() IsReceBufEmpty() IsReceBufFull() 宏來(lái)判斷緩沖區(qū)的空或滿,以防系統(tǒng)阻塞。
不推薦直接使用這一級(jí)的服務(wù)函數(shù),應(yīng)該使用高層次上的服務(wù)函數(shù)或者在這一級(jí)服務(wù)函數(shù)的基礎(chǔ)上構(gòu)造自己的通訊函數(shù)。
數(shù)據(jù)包級(jí)服務(wù)函數(shù):在Config.h文件中定義宏SCOMM_PackageInterface(如: #define SCOMM_PackageInterface)則可以使用數(shù)據(jù)包級(jí)服務(wù)函數(shù)。
共有兩個(gè)函數(shù)可以使用:
void SendPackage(BYTE* pbyData, BYTE byLen);
發(fā)送數(shù)據(jù)包,參數(shù)pbyData為將要發(fā)送的數(shù)據(jù)包緩沖區(qū)(數(shù)組)的指針,byLen為將要發(fā)送的數(shù)據(jù)包的長(zhǎng)度。
當(dāng)沒(méi)有定義SCOMM_DriverInterface時(shí),數(shù)據(jù)被完全緩沖。即不能夠發(fā)送長(zhǎng)度超過(guò)發(fā)送緩沖區(qū)長(zhǎng)度的數(shù)據(jù)包。當(dāng)定義了SCOMM_DriverInterface時(shí),采用單字節(jié)發(fā)送,這時(shí)不限制需要發(fā)送的數(shù)據(jù)的長(zhǎng)度。
BYTE RecePackage(BYTE* pbyData, BYTE byLen);
接收數(shù)據(jù)包,參數(shù)pbyData為存放將要接收的數(shù)據(jù)的緩沖區(qū),byLen為緩沖區(qū)長(zhǎng)度。返回值為接收到的字節(jié)數(shù),當(dāng)模塊的接收緩沖區(qū)為空時(shí),函數(shù)非阻塞,立即返回,返回值為零。
同步發(fā)送接收服務(wù)函數(shù):
比如在一個(gè)串行總線多機(jī)通訊系統(tǒng)中,主機(jī)需要定時(shí)循檢各從機(jī)的狀態(tài),往往是發(fā)一個(gè)包含從機(jī)地址及指令的數(shù)據(jù)包給從機(jī),之后等待一定的時(shí)間,從機(jī)需要在這段時(shí)間之內(nèi)給主機(jī)一個(gè)應(yīng)答,如果沒(méi)有這個(gè)應(yīng)答,則認(rèn)為從機(jī)工作狀態(tài)出錯(cuò),轉(zhuǎn)去進(jìn)行相應(yīng)的處理。在這個(gè)模型里,主機(jī)不能夠不進(jìn)行等待而給另一臺(tái)從機(jī)發(fā)送指令,也不能夠不管從機(jī)在很久沒(méi)有應(yīng)答的情況下繼續(xù)等待。還有一種情況,比如當(dāng)使用485總線進(jìn)行通信時(shí),如果是兩條通訊線則系統(tǒng)只能工作在半雙工模式下,總線在同一時(shí)間內(nèi)只能工作在發(fā)送或接收,為了防止發(fā)送和接收相互干擾,這時(shí)的通訊常常需要使用同步發(fā)送和接收。
當(dāng)在Config.h文件中定義宏SCOMM_SyncInterface后,則可以使用通訊模塊提供同步發(fā)送接收函數(shù):
void SendPackage(BYTE* pbyData, BYTE byLen);
發(fā)送數(shù)據(jù)包,參數(shù)pbyData為將要改善的數(shù)據(jù)包的緩沖區(qū)指針,byLen為將要發(fā)送的數(shù)據(jù)包的長(zhǎng)度。
這個(gè)函數(shù)可以保證等待一個(gè)完整的數(shù)據(jù)包完全發(fā)送出去之后,它才返回,在這段時(shí)間內(nèi),它會(huì)阻塞運(yùn)行。
BYTE SyncRecePackage(BYTE* pbyBuf, BYTE byBufLen, WORD wTimeout, BYTE byParam);
接收數(shù)據(jù)包。返回值為接收到的數(shù)據(jù)包長(zhǎng)度。參數(shù)pbyBuf為將要接收數(shù)據(jù)包的緩沖區(qū)的指針,byBufLen為提供的緩沖區(qū)的長(zhǎng)度,wTimeout為通信超時(shí)值,如果在發(fā)生了由wTimeout所指定次數(shù)的時(shí)鐘中斷而還沒(méi)有接收到或沒(méi)有接收到完整的數(shù)據(jù)包時(shí),函數(shù)返回零,最后一個(gè)參數(shù)byParam的含義見(jiàn)后面的解釋。
異步發(fā)送接收服務(wù)函數(shù):
在一個(gè)簡(jiǎn)單的系統(tǒng)或多機(jī)通訊系統(tǒng)中的從機(jī)上,一般情況下不需要復(fù)雜的停等的工作模式,而且往往單片機(jī)需要對(duì)硬件進(jìn)行控制和檢測(cè),不允許長(zhǎng)時(shí)間的停下來(lái)檢測(cè)通訊,但又要求當(dāng)需要通訊時(shí)需要盡快的反應(yīng)速度,這時(shí)就需要使用異步發(fā)送和接收服務(wù)函數(shù)。
使用異步發(fā)送和接收服務(wù)函數(shù)需要在Config.h文件中定義SCOMM_AsyncInterface宏。
同樣提供兩個(gè)服務(wù)函數(shù):
void SendPackage(BYTE* pbyData, BYTE byLen);
發(fā)送數(shù)據(jù)包,參數(shù)pbyData為將要改善的數(shù)據(jù)包的緩沖區(qū)指針,byLen為將要發(fā)送的數(shù)據(jù)包的長(zhǎng)度。
這里的函數(shù)的接口與同步發(fā)送和接收的服務(wù)函數(shù)相同。關(guān)于這里的細(xì)節(jié),見(jiàn)后面對(duì)同步和異步服務(wù)函數(shù)的說(shuō)明。
void AsyncRecePackage(BYTE byParam);
接收數(shù)據(jù)包,參數(shù)byParam的意義見(jiàn)后面的描述。
使用異步通訊需要用戶定義一個(gè)回調(diào)函數(shù),原型如下:
void OnRecePackage(BYTE* pbyData, BYTE byBufLen);
當(dāng)異步接收服務(wù)函數(shù)接收到數(shù)據(jù)包之后,調(diào)用OnRecePackage回調(diào)函數(shù),在pbyData指定的緩沖區(qū)中存放數(shù)據(jù)包,byBufLen為數(shù)據(jù)包的長(zhǎng)度。
在Config.h文件中定義宏SCOMM_TIMEOUT可以設(shè)定異步接收的超時(shí)值,當(dāng)開(kāi)始接收數(shù)據(jù)包,但沒(méi)有收完數(shù)據(jù)而發(fā)生了SCOMM_TIMEOUT次時(shí)鐘中斷后,認(rèn)為接收超時(shí),將已接收到的數(shù)據(jù)刪除。
同步和異步通訊服務(wù)函數(shù):
有些情況下,比如一個(gè)通訊系統(tǒng)中,由一臺(tái)計(jì)算機(jī)通過(guò)串口控制主機(jī),主機(jī)通過(guò)串口連接很多從機(jī),主機(jī)的串口采用分時(shí)復(fù)用,在這樣的模型中,主機(jī)和控制計(jì)算機(jī)之間的通訊可以使用,異步通訊方式,而主機(jī)與從機(jī)可以使用同步通訊方式。而同步和異步的發(fā)送函數(shù)接口是相同的,在這樣的情況下,發(fā)送都是同步的。在這樣的模型中,當(dāng)使用不同的接收函數(shù)之前,需要注意清除接收緩沖區(qū)中的內(nèi)容,通訊模塊提供函數(shù):ClearReceBuffer來(lái)做到這一點(diǎn),此函數(shù)原型如下:
void ClearReceBuffer();
通訊過(guò)程中,數(shù)據(jù)包往往是有固定的格式的,這種格式需要根據(jù)用戶所使用的協(xié)議的不同而不同。同步和異步接收服務(wù)函數(shù)支持從接收到的數(shù)據(jù)中識(shí)別出一定格式的數(shù)據(jù)包。
舉例說(shuō)明:目前使用的協(xié)議決定數(shù)據(jù)包的格式為固定的包頭0xff,固定的長(zhǎng)度4個(gè)字節(jié)。其它的細(xì)節(jié)在這里不重要,所以忽略掉。
為了能夠使用用SyncRecePackage或AsyncRecePackage函數(shù)從接收到的數(shù)據(jù)中識(shí)別出如上格式的數(shù)據(jù)包,有兩種方法:
第一種辦法是在Config.h文件中定義宏SCOMM_SimplePackageFormat,說(shuō)明數(shù)據(jù)包為一種簡(jiǎn)單格式,比如上面的協(xié)議。
之后還要定義兩個(gè)宏分別用來(lái)識(shí)別數(shù)據(jù)包頭和數(shù)據(jù)包尾,兩個(gè)宏分別是:
IsPackageHeader(x)和IsPackageTailer(x, y, z)
接收函數(shù)(SyncRecePackage和AsyncRecePackage)在沒(méi)有開(kāi)始接收數(shù)據(jù)包(準(zhǔn)確的說(shuō)是還沒(méi)有從接收到的數(shù)據(jù)包中找到包頭的時(shí)候),會(huì)對(duì)接收到的每一個(gè)字節(jié)的數(shù)據(jù)調(diào)用IsPackageHeader宏,將相應(yīng)的數(shù)據(jù)作為參數(shù),如果IsPackageHeader宏的結(jié)果為 TRUE,則認(rèn)為找到了數(shù)據(jù)包頭,否則繼續(xù)對(duì)下一個(gè)字節(jié)進(jìn)行判斷。
上面的協(xié)議對(duì)應(yīng)的IsPackageHeader宏可以寫(xiě)為:
#define IsPackageHeader(x) ((x) == 0xff)
當(dāng)接收到包頭之后,接收函數(shù)會(huì)對(duì)接下來(lái)的每一個(gè)字節(jié)數(shù)據(jù)調(diào)用IsPackagTailer宏來(lái)判斷是不是已經(jīng)接收完數(shù)據(jù)包,三個(gè)參數(shù)分別為:
x: 當(dāng)前判斷的數(shù)據(jù)。
y: 從包頭開(kāi)始到當(dāng)前被判斷的數(shù)據(jù)止的計(jì)數(shù)值,即當(dāng)前已經(jīng)接收到的字節(jié)數(shù)。
z:用戶在調(diào)用SyncRecePackage或AsyncRecePackage時(shí)指定的byParam參數(shù)。
與IsPackageHeader相似,如果宏IsPackageTailer的運(yùn)算結(jié)果為T(mén)RUE,則認(rèn)為接收到完整的數(shù)據(jù)包,則調(diào)用相應(yīng)的回調(diào)函數(shù)(對(duì)于異步接收函數(shù))或返回(對(duì)于同步接收函數(shù))。如果運(yùn)算結(jié)果為FALSE則繼續(xù)判斷下一個(gè)字節(jié)的數(shù)據(jù)。
上面的協(xié)議對(duì)應(yīng)的IsPackageTailer宏可以寫(xiě)為:
#define IsPackageTailer(x, y, z) ((y) >= (z))
當(dāng)然,用戶也可以將IsPackageHeader和IsPackageTailer定義成為函數(shù),通過(guò)BIT類(lèi)型的返回值來(lái)向調(diào)用者提供與相應(yīng)宏相同的信息。
另一種辦法需要在Config.h文件中定義宏SCOMM_ComplexPackageFormat。(需要注意的是,不能夠同時(shí)定義 SCOMM_SimplePackageFormat和SCOMM_ComplexPackageFormat宏,否則會(huì)造成嚴(yán)重的不可預(yù)見(jiàn)性錯(cuò)誤。
這時(shí)需要提供回調(diào)函數(shù)QueryPackageFormat,原形如下:
BYTE QueryPackageFormat(BYTE byData, BYTE byCount, BYTE byParam);
函數(shù)中三個(gè)參數(shù)的含義與使用簡(jiǎn)單數(shù)據(jù)包格式時(shí)判斷數(shù)據(jù)包尾的宏的參數(shù)相同。
函數(shù)通過(guò)返回值來(lái)通知作為調(diào)用者的接收函數(shù)對(duì)接收到的數(shù)據(jù)如何處理,但目前這種方法僅為需要處理復(fù)雜數(shù)據(jù)包格式時(shí)的一種可選方法,但不推薦。用戶如果想使用這種方法可以自己更改接收函數(shù)中相應(yīng)的
#ifdef SCOM_ComplexPackageFormat
#endif // SCOMM_ComplexPackageFormat
預(yù)編譯指令之間的內(nèi)容。
例如指定QueryPackageFormat的返回值的含義:
0:繼續(xù)找數(shù)據(jù)包頭或繼續(xù)找數(shù)據(jù)包尾。
1:找到數(shù)據(jù)包頭。
2:找到數(shù)據(jù)包尾。
3:數(shù)據(jù)包出錯(cuò),需要拋棄。
然后更改源代碼來(lái)實(shí)現(xiàn)上面的協(xié)議。
注意:當(dāng)用戶需要使用字符串的時(shí)候,可以利用簡(jiǎn)單的包裝函數(shù)將字符串轉(zhuǎn)換為字節(jié)數(shù)組。所以沒(méi)有必要提供專(zhuān)用的字符串處理函數(shù)。
鍵盤(pán)掃描模塊
鍵盤(pán)掃描模塊有兩種工作方式, 一種為自動(dòng)的由時(shí)鐘模塊調(diào)用, 另一種是由程序員自行調(diào)用。
1) 由時(shí)鐘模塊自動(dòng)調(diào)用的方式
將時(shí)鐘模塊實(shí)現(xiàn)文件(Timer.h)及鍵盤(pán)掃描模塊的實(shí)現(xiàn)文件(KBScan。c)包含進(jìn)工程, 在Config.h 文件中添加TIMER_KBSCANDELAY宏。時(shí)鐘模塊自動(dòng)對(duì)時(shí)鐘中斷進(jìn)行計(jì)數(shù), 當(dāng)達(dá)到TIMER_KBSCANDELAY宏所定義的值后, 自動(dòng)調(diào)用鍵盤(pán)掃描模塊中的函數(shù)KBScanProcess()進(jìn)行鍵盤(pán)掃描,也就是說(shuō),這個(gè)宏的值可以決定按鍵消抖動(dòng)的時(shí)間。
用戶應(yīng)該提供兩個(gè)回調(diào)函數(shù)OnKBScan()及onKeysPressed()。 在函數(shù)OnKBScan中進(jìn)行鍵盤(pán)掃描, 并返回掃描碼。掃描碼的類(lèi)型缺省為BYTE, 當(dāng)鍵盤(pán)規(guī)模較大時(shí), BYTE不能夠完全包含鍵盤(pán)信息時(shí), 可在Config.h文件中重定義宏KBVALUE, 如下:
#define KBVALUE WORD
這樣, 就可以使用16位的鍵盤(pán)掃描碼, 如果此時(shí)還達(dá)不到要求, 可以將鍵盤(pán)掃描碼定義成一個(gè)結(jié)構(gòu), 但這樣做將會(huì)增加代碼量及消耗更多的RAM資源, 故不推薦。
掃描模塊調(diào)用OnKBScan取得掃描碼, 并調(diào)用用戶可以重定義的宏IsNoKeyPressed來(lái)判斷是否有鍵按下, 缺省的IsNoKeyPressed實(shí)現(xiàn)如下:
#define IsNoKeyPressed(x) ((x) == 0x00)
即認(rèn)為OnKBScan返回0掃描碼時(shí)為沒(méi)有鍵按下, 如果掃描函數(shù)返回其它非零掃描碼做為無(wú)鍵按下的掃描碼時(shí), 可以在Config.h文件中重定義IsNoKeyPressed宏的實(shí)現(xiàn)。
8位鍵盤(pán)掃描碼(缺省值)時(shí), 相應(yīng)的掃描函數(shù)為:
BYTE OnKBScan()
當(dāng)掃描模塊經(jīng)過(guò)軟件消抖動(dòng)之后, 發(fā)現(xiàn)有鍵按下, 就會(huì)調(diào)用另一個(gè)回調(diào)函數(shù)onKeysPressed。 函數(shù)的聲明應(yīng)該如下:
void onKeyPressed(BYTE byKBValue, BYTE byState)
其中中的參數(shù)byKBValue的類(lèi)型為BYTE, 此為缺省值, 如果使用其它類(lèi)型的掃描碼, 就將此參數(shù)變?yōu)橄鄳?yīng)類(lèi)型。這個(gè)值由OnKBScan返回。另一個(gè)參數(shù)byState在通常情況下為零。但當(dāng)用戶在Config.h中定義宏KBSCAN_BRUSTCOUNT,同時(shí)鍵盤(pán)上的某鍵被按住不放時(shí), 掃描模塊對(duì)它自己的調(diào)用(注意這里和TIMER_KBSCANDELAY宏不同, TIMER_KBSCANDELAY是時(shí)鐘中斷足夠的次數(shù)后調(diào)用掃描模塊, 而KBSCAN_BRUSHCOUNT為掃描模塊自身的被調(diào)用次數(shù))進(jìn)行計(jì)數(shù),當(dāng)達(dá)到KBSCAN_BRUSTCOUNT時(shí),掃描模塊調(diào)用 onKeysPressed,此時(shí)第一個(gè)參數(shù)的含義不變,而byState變成1, 同時(shí)計(jì)數(shù)器復(fù)位,又經(jīng)過(guò)一段時(shí)間后,用值為3的byState 調(diào)用onKeysPressed。 這樣就可以很方便的實(shí)現(xiàn)多功能鍵或者檢測(cè)某鍵的長(zhǎng)時(shí)間被按下。
2)由用戶自行調(diào)用
由用戶自行在程序中調(diào)用掃描模塊,而不是由時(shí)鐘中斷自行調(diào)用。其它與方式1相同。
注意:
1) 函數(shù)KBScanProcess為非阻塞函數(shù),它將在很快的時(shí)間內(nèi)返回,等待再次分配給它執(zhí)行的機(jī)會(huì)。
2) 函數(shù)KBScanProcess是在時(shí)鐘中斷外部運(yùn)行的,它的過(guò)程可以被任何中斷打斷,但不影響系統(tǒng)運(yùn)行。
3) byState的最大值為250,之后被復(fù)位為零。
應(yīng)用舉例
現(xiàn)在來(lái)舉例說(shuō)明上述幾個(gè)模塊的使用方法。
硬件環(huán)境描述:
為了控制一盞燈,需要單片機(jī)提供一個(gè)做控制功能的開(kāi)關(guān)量,這里不描述外部接口電路,只說(shuō)明當(dāng)單片機(jī)的P10腳為高電平時(shí),燈滅,當(dāng)P10腳為低電平時(shí),燈亮。
可以通過(guò)計(jì)算機(jī)由串口發(fā)送命令來(lái)控制,或通過(guò)一個(gè)按鍵(push button不是自鎖式的按鍵)來(lái)手動(dòng)控制(按鍵接在P11腳上,當(dāng)鍵沒(méi)有按下時(shí),P11電平為高,鍵按下時(shí),引腳電平被接低),當(dāng)使用按鍵手動(dòng)控制的時(shí)候,需要給計(jì)算機(jī)發(fā)送通知。
設(shè)定串口通訊指令如下:
數(shù)據(jù)包由0xff做包頭,4個(gè)字節(jié)長(zhǎng),第二個(gè)字節(jié)為命令代碼,第三個(gè)字節(jié)為數(shù)據(jù),最后一個(gè)字節(jié)為校驗(yàn)位。
命令和數(shù)據(jù)代碼有如下組合:
(計(jì)算機(jī)發(fā)給單片機(jī))
0x10 0x01: 計(jì)算機(jī)控制燈亮。(數(shù)據(jù)位是非零值即可)
0x10 0x00: 計(jì)算機(jī)控制燈滅。
(單片機(jī)發(fā)給計(jì)算機(jī))
0x11 0x01:?jiǎn)纹瑱C(jī)正常執(zhí)行控制指令,返回。(數(shù)據(jù)位是非零值即可)
0x11 0x00: 單片機(jī)不能夠正常執(zhí)行控制指令,或控制指令錯(cuò)(不明含義的數(shù)據(jù)包或校驗(yàn)錯(cuò)等)。
0x12 0x01:手動(dòng)控制燈亮。(數(shù)據(jù)位是非零值即可)
0x12 0x00: 手動(dòng)控制燈滅。
建立工程:
在硬盤(pán)上建立文件夾Projects,在Projects下建立Common文件夾及Example文件夾。將各模塊的頭文件及實(shí)現(xiàn)文件拷貝到 Common文件夾下(推薦使用這樣的文件組織結(jié)構(gòu),其它工程也可以建立在Projects下,各工程共享Common文件夾中的代碼)。
啟動(dòng)KeilC的IDE,在Example下建立新工程,將各模塊的實(shí)現(xiàn)文件包含進(jìn)工程。
在Example文件夾下建立Output文件夾,更改工程設(shè)置,將Output作為輸出文件和List文件的輸出文件夾(推薦使用這樣的結(jié)構(gòu),當(dāng)保存工程文件時(shí),可以簡(jiǎn)單的刪除Output文件夾中的內(nèi)容而不會(huì)誤刪有用的工程文件)。
建立工程配置頭文件Config.h及工程主文件Example.c,并將Exmaple.c文件加入工程。
輸入代碼:
代碼的具體編寫(xiě)過(guò)程略。下面是最后的Config.h文件及Example.c文件。
//
// file: Config.h
//
#ifndef _CONFIG_H_
#define _CONFIG_H_
#i nclude <Atmel/At89x52.h> // 使用AT89C52做控制
#i nclude “../Common/Common.h” // 使用自定義的數(shù)據(jù)類(lèi)型
#define TIMER_RELOAD 922 // 11.0592MHz晶振,1ms中斷周期
#define TIMER_KBSCANDELAY 40 // 40ms重檢測(cè)按鍵狀態(tài),即40ms消抖
#define SCOMM_AsyncInterface // 使用異步通訊服務(wù)
#define IsPackageHeader(x) ((x) == 0xff) // 判斷包頭是不是0xff
#define IsPackageTailer(x, y, z) ((y) <= (z)) // 判斷包的長(zhǎng)度是不是足夠
#endif // _CONFIG_H_
//
// file: Example.c
//
#i nclude <Atmail/At89x52.h>
#i nclude “../Common/Common.h”
#i nclude “../Common/Timer.h”
#i nclude “../Common/Scomm.h”
#i nclude “../Common/KBScan.h”
BIT gbitLampState = 1; // 燈的狀態(tài),缺省為off
static void Initialize()
{
InitTimerModule(); // 初始化時(shí)鐘模塊
InitSCommModule(0xfd, TRUE); // 初始化通訊模塊,11.0592MHz晶振,
// 波特率為19200
EA = 1; // 開(kāi)中斷
}
void main()
{
Initialize(); // 初始化
while(TRUE) // 主循環(huán)
{
ImpTimerService(); // 實(shí)現(xiàn)時(shí)鐘中斷服務(wù),如鍵盤(pán)掃描
AsyncRecePackage(4); // 接收4個(gè)字節(jié)長(zhǎng)的數(shù)據(jù)包
}
}
// 在中斷外部響應(yīng)時(shí)鐘中斷事件
void OnTimerEvent()
{
// do nothing
}
// 控制外部燈
static void TriggerLamp(BIT bEnable)
{
P10 = ~bEnable; // 需要反相控制
}
// 鍵掃描回調(diào)函數(shù)
BYTE KBScan()
{
BIT b;
P11 = 1; // 讀之前拉高引腳電平
b = P11; // 讀入引腳狀態(tài)
return ~b; // 數(shù)據(jù)反相做掃描碼
}
// 計(jì)算校驗(yàn)和
static BYTE CalcCheckSum(BYTE* pbyBuf, BYTE byLen)
{
BYTE by, bySum = 0;
for(by = 0; by < byLen; by++)
bySum += pbyBuf[by];
return 0 – bySum;
}
// 接收到鍵盤(pán)消息回調(diào)函數(shù)
void onKeyPressed(BYTE byValue, BYTE byState)
{
BYTE by[4];
if(byState == 0)
{
switch(byValue)
{
case 0x01:
gbitLampState = ~g bitLampState; // 燈狀態(tài)取反
TriggerLamp(gbitLampState); // 執(zhí)行控制
by[0] = 0xff; // 構(gòu)造數(shù)據(jù)包
by[1] = 0x12;
by[2] = (BYTE)gbitLampState;
by[3] = CalcCheckSum(by, 3); // 求校驗(yàn)和
SendPackage(by, 4); // 發(fā)送數(shù)據(jù)包
break;
// 處理其它掃描碼
default:
break;
}
}
// 接收到數(shù)據(jù)包回調(diào)函數(shù)
void OnRecePackage(BYTE* pbyBuf, BYTE byBufLen)
{
BYTE by[4];
by[0] = 0xff;
by[1] = 0x11;
if(byBufLen != 4 || pbyBuf[3] != CalcCheckSum(pbyBuf, 3))
{
by[2] = 0;
by[3] = CalcCheckSum(by, 3);
SendPackage(by, 4); // 處理長(zhǎng)度或校驗(yàn)和不正確
}
switch(pbyBuf[1])
{
case 0x10:
gbitLampState = (BIT)pbyBuf[2];
TriggerLamp(gbitLampState);
by[2] = 1;
by[3] = CalcCheckSum(by, 3);
SendPackage(by, 4); // 發(fā)送成功執(zhí)行通知
break;
default: // 不知道的命令
by[2] = 0;
by[3] = CalcCheckSum(by, 3);
SendPackage(by, 4); // 發(fā)送沒(méi)有成功執(zhí)行通知
break;