C語言嵌入式系統編程軟件架構篇

模塊劃分程序員


模塊劃分的"劃"是規劃的意思,意指怎樣合理的將一個很大的軟件劃分爲一系列功能獨立的部分合做完成系統的需求。C語言做爲一種結構化的程序設計語言,在模塊的劃分上主要依據功能(依功能進行劃分在面向對象設計中成爲一個錯誤,牛頓定律遇到了>相對論), C語言模塊化程序設計需理解以下概念:編程


(1) 模塊便是一個.c文件和一個.h文件的結合,頭文件(.h)中是對於該模塊接口的聲明;多線程

(2) 某模塊提供給其它模塊調用的外部函數及數據需在.h中文件中冠以extern關鍵字聲明;架構

(3) 模塊內的函數和全局變量需在.c文件開頭冠以static關鍵字聲明;併發

(4) 永遠不要在.h文件中定義變量!定義變量和聲明變量的區別在於定義會產生內存分配的操做,是彙編階段的概念;而聲明則只是告訴包含該聲明的模塊在鏈接階段從其它模塊尋找外部函數和變量。如:框架


/*module1.h*/模塊化

int a = 5; /* 在模塊1的.h文件中定義int a */函數


/*module1 .c*/性能

#include "module1.h" /* 在模塊1中包含模塊1的.h文件 */測試


/*module2 .c*/

#include "module1.h" /* 在模塊2中包含模塊1的.h文件 */


/*module3 .c*/

#include "module1.h" /* 在模塊3中包含模塊1的.h文件 */


以上程序的結果是在模塊一、二、3中都定義了整型變量a,a在不一樣的模塊中對應不一樣的地址單元,這個世界上歷來不須要這樣的程序。正確的作法是:


/*module1.h*/

extern int a; /* 在模塊1的.h文件中聲明int a */


/*module1 .c*/

#include "module1.h" /* 在模塊1中包含模塊1的.h文件 */

int a = 5; /* 在模塊1的.c文件中定義int a */


/*module2 .c*/

#include "module1.h" /* 在模塊2中包含模塊1的.h文件 */


/*module3 .c*/

#include "module1.h" /* 在模塊3中包含模塊1的.h文件 */


這樣若是模塊一、二、3操做a的話,對應的是同一片內存單元。一個嵌入式系統一般包括兩類模塊:


(1)硬件驅動模塊,一種特定硬件對應一個模塊;

(2)軟件功能模塊,其模塊的劃分應知足低偶合、高內聚的要求。


多任務仍是單任務


所謂"單任務系統"是指該系統不能支持多任務併發操做,宏觀串行地執行一個任務。而多任務系統則能夠宏觀並行(微觀上可能串行)地"同時"執行多個任務。


多任務的併發執行一般依賴於一個多任務操做系統(OS),多任務OS的核心是系統調度器,它使用任務控制塊(TCB)來管理任務調度功能。TCB包括任務的當前狀態、優先級、要等待的事件或資源、任務程序碼的起始地址、初始堆棧指針等信息。調度器在任務被激活時,要用到這些信息。此外,TCB還被用來存聽任務的"上下文"(context)。任務的上下文就是當一個執行中的任務被中止時,所要保存的全部信息。一般,上下文就是計算機當前的狀態,也即各個寄存器的內容。當發生任務切換時,當前運行的任務的上下文被存入TCB,並將要被執行的任務的上下文從它的TCB中取出,放入各個寄存器中。


嵌入式多任務OS的典型例子有Vxworks、ucLinux等。嵌入式OS並不是高不可攀的神壇之物,咱們能夠用不到1000行代碼實現一個針對80186處理器的功能最簡單的OS內核,做者正準備進行此項工做,但願能將心得貢獻給你們。


究竟選擇多任務仍是單任務方式,依賴於軟件的體系是否龐大。例如,絕大多數手機程序都是多任務的,但也有一些小靈通的協議棧是單任務的,沒有操做系統,它們的主程序輪流調用各個軟件模塊的處理程序,模擬多任務環境。


單任務程序典型架構


(1)從CPU復位時的指定地址開始執行;

(2)跳轉至彙編代碼startup處執行;

(3)跳轉至用戶主程序main執行,在main中完成:

a.初試化各硬件設備;

b.初始化各軟件模塊;

c.進入死循環(無限循環),調用各模塊的處理函數



用戶主程序和各模塊的處理函數都以C語言完成。用戶主程序最後都進入了一個死循環,其首選方案是:


while(1)

{

}


有的程序員這樣寫:


for(;;)

{

}


這個語法沒有確切表達代碼的含義,咱們從for(;;)看不出什麼,只有弄明白for(;;)在C語言中意味着無條件循環才明白其意。下面是幾個"著名"的死循環:


(1)操做系統是死循環;

(2)WIN32程序是死循環;

(3)嵌入式系統軟件是死循環;

(4)多線程程序的線程處理函數是死循環。


你可能會辯駁,大聲說:"凡事都不是絕對的,二、三、4均可以不是死循環"。Yes,you areright,可是你得不到鮮花和掌聲。實際上,這是一個沒有太大意義的牛角尖,由於這個世界歷來不須要一個處理完幾個消息就喊着要OS殺死它的WIN32程序,不須要一個剛開始RUN就自行了斷的嵌入式系統,不須要莫名其妙啓動一個作一點事就幹掉本身的線程。有時候,過於嚴謹製造的不是便利而是麻煩。君不見,五層的TCP/IP協議棧超越嚴謹的ISO/OSI七層協議棧大行其道成爲事實上的標準?


常常有網友討論:


printf("%d,%d",++i,i++); /* 輸出是什麼?*/

c = a+++b; /* c=? */


等相似問題。面對這些問題,咱們只能發出由衷的感慨:世界上還有不少有意義的事情等着咱們去消化攝入的食物。實際上,嵌入式系統要運行到世界末日。


中斷服務程序


中斷是嵌入式系統中重要的組成部分,可是在標準C中不包含中斷。許多編譯開發商在標準C上增長了對中斷的支持,提供新的關鍵字用於標示中斷服務程序(ISR),相似於__interrupt、#programinterrupt等。當一個函數被定義爲ISR的時候,編譯器會自動爲該函數增長中斷服務程序所須要的中斷現場入棧和出棧代碼。


中斷服務程序須要知足以下要求:


(1)不能返回值;

(2)不能向ISR傳遞參數;

(3) ISR應該儘量的短小精悍;

(4) printf(char * lpFormatString,…)函數會帶來重入和性能問題,不能在ISR中採用。


在某項目的開發中,咱們設計了一個隊列,在中斷服務程序中,只是將中斷類型添加入該隊列中,在主程序的死循環中不斷掃描中斷隊列是否有中斷,有則取出隊列中的第一個中斷類型,進行相應處理。


/* 存放中斷的隊列 */

typedef struct tagIntQueue

{

 int intType; /* 中斷類型 */

 struct tagIntQueue *next;

}IntQueue;


IntQueue lpIntQueueHead;


__interrupt ISRexample ()

{

 int intType;

 intType = GetSystemType();

 QueueAddTail(lpIntQueueHead, intType);/* 在隊列尾加入新的中斷 */

}


在主程序循環中判斷是否有中斷:


While(1)

{

 If( !IsIntQueueEmpty() )

 {

  intType = GetFirstInt();

  switch(intType) /* 是否是很象WIN32程序的消息解析函數? */

  {

   /* 對,咱們的中斷類型解析很相似於消息驅動 */

   case xxx: /* 咱們稱其爲"中斷驅動"吧? */

    …

    break;

   case xxx:

    …

    break;

   …

  }

 }

}


按上述方法設計的中斷服務程序很小,實際的工做都交由主程序執行了。


硬件驅動模塊


一個硬件驅動模塊一般應包括以下函數:


(1)中斷服務程序ISR

(2)硬件初始化

a.修改寄存器,設置硬件參數(如UART應設置其波特率,AD/DA設備應設置其採樣速率等);

b.將中斷服務程序入口地址寫入中斷向量表:



/* 設置中斷向量表 */

m_myPtr = make_far_pointer(0l); /* 返回void far型指針void far * */

m_myPtr += ITYPE_UART; /* ITYPE_UART: uart中斷服務程序 */

/* 相對於中斷向量表首地址的偏移 */

*m_myPtr = &UART _Isr; /* UART _Isr:UART的中斷服務程序 */


(3)設置CPU針對該硬件的控制線

a.若是控制線可做PIO(可編程I/O)和控制信號用,則設置CPU內部對應寄存器使其做爲控制信號;

b.設置CPU內部的針對該設備的中斷屏蔽位,設置中斷方式(電平觸發仍是邊緣觸發)。



(4)提供一系列針對該設備的操做接口函數。例如,對於LCD,其驅動模塊應提供繪製像素、畫線、繪製矩陣、顯示字符點陣等函數;而對於實時鐘,其驅動模塊則需提供獲取時間、設置時間等函數。


C的面向對象化


在面向對象的語言裏面,出現了類的概念。類是對特定數據的特定操做的集合體。類包含了兩個範疇:數據和操做。而C語言中的struct僅僅是數據的集合,咱們能夠利用函數指針將struct模擬爲一個包含數據和操做的"類"。下面的C程序模擬了一個最簡單的"類":


#ifndef C_Class

#define C_Class struct

#endif

C_Class A

{

 C_Class A *A_this; /* this指針 */

 void (*Foo)(C_Class A *A_this); /* 行爲:函數指針 */

 int a; /* 數據 */

 int b;

};


咱們能夠利用C語言模擬出面向對象的三個特性:封裝、繼承和多態,可是更多的時候,咱們只是須要將數據與行爲封裝以解決軟件結構混亂的問題。C模擬面向對象思想的目的不在於模擬行爲自己,而在於解決某些狀況下使用C語言編程時程序總體框架結構分散、數據和函數脫節的問題。咱們在後續章節會看到這樣的例子。


總結


本篇介紹了嵌入式系統編程軟件架構方面的知識,主要包括模塊劃分、多任務仍是單任務選取、單任務程序典型架構、中斷服務程序、硬件驅動模塊設計等,從宏觀上給出了一個嵌入式系統軟件所包含的主要元素。


請記住:軟件結構是軟件的靈魂!結構混亂的程序面目可憎,調試、測試、維護、升級都極度困難。

相關文章
相關標籤/搜索