正如我早先說的。這個應用程序所做的兩件事之一是使紅色指示燈閃爍。這可以通過下面的代碼來做到。這里函數flashRed()作為一個任務來執行。然而,如果忽略這一點以及新的函數名,那么這段代碼和第七章“外圍設備”中我們研究過的LED 閃爍函數幾乎是一樣的。在這一級上的唯一差別就是新的頻率(10HZ)和指示燈的顏色(紅色)。
#include "led.h"
#include "timer.h"
/*******************************************************************
*
* Function : flashRed()
*
* Description : Blink the red LED ten times a second.
*
* Notes : This outer loop is hardware-independent, However, it
* calls the hardware-dependent function toggleLed().
*
* Returns : This routine contains an infinite loop.
*
*******************************************************************/
void
flashRed(void)
{
Timer timer;
timer.start(50, Periodic); // Start a periodic 50 ms timer.
while(1)
{
toggleLed(LED_RED); // Toggle the red LED.
timer.waitfor(); // Wait for the timer to expire.
}
} /* flashRed() */
LED 閃爍程序主要的改變在這段代碼中是看不見的。這些主要的變化是對toggleLed()函數和Timer 類進行了修改以兼容多任務的環境。toggleLed()函數就是我現在稱之為指示燈驅動的東西。一旦你開始這樣考慮這個問題,tb也許會考慮把驅動重寫成一個C++類,并已加入新的方法以明確地設置和清除指示燈,這個方法也是講得通的。但是,采用和第七章一樣的實現方法,并且僅使用一個互斥體來保護P2LTCH 寄存器不被一個以上的任務同時訪問,就已經足夠了(注1)。這里是修改后的代碼:
#include "i8018xEB.h"
#include "adeos.h"
static Mutex gLedMutex;
/*******************************************************************
*
* Function : toggleLed()
*
* Description : Toggle the state of one or both LEDs.
*
* Notes : This version is ready for multitasking.
*
* Returns : None defined.
*
*******************************************************************/
void
toggleLed(unsigned char ledMask)
{
gLedMutex.take();
// Read P2LTCH, modify its value, and write the result.
//
gProcessor.pPCB->ioPort[1].latch ^= ledMask;
gLedMutex.release();
} /* toggleLed() */
注 1:在早先的那個toggleLed()函數中存在競爭的情況。為了理解這一點,返回去查看這段代碼,并且假想兩個任務在共享指示燈,第一個任務恰好調用了那個切換紅色指示燈的函數,突然之間,第一個任務被第二個任務占先,此時,在toggleLed()函數中,指示燈的狀態都被讀入并存入一個處理器的寄存器。現在第二個任務導致兩個指示燈的狀態都被再次讀入并且存入另一個處理器的寄存器,然后兩個指示燈的狀態都被修改以改變綠色指示燈的狀態,并且導致結果寫出到P2LTCH 寄存器。當中斷的任務重新啟動時,它已經有一個指示燈狀態的拷貝。但是這個拷貝不再準確了。當發生這個變化后,指示燈變成紅燈,并且比新的指示燈狀態寫到P2LTCH 寄存器。這樣,第二個任務的改變將會被撤銷。加入一個互斥體可以消除這個潛在的危險。
第七章中的時鐘驅動程序在應用于一個多任務的環境之前,tbw必須對它做類似的改動。然而這時不存在競爭的情況(注2)。我們需要利用一個互斥體來消除waitfor 方法中的輪詢。通過把一個互斥體和每一個軟件時鐘關聯,我們可以使任何一個在等待時鐘的任務休眠,因此,釋放了處理器以執行就緒的低優先級的任務。當等待的時鐘到期了,休眠的任務會被操作系統喚起。為此,一個指向互斥體的指針,叫做 pMutex,被加入到類的定義中:
class Timer
{
public:
Timer();
~Timer();
int start(unsigned int nMilliseconds, TimerType = OneShot);
int waitfor();
void cancel();
TimerState state;
TimerType type;
unsigned int length;
Mutex * pMutex;
unsigned int count;
Timer * pNext;
private:
static void interrupt Interrupt();
};
每次構造函數創建軟件時鐘的時候,這個指針被初始化。此后,無論何時一個時鐘對象被啟動,它的互斥體可以像下面這樣獲得:
——————————————————————————————————
注 2:記得時鐘硬件只初始化一次(在第一次構造函數請求的時候),并且此后時鐘專用寄存器只能由一個函數(即中斷服務例程)來讀寫。
/*******************************************************************
*
* Function : start()
*
* Description : Start a software timer, based on the tick from the
* underlying hardware timer.
*
* Notes : This version is ready for multitasking.
*
* Returns : 0 on success, -1 if the timer is already in use.
*
*******************************************************************/
int
Timer::start(unsigned int nMilliseconds, TimerType timerType)
{
if(state != Idle)
{
return(-1);
}
//
// Take the mutex. It will be released when the timer expires.
//
pMutex->take();
//
// Initialize the software timer.
//
state = Active;
type = timerType;
length = nMilliseconds / MS_PER_TICK;
//
// Add this timer to the active timer list.
//
timerList.insert(this);
return(0);
} /* start() */
通過在時鐘啟動的時候獲得互斥體,我們可以保證在同一個互斥體釋放之前沒有其他任務(甚至是啟動時鐘的任務)能夠再獲得它。并且這在時鐘自然到期(中斷服務程序)或是人為取消(通過取消的辦法)之前是不會發生的。所以在waitfor()中的輪詢可以由pMutex->take()來替換如下:
/*******************************************************************
*
* Function : waitfor()
*
* Description : Wait for the software timer to finish.
*
* Notes : This version is ready for multitasking.
*
* Returns : 0 on success, -1 if the timer is not running.
*
*******************************************************************/
int
Timer::waitfor()
{
if(state != Active)
{
return(-1);
}
//
// Wait for the timer to expire.
//
pMutex->take();
//
// Restart or idle the timer, depending on its type.
//
if(type == Periodic)
{
state == Active;
timerList.insert(this);
}
else
{
pMutex->release();
state = Idle;
}
return(0);
} /* waitfor() */
當時鐘最后到期時,中斷服務程序將會釋放互斥體,調用的任務會在waitfor()中被喚起。喚起的過程中,互斥體已經為下一個運行的時鐘獲得。互斥體只有當時鐘是OneShot 類型時才會被釋放,因此,是不會自動重啟的。