第一章:範例


在這一章裏將提供三個範例來說明如何使用 µC/OS-II。筆者之所以在本書一開始就寫這一章是爲了讓讀者儘快開始使用 µC/OS-II。在開始講述這些例子之前,筆者想先說明一些在這本書裏的約定。


這些例子曾經用Borland C/C++ 編譯器(V3.1)編譯過,用選擇項産生Intel/AMD80186處理器(大模式下編譯)的代碼。這些代碼實際上是在Intel Pentium II PC 300MHz)上運行和測試過,Intel Pentium II PC可以看成是特別快的80186。筆者選擇PC做爲目標系統是由於以下幾個原因:首先也是最爲重要的,以PC做爲目標系統比起以其他嵌入式環境,如評估板,模擬器等,更容易進行代碼的測試,不用不斷地燒寫EPROM,不斷地向EPROM模擬器中下載程式等等。用戶只需要簡單地編譯、鏈結和執行。其次,使用Borland C/C++産生的80186的目標代碼(實模式,在大模式下編譯)與所有IntelAMDCyrix公司的80x86 CPU相容。


1.00 安裝 µC/OS-II


本書附帶一張軟碟包括了所有我們討論的源代碼。是假定讀者在80x86Pentium,或者Pentium-II處理器上運行DOSWindows95。至少需要5Mb硬碟空間來安裝uC/OS-II


請按照以下步驟安裝:


1.進入到DOS(或在Windows 95下打開DOS窗口)並且指定C:爲默認驅動器。


2.將磁片插入到A:驅動器。


3.鍵入 AINSTALL drive


注意『drive』是讀者想要將µC/OSII安裝的目標磁片的盤符。


INSTALL.BAT 是一個DOS的批次檔案,位於磁片的根目錄下。它會自動在讀者指定的目標驅動器中建立\SOFTWARE目錄並且將uCOS-II.EXE文件從A:驅動器複製到\SOFTWARE並且運行。µC/OSII將在\SOFTWARE目錄下添加所有的目錄和文件。完成之後INSTALL.BAT將刪除uCOS-II.EXE並且將目錄改爲\SOFTWARE\uCOS-II\EX1_x86L,第一個例子就存放在這裏。


在安裝之前請一定閱讀一下READ.ME文件。當INSTALL.BAT已經完成時,用戶的目標目錄下應該有一下子目錄:




  • \SOFTWARE



這是根目錄,是所有軟體相關的文件都放在這個目錄下。






  • \SOFTWARE\BLOCKS



副程式模組目錄。筆者將例子中µC/OS-II用到的與PC相關的函數模組編譯以後放在這個目錄下。






  • \SOFTWARE\HPLISTC



這個目錄中存放的是與範例HPLIST相關的文件(請看附錄DHPLISTCTO)。HPLIST.C存放在\SOFTWARE\HPLISTC\SOURCE目錄下。DOS下的可執行文件(HPLIST.EXE)存放在\SOFTWARE\TO\EXE中。






  • \SOFTWARE\TO



這個目錄中存放的是和範例TO相關的文件(請看附錄DHPLISTCTO)。原始檔案TO.C存放在\SOFTWARE\TO\SOURCE中,DOS下的可執行文件(TO.EXE)存放在\SOFTWARE\TO\EXE中。注意TO需要一個TO.TBL文件,它必須放在根目錄下。用戶可以在\SOFTWARE\TO\EXE目錄下找到TO.TBL文件。如果要運行TO.EXE,必須將TO.TBL複製到根目錄下。






  • \SOFTWARE\uCOS-II



µC/OS-II 相關的文件都放在這個目錄下。






  • \SOFTWARE\uCOS-II\EX1_x86L



這個目錄裏包括例1的源代碼(參見 1.07, 1),可以在DOS(或Windows 95下的DOS窗口)下運行。






  • \SOFTWARE\uCOS-II\EX2_x86L



這個目錄裏包括例2的源代碼(參見 1.08, 2),可以在DOS(或Windows 95下的DOS窗口)下運行。






  • \SOFTWARE\uCOS-II\EX3_x86L



這個目錄裏包括例3的源代碼(參見 1.09, 3),可以在DOS(或Windows 95下的DOS窗口)下運行。






  • \SOFTWARE\uCOS-II\Ix86L



這個目錄下包括依賴於處理器類型的代碼。此時是爲在80x86處理器上運行uC/OS-II而必須的一些代碼,實模式,在大模式下編譯。






  • \SOFTWARE\uCOS-II\SOURCE



這個目錄裏包括與處理器類型無關的源代碼。這些代碼完全可移植到其他架構的處理器上。


1.01 INCLUDES.H


用戶將注意到本書中所有的 *.C 文件都包括了以下定義:








#include "includes.h"





INCLUDE.H可以使用戶不必在工程項目中每個*.C文件中都考慮需要什麽樣的頭文件。換句話說,INCLUDE.H是主頭文件。這樣做唯一的缺點是INCLUDES.H中許多頭文件在一些*.C文件的編譯中是不需要的。這意味著逐個編譯這些文件要花費額外的時間。這雖有些不便,但代碼的可攜性卻增加了。本書中所有的例子使用一個共同的頭文件INCLUDES.H3個副本分別存放在\SOFTWARE\uCOS-II\EX1_x86L\SOFTWARE\uCOS-II\EX2_x86L,以及\SOFTWARE\uCOS-II\EX3_x86L 中。當然可以重新編輯INCLUDES.H以添加用戶自己的頭文件。


1.02不依賴於編譯的資料類型


因爲不同的微處理器有不同的字長,µC/OS-II的移植文件包括很多類型定義以確保可攜性(參見\SOFTWARE\uCOS-II\Ix86L\OS_CPU.H,它是針對80x86的實模式,在大模式下編譯)。µCOS-II不使用C語言中的short,int,long等資料類型的定義,因爲它們與處理器類型有關,隱含著不可攜性。筆者代之以移植性強的整數資料類型,這樣,既直觀又可移植,如表L1.1所示。爲了方便起見,還定義了浮點數資料類型,雖然µC/OS-II中沒有使用浮點數。










































程式清單 L1.1 可移植型資料類型。



Typedef unsigned char BOOLEAN;



Typedef unsigned char INT8U;



Typedef signed char INT8S;



Typedef unsigned int INT16U;



Typedef signed int INT16S;



Typedef unsigned long INT32U;



Typedef signed long INT32S;



Typedef float FP32;



Typedef double FP64;






#define BYTE INT8S



#define UBYTE INT8U



#define WORD INT16S



#define UWORD INT16U



#define LONG INT32S



#define ULONG INT32U







INT16U資料類型爲例,它代表16位元無符號整數資料類型。µC/OS-II和用戶的應用代碼可以定義這種類型的資料,範圍從065,535。如果將µCO/S-II移植到32位處理器中,那就意味著INT16U不再不是一個無符號整型資料,而是一個無符號短整型資料。然而將無論µC/OS-II用到哪里,都會當作INT16U處理。表1.1是以Borland C/C++編譯器爲例,爲80x86提供的定義語句。爲了和µC/OS相容,還定義了BYTE,WORD,LONG以及相應的無符號變數。這使得用戶可以不作任何修改就能將µC/OS的代碼移植到µC/OS-II中。之所以這樣做是因爲筆者覺得這種新的資料類型定義有更多的靈活性,也更加易讀易懂。對一些人來說,WORD意味著32位數,而此處卻意味著16位數。這些新的資料類型應該能夠消除此類含混不請




1.03總體變數


以下是如何定義總體變數。衆所周知,總體變數應該是得到記憶體分配且可以被其他模組通過C語言中extern關鍵字調用的變數。因此,必須在 .C .H 文件中定義。這種重復的定義很容易導致錯誤。以下討論的方法只需用在頭文件中定義一次。雖然有點不易懂,但用戶一旦掌握,使用起來卻很靈活。表1.2中的定義出現在定義所有總體變數的.H頭文件中。


















程式清單 L 1.2 定義全局宏。



#ifdef xxx_GLOBALS



#define xxx_EXT



#else



#define xxx_EXT extern



#endif




.H 文件中每個總體變數都加上了xxx_EXT的字首。xxx代表模組的名字。該模組的.C文件中有以下定義:










#define xxx_GLOBALS



#include "includes.h"




當編譯器處理.C文件時,它強制xxx_EXT(在相應.H文件中可以找到)爲空,(因爲xxx_GLOBALS已經定義)。所以編譯器給每個總體變數分配記憶體空間,而當編譯器處理其他.C文件時,xxx_GLOBAL沒有定義,xxx_EXT被定義爲extern,這樣用戶就可以調用外部總體變數。爲了說明這個概念,可以參見uC/OS_II.H,其中包括以下定義:
























#ifdef OS_GLOBALS



#define OS_EXT



#else



#define OS_EXT extern



#endif






OS_EXT INT32U OSIdleCtr;



OS_EXT INT32U OSIdleCtrRun;



OS_EXT INT32U OSIdleCtrMax;





同時,uCOS_II.H有中以下定義:










#define OS_GLOBALS



#include “includes.h”





當編譯器處理uCOS_II.C時,它使得頭文件變成如下所示,因爲OS_EXT被設置爲空。












INT32U OSIdleCtr;



INT32U OSIdleCtrRun;



INT32U OSIdleCtrMax;




這樣編譯器就會將這些總體變數分配在記憶體中。當編譯器處理其他.C文件時,頭文件變成了如下的樣子,因爲OS_GLOBAL沒有定義,所以OS_EXT被定義爲extern












extern INT32U OSIdleCtr;



extern INT32U OSIdleCtrRun;



extern INT32U OSIdleCtrMax;




在這種情況下,不産生記憶體分配,而任何 .C文件都可以使用這些變數。這樣的就只需在 .H 文件中定義一次就可以了。


1.04 OS_ENTER_CRITICAL()


OS_EXIT_CRITICAL()


用戶會看到,調用OS_ENTER_CRITICAL()OS_EXIT_CRITICAL()兩個宏,貫穿本書的所有源代碼。OS_ENTER_CRITICAL() 關中斷;而OS_EXIT_CRITICAL()開中斷。關中斷和開中斷是爲了保護臨界段代碼。這些代碼很顯然與處理器有關。關於宏的定義可以在OS_CPU.H中找到。9.03.02節詳細討論定義這些宏的兩種方法。






























程式清單 L 1.3 進入正確部分的巨集。



#define OS_CRITICAL_METHOD 2






#if OS_CRITICAL_METHOD == 1



#define OS_ENTER_CRITICAL() asm CLI



#define OS_EXIT_CRITICAL() asm STI



#endif






#if OS_CRITICAL_METHOD == 2



#define OS_ENTER_CRITICAL() asm {PUSHF; CLI}



#define OS_EXIT_CRITICAL() asm POPF



#endif





用戶的應用代碼可以使用這兩個宏來開中斷和關中斷。很明顯,關中斷會影響中斷延遲,所以要特別小心。用戶還可以用信號量來保護林階段代碼。


1.05基於PC的服務


PC.C 文件和 PC.H 文件(在\SOFTWARE\BLOCKS\PC\SOURCE目錄下)是筆者在範例中使用到的一些基於PC的服務程式。與 µC/OS-II 以前的版本(即 µC/OS)不同,筆者希望集中這些函數以避免在各個例子中都重復定義,也更容易適應不同的編譯器。PC.C包括字元顯示,時間度量和其他各種服務。所有的函數都以PC_爲字首。


1.05.01字元顯示


爲了性能更好,顯示函數直接向顯示記憶體區中寫資料。在VGA顯示器中,顯示記憶體從絕對位址0x000B8000開始(或用段、偏移量表示則爲B800:0000)。在單色顯示器中,用戶可以把#define constant DISP_BASE0xB800改爲0xB000


PC.C中的顯示函數用xy座標來直接向顯示記憶體中寫ASCII字元。PC的顯示可以達到2580列一共2,000個字元。每個字元需要兩個位元組來顯示。第一個位元組是用戶想要顯示的字元,第二個位元組用來確定前景色和背景色。前景色用低四位來表示,背景色用第4位到6位來表示。最高位元表示這個字元是否閃爍,(1)表示閃爍,(0)表示不閃爍。用PC.H#defien constants定義前景和背景色,PC.C包括以下四個函數:




PC_DispClrScr() Clear the screen


PC_DispClrLine() Clear a single row (or line)


PC_DispChar() Display a single ASCII character anywhere on the screen


PC_DispStr() Display an ASCII string anywhere on the screen




1.05.02花費時間的測量


時間測量函數主要用於測試一個函數的運行花了多少時間。測量時間是用PC82C54計時器2。被測的程式碼是放在函數PC_ElapsedStart()PC_ElapsedStop()之間來測量的。在用這兩個函數之前,應該調用PC_ElapsedInit()來初始化,它主要是計算運行這兩個函數本身所附加的的時間。這樣,PC_ElapsedStop()函數中返回的數值就是準確的測量結果了。注意,這兩個函數都不具備可重入性,所以,必須小心,不要有多個任務同時調用這兩個函數。表1.4說明了如何測量PC_DisplayChar()的執行時間。注意,時間是以uS爲單位的。
























程式清單 L 1.4 測量代碼執行時間。



INT16U time;






PC_ElapsedInit();



.



.



PC_ElapsedStart();



PC_DispChar(40, 24, ‘A’, DISP_FGND_WHITE);



time = PC_ElapsedStop();





1.05.03其他函數


µC/OS-II的應用程式和其他DOS應用程式是一樣的,換句話說,用戶可以像在DOS下編譯其他單線程的程式一樣編譯和鏈結用戶程式。所生成的.EXE程式可以在DOS下裝載和運行,當然應用程式應該從main()函數開始。因爲µC/OS-II 是多工,而且爲每個任務開闢一個堆疊,所以單線程的DOS環境應該保存,在退出µC/OS-II 程式時返回到DOS。調用PC_DOSSaveReturn()可以保存當前DOS環境,而調用PC_DOSReturn()可以返回到DOSPC.C中使用ANSI Csetjmp(),longjmp()函數來分別保存和恢復DOS環境。Borland C/C++編譯庫提供這些函數,多數其他的編譯程序也應有這類函數。


應該注意到無論是應用程式的錯誤還是只調用exit(0)而沒有調用PC_DOSReturn()函數都會使DOS環境被破壞,從而導致DOSWINDOWS95下的DOS窗口崩潰。


調用PC_GetDateTime()函數可得到PC中的日期和時間,並且以SACII字串形式返回。格式是MM-DD-YY HH:MM:SS,用戶需要19個字元來存放這些資料。該函數使用了Borland C/C++gettime()getdate()函數,其他DOS環境下的C編譯應該也有類似函數。


PC_GetKey() 函數檢查是否有按鍵被按下。如果有按鍵被按下,函數返回其值。這個函數使用了Borland C/C++kbhit()getch()函數,其他DOS環境下的C編譯應該也有類似函數。


函數PC_SetTickRate()允許用戶爲 µC /OS-II定義頻率,以改變鍾節拍的速率。在DOS下,每秒産生18.20648次時鐘節拍,或每隔54.925ms一次。這是因爲82C54計時器晶片沒有初始化,而使用預設值65,535的結果。如果初始化爲58,659,那麽時鐘節拍的速率就會精確地爲20.000Hz。筆者決定將時鐘節拍設得更快一些,用的是200Hz(實際是上是 199.9966Hz)。注意OS_CPU_A.ASM中的OSTickISR()函數將會每11個時鐘節拍調用一次DOS中的時鐘節拍處理,這是爲了保證在DOS下時鐘的準確性。如果用戶希望將時鐘節拍的速度設置爲20HZ,就必須這樣做。在返回DOS以前,要調用PC_SetTickRate(),並設置18爲目標頻率,PC_SetTickRate()就會知道用戶要設置爲18.2Hz,並且會正確設置82C54


PC.C中最後兩個函數是得到和設置中斷向量,筆者是用Borland C/C++中的庫函數來完成的,但是PC_VectGet()PC_VectSet()很容易改寫,以適用於其他編譯器。

arrow
arrow
    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()