本文檔包括單片機系統(tǒng)中常用到的時鐘中斷、通訊及鍵盤掃描等模塊(見所附源程序)的說明。這些模塊使用前后臺系統(tǒng)模型。為達到最大的靈活性,需要在用戶工程中定義config.h文件,在其中定義各模塊可選參數(shù)的設(shè)置,而不是直接更改源代碼。
這些可選內(nèi)容大部分為宏定義,如果不定義宏相應(yīng)的功能在編譯時被屏蔽,不會增加代碼長度。具體可選內(nèi)容見各模塊中的說明。
在Config.h文件中還要包含一個單片機硬件的資源頭文件。
各模塊使用了定義在Common.h中的一些數(shù)據(jù)類型。如:BIT(bit) BYTE(unsigned char)等,具體請參見源程序。
時鐘模塊
在單片機軟件設(shè)計中,時鐘是重要資源,為了充分利用時鐘資源,故設(shè)計本時鐘模塊。本模塊使用定時器0,在完成用戶指定功能的同時,還能夠自動處理一些其它模塊中與時鐘相關(guān)的信息。
時鐘模塊由聲明文件Timer.h以及實現(xiàn)文件Timer.c組成。
用戶應(yīng)該在Config.h中定義宏TIMER_RELOAD來設(shè)定定時器0的重裝載初值。推薦的定時器0的中斷時間大于1毫秒。
在程序的初始化階段調(diào)用時鐘模塊的初始化函數(shù)InitTimerModule()之后,就可以使用時鐘模塊所以支持的各種功能。具體描述如下:
延時:當(dāng)用戶需要進行一定時間的延時時,可以通過調(diào)用Delay()來進行,參數(shù)為時鐘中斷的次數(shù)。如時鐘中斷周期為1ms,想進行100ms的延時,則可以調(diào)用Delay(100)。
注意:
如果延時的絕對時間小于時鐘中斷的周期,則不能夠用本方法做到延時。
定時:當(dāng)程序中需要使用定時功能時,如等待某外部事件,如果在一定時間內(nèi)發(fā)生則繼續(xù)執(zhí)行,如果在這段時間內(nèi)發(fā)生,則認(rèn)為出現(xiàn)錯誤,轉(zhuǎn)向錯誤處理機制。
在此推薦一種編程模式,但用戶可以用自己認(rèn)為更合理的方式處理此類問題。
這里簡單說明一下關(guān)于阻塞式函數(shù)及非阻塞式函數(shù)。簡單說,阻塞式函數(shù)就是當(dāng)檢測完成條件,如果不能夠完成則等待,如:
void CheckSomething()
{
// gbitSuccessFlag is a global variable
while(gbitSuccessFlag == FALSE)
{
// do nothing but waiting
}
}
可以看到,當(dāng)bitSuccessFlag沒有被設(shè)置為TRUE時,函數(shù)保持等待狀態(tài)不返回,這樣就是阻塞式的函數(shù)。
另外一種情況:
BIT CheckSomething()
{
if(gbitSuccessFlag == TRUE)
{
//…
return TRUE;
}
return FALSE;
}
在這里,如果所檢測的事件有沒有完成,函數(shù)進行檢測之后,立刻返回,通過返回值報告完成情況,如果沒有完成,則等待調(diào)用者分配再次執(zhí)行的機會。這樣的函數(shù)就是非阻塞函數(shù)。
在應(yīng)用定時功能時,首先要將檢測函數(shù)定義成非阻塞函數(shù)。如上面的第二個版本的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
}
或者簡單寫成:
BIT bitDone = FALSE;
ResetClock();
while(GetClock() < MAX_WAITINGTIME && (bitDone = CheckSomething));
if(bitDone == FLASE)
{
//…
}
軟件看門狗:實現(xiàn)具有局限性的看門狗功能。在程序中合適的地方加入對軟件看門狗的復(fù)位函數(shù)ResetWatchDog(),在Config.h中加入宏TIMER_WATCHDOGTIMEOUT。當(dāng)程序運行時,如果在發(fā)生TIMER_WATCHDOGTIMEOUT次時鐘中斷之內(nèi)沒有復(fù)位軟件看門狗,則系統(tǒng)復(fù)位。
注意:
如果沒有加入TIMER_WATCHDOGTIMEOUT宏,程序中的ResetWatchDog沒有任何用處,不用刪除。
如果系統(tǒng)不能實現(xiàn)時鐘中斷,則軟件看門狗也同時失去功能。
目前版本的的時鐘模塊的復(fù)位功能并不是完全復(fù)位,主要表現(xiàn)在當(dāng)復(fù)位之后,系統(tǒng)將不再響應(yīng)任何中斷。所以軟件看門狗只是一個程序的調(diào)試功能,不應(yīng)該將它用于正式工作的程序,此時應(yīng)該使用硬件看門狗。
用戶自定義任務(wù):如果想在時鐘中斷內(nèi)執(zhí)行一些耗時較短的任務(wù),可以定義回調(diào)函數(shù)OnTimerInterrupt。函數(shù)原形為:void OnTimerInterrupt();
如果想在發(fā)生時鐘中斷時執(zhí)行一些功能,而這些功能又耗時相對較長,不合適放在中斷響應(yīng)函數(shù)內(nèi)部,則可以在程序中的主循環(huán)中的任意地方添加: ImpTimerService(),同時提供原形為void OnTimerEvent()的回調(diào)函數(shù)。具體的程序如下所示:
void main()
{
Initialize();
while(TRUE)
{
//…working
ImpTimerService();
//…working
}
}
void OnTimerEvent()
{
// do some task
}
對通訊模塊提供支持:如通訊中的各種超時等,見通訊模塊中的詳細說明。
對鍵盤掃描模塊提供支持:可以自動調(diào)用鍵盤掃描模塊,見鍵盤掃描模塊中的詳細說明。
對程序調(diào)試提供支持:在程序開發(fā)過程中,有時為了判斷程序是不是在工作,常用利用單片機系統(tǒng)的某一空閑引腳通過一個限流電阻接一個發(fā)光二極管,在程序中間隔固定時間交替控制發(fā)光管的明暗。實現(xiàn)這個功能只要在Config.h文件中定義TIMER_FLASHLED宏,如:
#define TIMER_FLASHLED P1_0
則當(dāng)時鐘中斷發(fā)生256次之后,改變發(fā)光管的狀態(tài)。
通訊模塊
串口資源做為單片機與外界通信的常用手段,通訊模塊提供了完全緩沖的串口通訊底層機制,適用于長度不大的數(shù)據(jù)包的發(fā)送及接收。如果處理關(guān)鍵數(shù)據(jù),需要用戶自己提供糾錯協(xié)議。
通訊模塊由聲明文件SComm.h及實現(xiàn)文件SComm.c組成。
初始化:調(diào)用函數(shù)InitSCommModule()來初始化通訊模塊:
void InitSCommModule(BYTE byTimerReload, BIT bitTurbo)
參數(shù)說明:
byTimerReload:定時器1的重裝載初始值。
bitTurob:當(dāng)此參數(shù)為TRUE時,串行通訊在定時器1的溢出速率基礎(chǔ)上加倍。為FALSE時,串行通訊速率為定時器1的溢出速率。
緩沖區(qū):模塊使用了由宏SCOMM_SENDBUFSIZE、SCOMM_RECEBUFSIZE及SCOMM_PKGBUFSIZE所指定長度的三個緩沖區(qū),分別為發(fā)送、接收及數(shù)據(jù)包(用于處理接收到的數(shù)據(jù))緩沖區(qū)(如果沒有使用異步接收功能,則不需要使用數(shù)據(jù)包緩沖區(qū))。
在缺省時,這三個宏都被定義為10,但用戶可以自已按照系統(tǒng)的RAM資源占用情況在Config.h中重定義緩沖區(qū)的大小。需要注意的是,如果緩沖的長度不夠,當(dāng)發(fā)送或接收長數(shù)據(jù)包的時候可能會發(fā)生問題,關(guān)于數(shù)據(jù)緩沖區(qū)的最小值的設(shè)置可以參考下面的說明。
注意:需要盡快取出接收緩沖區(qū)中的數(shù)據(jù),否則當(dāng)緩沖區(qū)滿之后,新的數(shù)據(jù)將被簡單的丟掉。
字節(jié)級服務(wù)函數(shù):在Config.h文件中定義了宏SCOMM_DriverInterface(如:#define SCOMM_DriverInterface),則可以使用字節(jié)級服務(wù)函數(shù),即通訊模塊的底層函數(shù)。
共有兩個函數(shù)可以使用:
void SendByte(BYTE byData);
發(fā)送一個字節(jié),如果當(dāng)前緩沖區(qū)滿,則等待。參數(shù)byData為要發(fā)送的數(shù)據(jù)。
BYTE ReceByte();
接收一個字節(jié),如果當(dāng)前緩沖區(qū)中沒有數(shù)據(jù),則此函數(shù)阻塞,直到接收到數(shù)據(jù)為止。接收到數(shù)據(jù)通過返回值返回。
可以通過調(diào)用IsSendBufEmpty() IsSendBufFull() IsReceBufEmpty() IsReceBufFull() 宏來判斷緩沖區(qū)的空或滿,以防系統(tǒng)阻塞。
不推薦直接使用這一級的服務(wù)函數(shù),應(yīng)該使用高層次上的服務(wù)函數(shù)或者在這一級服務(wù)函數(shù)的基礎(chǔ)上構(gòu)造自己的通訊函數(shù)。
數(shù)據(jù)包級服務(wù)函數(shù):在Config.h文件中定義宏SCOMM_PackageInterface(如: #define SCOMM_PackageInterface)則可以使用數(shù)據(jù)包級服務(wù)函數(shù)。
共有兩個函數(shù)可以使用:
void SendPackage(BYTE* pbyData, BYTE byLen);
發(fā)送數(shù)據(jù)包,參數(shù)pbyData為將要發(fā)送的數(shù)據(jù)包緩沖區(qū)(數(shù)組)的指針,byLen為將要發(fā)送的數(shù)據(jù)包的長度。
當(dāng)沒有定義SCOMM_DriverInterface時,數(shù)據(jù)被完全緩沖。即不能夠發(fā)送長度超過發(fā)送緩沖區(qū)長度的數(shù)據(jù)包。當(dāng)定義了SCOMM_DriverInterface時,采用單字節(jié)發(fā)送,這時不限制需要發(fā)送的數(shù)據(jù)的長度。
BYTE RecePackage(BYTE* pbyData, BYTE byLen);
接收數(shù)據(jù)包,參數(shù)pbyData為存放將要接收的數(shù)據(jù)的緩沖區(qū),byLen為緩沖區(qū)長度。返回值為接收到的字節(jié)數(shù),當(dāng)模塊的接收緩沖區(qū)為空時,函數(shù)非阻塞,立即返回,返回值為零。
同步發(fā)送接收服務(wù)函數(shù):
比如在一個串行總線多機通訊系統(tǒng)中,主機需要定時循檢各從機的狀態(tài),往往是發(fā)一個包含從機地址及指令的數(shù)據(jù)包給從機,之后等待一定的時間,從機需要在這段時間之內(nèi)給主機一個應(yīng)答,如果沒有這個應(yīng)答,則認(rèn)為從機工作狀態(tài)出錯,轉(zhuǎn)去進行相應(yīng)的處理。在這個模型里,主機不能夠不進行等待而給另一臺從機發(fā)送指令,也不能夠不管從機在很久沒有應(yīng)答的情況下繼續(xù)等待。還有一種情況,比如當(dāng)使用485總線進行通信時,如果是兩條通訊線則系統(tǒng)只能工作在半雙工模式下,總線在同一時間內(nèi)只能工作在發(fā)送或接收,為了防止發(fā)送和接收相互干擾,這時的通訊常常需要使用同步發(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ù)包的長度。
這個函數(shù)可以保證等待一個完整的數(shù)據(jù)包完全發(fā)送出去之后,它才返回,在這段時間內(nèi),它會阻塞運行。
BYTE SyncRecePackage(BYTE* pbyBuf, BYTE byBufLen, WORD wTimeout, BYTE byParam);
接收數(shù)據(jù)包。返回值為接收到的數(shù)據(jù)包長度。參數(shù)pbyBuf為將要接收數(shù)據(jù)包的緩沖區(qū)的指針,byBufLen為提