欧美色欧美亚洲高清在线观看,国产特黄特色a级在线视频,国产一区视频一区欧美,亚洲成a 人在线观看中文

  1. <ul id="fwlom"></ul>

    <object id="fwlom"></object>

    <span id="fwlom"></span><dfn id="fwlom"></dfn>

      <object id="fwlom"></object>

      C語言 C++ volatile關鍵字作用與嵌入式系統(tǒng)程序員(精選五篇)

      時間:2019-05-12 21:03:42下載本文作者:會員上傳
      簡介:寫寫幫文庫小編為你整理了多篇相關的《C語言 C++ volatile關鍵字作用與嵌入式系統(tǒng)程序員》,但愿對你工作學習有幫助,當然你在寫寫幫文庫還可以找到更多《C語言 C++ volatile關鍵字作用與嵌入式系統(tǒng)程序員》。

      第一篇:C語言 C++ volatile關鍵字作用與嵌入式系統(tǒng)程序員

      C語言 C++ volatile關鍵字作用與嵌入式系統(tǒng)程序員

      volatile關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改。

      用volatile關鍵字聲明的變量i每一次被訪問時,執(zhí)行部件都會從i相應的內(nèi)存單元中取出i的值。

      沒有用volatile關鍵字聲明的變量i在被訪問的時候可能直接從cpu的寄存器中取值(因為之前i被訪問過,也就是說之前就從內(nèi)存中取出i的值保存到某個寄存器中),之所以直接從寄存器中取值,而不去內(nèi)存中取值,是因為編譯器優(yōu)化代碼的結果(訪問cpu寄存器比訪問ram快的多)。

      以上兩種情況的區(qū)別在于被編譯成匯編代碼之后,兩者是不一樣的。之所以這樣做是因為變量i可能會經(jīng)常變化,保證對特殊地址的穩(wěn)定訪問。

      volatile關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統(tǒng)、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優(yōu)化,從而可以提供對特殊地址的穩(wěn)定訪問。

      使用該關鍵字的例子如下:

      int volatile nVint;

      當要求使用volatile 聲明的變量的值的時候,系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù),即使它前面的指令剛剛從該處讀取過數(shù)據(jù)。而且讀取的數(shù)據(jù)立刻被保存。

      例如:

      volatile int i=10;

      int a = i;

      ...//其他代碼,并未明確告訴編譯器,對i進行過操作

      int b = i;

      volatile 指出 i是隨時可能發(fā)生變化的,每次使用它的時候必須從i的地址中讀取,因而編譯器生成的匯編代碼會重新從i的地址讀取數(shù)據(jù)放在b中。而優(yōu)化做法是,由于編譯器發(fā)現(xiàn)兩次從i讀數(shù)據(jù)的代碼之間的代碼沒有對i進行過操作,它會自動把上次讀的數(shù)據(jù)放在b中。而不是重新從i里面讀。這樣以來,如果i是一個寄存器變量或者表示一個端口數(shù)據(jù)就容易出錯,所以說volatile可以保證對特殊地址的穩(wěn)定訪問。

      注意,在vc6中,一般調(diào)試模式?jīng)]有進行代碼優(yōu)化,所以這個關鍵字的作用看不出來。下面通過插入?yún)R編代碼,測試有無volatile關鍵字,對程序最終代碼的影響:

      首先,用classwizard建一個win32 console工程,插入一個voltest.cpp文件,輸入下面的代碼:

      #i nclude

      void main()

      {

      int i=10;

      int a = i;

      printf(“i= %dn”,a);

      //下面匯編語句的作用就是改變內(nèi)存中i的值,但是又不讓編譯器知道

      __asm {

      mov dword ptr [ebp-4], 20h

      }

      int b = i;

      printf(“i= %dn”,b);

      }

      然后,在調(diào)試版本模式運行程序,輸出結果如下:

      i = 10

      i = 32

      然后,在release版本模式運行程序,輸出結果如下:

      i = 10

      i = 10

      輸出的結果明顯表明,release模式下,編譯器對代碼進行了優(yōu)化,第二次沒有輸出正確的i值。

      下面,我們把 i的聲明加上volatile關鍵字,看看有什么變化:

      #i nclude

      void main()

      {

      volatile int i=10;

      int a = i;

      printf(“i= %dn”,a);

      __asm {

      mov dword ptr [ebp-4], 20h

      }

      int b = i;

      printf(“i= %dn”,b);

      }

      分別在調(diào)試版本和release版本運行程序,輸出都是:

      i = 10

      i = 32

      這說明這個關鍵字發(fā)揮了它的作用!

      文章一:

      講講volatile的作用

      一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優(yōu)化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個例子: 1).并行設備的硬件寄存器(如:狀態(tài)寄存器)

      2).一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)3).多線程應用中被幾個任務共享的變量 回答不出這個問題的人是不會被雇傭的。我認為這是區(qū)分C程序員和嵌入式系統(tǒng)程序員的最基本的問題。嵌入式系統(tǒng)程序員經(jīng)常同硬件、中斷、RTOS等等打交道,所用這些都要求volatile變量。不懂得volatile內(nèi)容將會帶來災難。

      假設被面試者正確地回答了這是問題(嗯,懷疑這否會是這樣),我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。

      1).一個參數(shù)既可以是const還可以是volatile嗎?解釋為什么。2).一個指針可以是volatile 嗎?解釋為什么。3).下面的函數(shù)有什么錯誤:

      int square(volatile int *ptr){ return *ptr * *ptr;} 下面是答案:

      1).是的。一個例子是只讀的狀態(tài)寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應該試圖去修改它。

      2).是的。盡管這并不很常見。一個例子是當一個中服務子程序修該一個指向一個buffer的指針時。

      3).這段代碼的有個惡作劇。這段代碼的目的是用來返指針*ptr指向值的平方,但是,由于*ptr指向一個volatile型參數(shù),編譯器將產(chǎn)生類似下面的代碼: int square(volatile int *ptr){ int a,b;a = *ptr;b = *ptr;return a * b;} 由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結果,這段代碼可能返不是你所期望的平方值!正確的代碼如下: long square(volatile int *ptr){ int a;a = *ptr;return a * a;} 講講我的理解:(歡迎打板子...~~?。?/p>

      關鍵在于兩個地方:

      1.編譯器的優(yōu)化(請高手幫我看看下面的理解)

      在本次線程內(nèi), 當讀取一個變量時,為提高存取速度,編譯器優(yōu)化時有時會先把變量讀取到一個寄存器中;以后,再取變量值時,就直接從寄存器中取值;

      當變量值在本線程里改變時,會同時把變量的新值copy到該寄存器中,以便保持一致

      當變量在因別的線程等而改變了值,該寄存器的值不會相應改變,從而造成應用程序讀取的值和實際的變量值不一致

      當該寄存器在因別的線程等而改變了值,原變量的值不會改變,從而造成應用程序讀取的值和實際的變量值不一致

      舉一個不太準確的例子:

      發(fā)薪資時,會計每次都把員工叫來登記他們的銀行卡號;一次會計為了省事,沒有即時登記,用了以前登記的銀行卡號;剛好一個員工的銀行卡丟了,已掛失該銀行卡號;從而造成該員工領不到工資

      員工 -- 原始變量地址

      銀行卡號 -- 原始變量在寄存器的備份

      2.在什么情況下會出現(xiàn)(如1樓所說)

      1).并行設備的硬件寄存器(如:狀態(tài)寄存器)

      2).一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)3).多線程應用中被幾個任務共享的變量

      補充: volatile應該解釋為“直接存取原始內(nèi)存地址”比較合適,“易變的”這種解釋簡直有點誤導人;

      “易變”是因為外在因素引起的,象多線程,中斷等,并不是因為用volatile修飾了的變量就是“易變”了,假如沒有外因,即使用volatile定義,它也不會變化;

      而用volatile定義之后,其實這個變量就不會因外因而變化了,可以放心使用了; 大家看看前面那種解釋(易變的)是不是在誤導人

      ------------簡明示例如下:------------------

      volatile關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統(tǒng)、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優(yōu)化,從而可以提供對特殊地址的穩(wěn)定訪問。使用該關鍵字的例子如下: int volatile nVint;>>>>當要求使用volatile 聲明的變量的值的時候,系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù),即使它前面的指令剛剛從該處讀取過數(shù)據(jù)。而且讀取的數(shù)據(jù)立刻被保存。例如:

      volatile int i=10;int a = i;...//其他代碼,并未明確告訴編譯器,對i進行過操作 int b = i;>>>>volatile 指出 i是隨時可能發(fā)生變化的,每次使用它的時候必須從i的地址中讀取,因而編譯器生成的匯編代碼會重新從i的地址讀取數(shù)據(jù)放在b中。而優(yōu)化做法是,由于編譯器發(fā)現(xiàn)兩次從i讀數(shù)據(jù)的代碼之間的代碼沒有對i進行過操作,它會自動把上次讀的數(shù)據(jù)放在b中。而不是重新從i里面讀。這樣以來,如果i是一個寄存器變量或者表示一個端口數(shù)據(jù)就容易出錯,所以說volatile可以保證對特殊地址的穩(wěn)定訪問。

      >>>>注意,在vc6中,一般調(diào)試模式?jīng)]有進行代碼優(yōu)化,所以這個關鍵字的作用看不出來。下面通過插入?yún)R編代碼,測試有無volatile關鍵字,對程序最終代碼的影響:

      >>>>首先,用classwizard建一個win32 console工程,插入一個voltest.cpp文件,輸入下面的代碼: >> #i nclude void main(){ int i=10;int a = i;printf(“i= %d”,a);//下面匯編語句的作用就是改變內(nèi)存中i的值,但是又不讓編譯器知道 __asm { mov dword ptr [ebp-4], 20h } int b = i;printf(“i= %d”,b);} 然后,在調(diào)試版本模式運行程序,輸出結果如下: i = 10 i = 32 然后,在release版本模式運行程序,輸出結果如下: i = 10 i = 10 輸出的結果明顯表明,release模式下,編譯器對代碼進行了優(yōu)化,第二次沒有輸出正確的i值。下面,我們把 i的聲明加上volatile關鍵字,看看有什么變化: #i nclude void main(){ volatile int i=10;int a = i;printf(“i= %d”,a);__asm { mov dword ptr [ebp-4], 20h } int b = i;printf(“i= %d”,b);} 分別在調(diào)試版本和release版本運行程序,輸出都是: i = 10 i = 32 這說明這個關鍵字發(fā)揮了它的作用!

      ------------------------------------

      volatile對應的變量可能在你的程序本身不知道的情況下發(fā)生改變

      比如多線程的程序,共同訪問的內(nèi)存當中,多個程序都可以操縱這個變量 你自己的程序,是無法判定合適這個變量會發(fā)生變化

      還比如,他和一個外部設備的某個狀態(tài)對應,當外部設備發(fā)生操作的時候,通過驅動程序和中斷事件,系統(tǒng)改變了這個變量的數(shù)值,而你的程序并不知道。

      對于volatile類型的變量,系統(tǒng)每次用到他的時候都是直接從對應的內(nèi)存當中提取,而不會利用cache當中的原有數(shù)值,以適應它的未知何時會發(fā)生的變化,系統(tǒng)對這種變量的處理不會做優(yōu)化——顯然也是因為它的數(shù)值隨時都可能變化的情況。

      ------------------

      典型的例子

      for(int i=0;i<100000;i++);這個語句用來測試空循環(huán)的速度的

      但是編譯器肯定要把它優(yōu)化掉,根本就不執(zhí)行 如果你寫成

      for(volatile int i=0;i<100000;i++);它就會執(zhí)行了

      volatile的本意是“易變的” 由于訪問寄存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優(yōu)化。比如:

      static int i=0;

      int main(void){...while(1){ if(i)dosomething();} }

      /* Interrupt service routine.*/ void ISR_2(void){ i=1;}

      程序的本意是希望ISR_2中斷產(chǎn)生時,在main當中調(diào)用dosomething函數(shù),但是,由于編譯器判斷在main函數(shù)里面沒有修改過i,因此

      可能只執(zhí)行一次對從i到某寄存器的讀操作,然后每次if判斷都只使用這個寄存器里面的“i副本”,導致dosomething永遠也不會被

      調(diào)用。如果將將變量加上volatile修飾,則編譯器保證對此變量的讀寫操作都不會被優(yōu)化(肯定執(zhí)行)。此例中i也應該如此說明。

      一般說來,volatile用在如下的幾個地方:

      1、中斷服務程序中修改的供其它程序檢測的變量需要加volatile;

      2、多任務環(huán)境下各任務間共享的標志應該加volatile;

      3、存儲器映射的硬件寄存器通常也要加volatile說明,因為每次對它的讀寫都可能由不同意義;

      另外,以上這幾種情況經(jīng)常還要同時考慮數(shù)據(jù)的完整性(相互關聯(lián)的幾個標志讀了一半被打斷了重寫),在1中可以通過關中斷來實

      現(xiàn),2中可以禁止任務調(diào)度,3中則只能依靠硬件的良好設計了。

      第二篇:嵌入式程序員C語言筆試題目

      華碩_嵌入式程序員C語言筆試題目

      預處理器(Preprocessor).用預處理指令#define 聲明一個常數(shù),用以表明1年中有多少秒(忽略閏年問題)

      #define SECONDS_PER_YEAR(60 * 60 * 24 * 365)UL

      我在這想看到幾件事情:

      1)#define 語法的基本知識(例如:不能以分號結束,括號的使用,等等)

      2)懂得預處理器將為你計算常數(shù)表達式的值,因此,直接寫出你是如何計算一年中有多少秒而不是計算出實際的值,是更清晰而沒有代價的。

      3)意識到這個表達式將使一個16位機的整型數(shù)溢出-因此要用到長整型符號L,告訴編譯器這個常數(shù)是的長整型數(shù)。

      4)如果你在你的表達式中用到UL(表示無符號長整型),那么你有了一個好的起點。記住,第一印象很重要。.寫一個“標準”宏MIN,這個宏輸入兩個參數(shù)并返回較小的一個。

      #define MIN(A,B)((A)<=(B)?(A):(B))

      這個測試是為下面的目的而設的:

      1)標識#define在宏中應用的基本知識。這是很重要的。因為在 嵌入(inline)操作符 變?yōu)闃藴蔆的一部分之前,宏是方便產(chǎn)生嵌入代碼的唯一方法,對于嵌入式系統(tǒng)來說,為了能達到要求的性能,嵌入代碼經(jīng)常是必須的方法。

      2)三重條件操作符的知識。這個操作符存在C語言中的原因是它使得編譯器能產(chǎn)生比if-then-else更優(yōu)化的代碼,了解這個用法是很重要的。

      3)懂得在宏中小心地把參數(shù)用括號括起來

      4)我也用這個問題開始討論宏的副作用,例如:當你寫下面的代碼時會發(fā)生什么事?

      least = MIN(*p++, b);

      3.預處理器標識#error的目的是什么?

      Error directives produce compiler-time error messages.死循環(huán)(Infinite loops)

      4.嵌入式系統(tǒng)中經(jīng)常要用到無限循環(huán),你怎么樣用C編寫死循環(huán)呢?

      這個問題用幾個解決方案。我首選的方案是:

      while(1){ }

      一些程序員更喜歡如下方案:

      for(;;){ }

      這個實現(xiàn)方式讓我為難,因為這個語法沒有確切表達到底怎么回事。如果一個應試者給出這個作為方案,我將用這個作為一個機會去探究他們這樣做的基本原理。如果他們的基本答案是:“我被教著這樣做,但從沒有想到過為什么?!边@會給我留下一個壞印象。

      第三個方案是用 goto Loop:...goto Loop;

      應試者如給出上面的方案,這說明或者他是一個匯編語言程序員(這也許是好事)或者他是一個想進入新領域的BASIC/FORTRAN程序員。

      數(shù)據(jù)聲明(Data declarations)

      5.用變量a給出下面的定義

      a)一個整型數(shù)(An integer)

      b)一個指向整型數(shù)的指針(A pointer to an integer)c)一個指向指針的的指針,它指向的指針是指向一個整型數(shù)(A pointer to a pointer to an intege)r

      d)一個有10個整型數(shù)的數(shù)組(An array of 10 integers)e)一個有10個指針的數(shù)組,該指針是指向一個整型數(shù)的。(An array of 10 pointers to integers)

      f)一個指向有10個整型數(shù)數(shù)組的指針(A pointer to an array of 10 integers)

      g)一個指向函數(shù)的指針,該函數(shù)有一個整型參數(shù)并返回一個整型數(shù)(A pointer to a function that takes an integer as an argument and returns an integer)

      h)一個有10個指針的數(shù)組,該指針指向一個函數(shù),該函數(shù)有一個整型參數(shù)并返回一個整型數(shù)(An array of ten pointers to functions that take an integer argument and return an integer)

      答案是:

      a)int a;// An integer

      b)int *a;// A pointer to an integer

      c)int **a;// A pointer to a pointer to an integer

      d)int a[10];// An array of 10 integers

      e)int *a[10];// An array of 10 pointers to integers

      f)int(*a)[10];// A pointer to an array of 10 integers

      g)int(*a)(int);// A pointer to a function a that takes an integer argument and returns an integer

      h)int(*a[10])(int);// An array of 10 pointers to functions that take an integer argument and return an integer

      人們經(jīng)常聲稱這里有幾個問題是那種要翻一下書才能回答的問題,我同意這種說法。當我寫這篇文章時,為了確定語法的正確性,我的確查了一下書。但是當我被面試的時候,我期望被問到這個問題(或者相近的問題)。因為在被面試的這段時間里,我確定我知道這個問題的答案。應試者如果不知道所有的答案(或至少大部分答案),那么也就沒有為這次面試做準備,如果該面試者沒有為這次面試做準備,那么他又能為什么出準備呢? Static

      6.關鍵字static的作用是什么?

      這個簡單的問題很少有人能回答完全。在C語言中,關鍵字static有三個明顯的作用:

      1)在函數(shù)體,一個被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用過程中維持其值不變。

      2)在模塊內(nèi)(但在函數(shù)體外),一個被聲明為靜態(tài)的變量可以被模塊內(nèi)所用函數(shù)訪問,但不能被模塊外其它函數(shù)訪問。它是一個本地的全局變量。

      3)在模塊內(nèi),一個被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。那就是,這個函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用。

      大多數(shù)應試者能正確回答第一部分,一部分能正確回答第二部分,同是很少的人能懂得第三部分。這是一個應試者的嚴重的缺點,因為他顯然不懂得本地化數(shù)據(jù)和代碼范圍的好處和重要性。

      Const

      7.關鍵字const有什么含意?

      我只要一聽到被面試者說:“const意味著常數(shù)”,我就知道我正在和一個業(yè)余者打交道。去年Dan Saks已經(jīng)在他的文章里完全概括了const的所有用法,因此ESP(譯者:Embedded Systems Programming)的每一位讀者應該非常熟悉const能做什么和不能做什么.如果你從沒有讀到那篇文章,只要能說出const意味著“只讀”就可以了。盡管這個答案不是完全的答案,但我接受它作為一個正確的答案。(如果你想知道更詳細的答案,仔細讀一下Saks的文章吧。)

      如果應試者能正確回答這個問題,我將問他一個附加的問題:

      下面的聲明都是什么意思?

      const int a;

      int const a;

      const int *a;

      int * const a;

      int const * const a=new int(1);

      /******/

      前兩個的作用是一樣,a是一個常整型數(shù)。第三個意味著a是一個指向常整型數(shù)的指針(也就是,整型數(shù)是不可修改的,但指針可以)。第四個意識a是一個指向整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是可以修改的,但指針是不可修改的)。最后一個意味著a是一個指向常整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是不可修改的,同時指針也是不可修改的)。如果應試者能正確回答這些問題,那么他就給我留下了一個好印象。順帶提一句,也許你可能會問,即使不用關鍵字 const,也還是能很容易寫出功能正確的程序,那么我為什么還要如此看重關鍵字const呢?我也如下的幾下理由:

      1)關鍵字const的作用是為給讀你代碼的人傳達非常有用的信息,實際上,聲明一個參數(shù)為常量是為了告訴了用戶這個參數(shù)的應用目的。如果你曾花很多時間清理其它人留下的垃圾,你就會很快學會感謝這點多余的信息。(當然,懂得用const的程序員很少會留下的垃圾讓別人來清理的。)

      第三篇:嵌入式程序員C語言筆試經(jīng)典題

      新一篇: 存儲過程,無限級分類 | 舊一篇: 類繼承中構造函數(shù)和析構函數(shù)的調(diào)用

      這個測試適于不同水平的應試者,大多數(shù)初級水平的應試者的成績會很差,經(jīng)驗豐富的程序員應該有很好的成績。為了讓你能自己決定某些問題的偏好,每個問題沒有分配分數(shù),如果選擇這些考題為你所用,請自行按你的意思分配分數(shù)。

      預處理器(Preprocessor).用預處理指令#define 聲明一個常數(shù),用以表明1年中有多少秒(忽略閏年問題)#define SECONDS_PER_YEAR(60 * 60 * 24 * 365)UL 我在這想看到幾件事情:

      1)#define 語法的基本知識(例如:不能以分號結束,括號的使用,等等)

      2)懂得預處理器將為你計算常數(shù)表達式的值,因此,直接寫出你是如何計算一年中有多少秒而不是計算出實際的值,是更清晰而沒有代價的。

      3)意識到這個表達式將使一個16位機的整型數(shù)溢出-因此要用到長整型符號L,告訴編譯器這個常數(shù)是的長整型數(shù)。

      4)如果你在你的表達式中用到UL(表示無符號長整型),那么你有了一個好的起點。記住,第一印象很重要。.寫一個“標準”宏MIN,這個宏輸入兩個參數(shù)并返回較小的一個。#define MIN(A,B)((A)<=(B)?(A):(B))這個測試是為下面的目的而設的:

      1)標識#define在宏中應用的基本知識。這是很重要的。因為在嵌入(inline)操作符 變?yōu)闃藴蔆的一部分之前,宏是方便產(chǎn)生嵌入代碼的唯一方法,對于嵌入式系統(tǒng)來說,為了能達到要求的性能,嵌入代碼經(jīng)常是必須的方法。

      2)三重條件操作符的知識。這個操作符存在C語言中的原因是它使得編譯器能產(chǎn)生比if-then-else更優(yōu)化的代碼,了解這個用法是很重要的。3)懂得在宏中小心地把參數(shù)用括號括起來

      4)我也用這個問題開始討論宏的副作用,例如:當你寫下面的代碼時會發(fā)生什么事? least = MIN(*p++, b);

      3.預處理器標識#error的目的是什么?

      如果你不知道答案,請看參考文獻1。這問題對區(qū)分一個正常的伙計和一個書呆子是很有用的。只有書呆子才會讀C語言課本的附錄去找出象這種問題的答案。當然如果你不是在找一個書呆子,那么應試者最好希望自己不要知道答案。指令 用途

      # 空指令,無任何效果

      #include 包含一個源代碼文件

      #define 定義宏

      #undef 取消已定義的宏

      #if 如果給定條件為真,則編譯下面代碼

      #ifdef 如果宏已經(jīng)定義,則編譯下面代碼

      #ifndef 如果宏沒有定義,則編譯下面代碼

      #elif 如果前面的#if給定條件不為真,當前條件為真,則編譯下面代碼 #endif 結束一個#if……#else條件編譯塊

      #error 停止編譯并顯示錯誤信息

      死循環(huán)(Infinite loops)

      4.嵌入式系統(tǒng)中經(jīng)常要用到無限循環(huán),你怎么樣用C編寫死循環(huán)呢? 這個問題用幾個解決方案。我首選的方案是:

      while(1){ }

      一些程序員更喜歡如下方案:

      for(;;){ }

      這個實現(xiàn)方式讓我為難,因為這個語法沒有確切表達到底怎么回事。如果一個應試者給出這個作為方案,我將用這個作為一個機會去探究他們這樣做的基本原理。如果他們的基本答案是:“我被教著這樣做,但從沒有想到過為什么?!边@會給我留下一個壞印象。

      第三個方案是用 goto Loop:...goto Loop;應試者如給出上面的方案,這說明或者他是一個匯編語言程序員(這也許是好事)或者他是一個想進入新領域的BASIC/FORTRAN程序員。

      數(shù)據(jù)聲明(Data declarations)

      5.用變量a給出下面的定義 a)一個整型數(shù)(An integer)

      //int a me b)一個指向整型數(shù)的指針(A pointer to an integer)

      //int *a me c)一個指向指針的的指針,它指向的指針是指向一個整型數(shù)(A pointer to a pointer to an intege)r

      //int ** a me

      d)一個有10個整型數(shù)的數(shù)組(An array of 10 integers)//int a[10] me e)一個有10個指針的數(shù)組,該指針是指向一個整型數(shù)的。(An array of 10 pointers to integers)

      // int * a[10] me f)一個指向有10個整型數(shù)數(shù)組的指針(A pointer to an array of 10 integers)

      //in t(*a)[10] me g)一個指向函數(shù)的指針,該函數(shù)有一個整型參數(shù)并返回一個整型數(shù)(A pointer to a function that takes an integer as an argument and returns an integer)

      int(*fun)(int)

      h)一個有10個指針的數(shù)組,該指針指向一個函數(shù),該函數(shù)有一個整型參數(shù)并返回一個整型數(shù)(An array of ten pointers to functions that take an integer argument and return an integer)

      //int(*fun[10])(int)答案是:

      a)int a;// An integer b)int *a;// A pointer to an integer c)int **a;// A pointer to a pointer to an integer d)int a[10];// An array of 10 integers e)int *a[10];// An array of 10 pointers to integers f)int(*a)[10];// A pointer to an array of 10 integers g)int(*a)(int);// A pointer to a function a that takes an integer argument and returns an integer h)int(*a[10])(int);// An array of 10 pointers to functions that take an integer argument and return an integer

      人們經(jīng)常聲稱這里有幾個問題是那種要翻一下書才能回答的問題,我同意這種說法。當我寫這篇文章時,為了確定語法的正確性,我的確查了一下書。但是當我被面試的時候,我期望被問到這個問題(或者相近的問題)。因為在被面試的這段時間里,我確定我知道這個問題的答案。應試者如果不知道所有的答案(或至少大部分答案),那么也就沒有為這次面試做準備,如果該面試者沒有為這次面試做準備,那么他又能為什么出準備呢?

      Static

      6.關鍵字static的作用是什么?

      這個簡單的問題很少有人能回答完全。在C語言中,關鍵字static有三個明顯的作用: 1)在函數(shù)體,一個被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用過程中維持其值不變。

      2)在模塊內(nèi)(但在函數(shù)體外),一個被聲明為靜態(tài)的變量可以被模塊內(nèi)所用函數(shù)訪問,但不能被模塊外其它函數(shù)訪問。它是一個本地的全局變量。

      3)在模塊內(nèi),一個被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。那就是,這個函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用。

      大多數(shù)應試者能正確回答第一部分,一部分能正確回答第二部分,同是很少的人能懂得第三部分。這是一個應試者的嚴重的缺點,因為他顯然不懂得本地化數(shù)據(jù)和代碼范圍的好處和重要性。

      Const

      7.關鍵字const有什么含意?

      我只要一聽到被面試者說:“const意味著常數(shù)”,我就知道我正在和一個業(yè)余者打交道。去年Dan Saks已經(jīng)在他的文章里完全概括了const的所有用法,因此ESP(譯者:Embedded Systems Programming)的每一位讀者應該非常熟悉const能做什么和不能做什么.如果你從沒有讀到那篇文章,只要能說出const意味著“只讀”就可以了。盡管這個答案不是完全的答案,但我接受它作為一個正確的答案。(如果你想知道更詳細的答案,仔細讀一下Saks的文章吧。)

      如果應試者能正確回答這個問題,我將問他一個附加的問題: 下面的聲明都是什么意思?

      const int a;int const a;const int *a;int * const a;int const * a const;

      /******/ 前兩個的作用是一樣,a是一個常整型數(shù)。第三個意味著a是一個指向常整型數(shù)的指針(也就是,整型數(shù)是不可修改的,但指針可以)。第四個意識a是一個指向整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是可以修改的,但指針是不可修改的)。最后一個意味著a是一個指向常整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是不可修改的,同時指針也是不可修改的)。如果應試者能正確回答這些問題,那么他就給我留下了一個好印象。順帶提一句,也許你可能會問,即使不用關鍵字 const,也還是能很容易寫出功能正確的程序,那么我為什么還要如此看重關鍵字const呢?我也如下的幾下理由:

      1)關鍵字const的作用是為給讀你代碼的人傳達非常有用的信息,實際上,聲明一個參數(shù)為常量是為了告訴了用戶這個參數(shù)的應用目的。如果你曾花很多時間清理其它人留下的垃圾,你就會很快學會感謝這點多余的信息。(當然,懂得用const的程序員很少會留下的垃圾讓別人來清理的。)2)通過給優(yōu)化器一些附加的信息,使用關鍵字const也許能產(chǎn)生更緊湊的代碼。

      3)合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數(shù),防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現(xiàn)。

      Volatile

      8.關鍵字volatile有什么含意?并給出三個不同的例子。

      一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優(yōu)化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個例子: 1)并行設備的硬件寄存器(如:狀態(tài)寄存器)

      2)一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)3)多線程應用中被幾個任務共享的變量

      回答不出這個問題的人是不會被雇傭的。我認為這是區(qū)分C程序員和嵌入式系統(tǒng)程序員的最基本的問題。搞嵌入式的家伙們經(jīng)常同硬件、中斷、RTOS等等打交道,所有這些都要求用到volatile變量。不懂得volatile的內(nèi)容將會帶來災難。

      假設被面試者正確地回答了這是問題(嗯,懷疑是否會是這樣),我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。

      1)一個參數(shù)既可以是const還可以是volatile嗎?解釋為什么。2);一個指針可以是volatile 嗎?解釋為什么。3);下面的函數(shù)有什么錯誤:

      int square(volatile int *ptr){ return *ptr * *ptr;}

      下面是答案:

      1)是的。一個例子是只讀的狀態(tài)寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應該試圖去修改它。

      2);是的。盡管這并不很常見。一個例子是當一個中服務子程序修該一個指向一個buffer的指針時。3)這段代碼有點變態(tài)。這段代碼的目的是用來返指針*ptr指向值的平方,但是,由于*ptr指向一個volatile型參數(shù),編譯器將產(chǎn)生類似下面的代碼:

      int square(volatile int *ptr){ int a,b;a = *ptr;b = *ptr;return a * b;}

      由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:

      long square(volatile int *ptr){ int a;a = *ptr;return a * a;}

      位操作(Bit manipulation)

      9.嵌入式系統(tǒng)總是要用戶對變量或寄存器進行位操作。給定一個整型變量a,寫兩段代碼,第一個設置a的bit 3,第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。對這個問題有三種基本的反應

      1)不知道如何下手。該被面者從沒做過任何嵌入式系統(tǒng)的工作。

      2)用bit fields。Bit fields是被扔到C語言死角的東西,它保證你的代碼在不同編譯器之間是不可移植的,同時也保證了的你的代碼是不可重用的。我最近不幸看到 Infineon為其較復雜的通信芯片寫的驅動程序,它用到了bit fields因此完全對我無用,因為我的編譯器用其它的方式來實現(xiàn)bit fields的。從道德講:永遠不要讓一個非嵌入式的家伙粘實際硬件的邊。

      3)用 #defines 和 bit masks 操作。這是一個有極高可移植性的方法,是應該被用到的方法。最佳的解決方案如下:

      #define BIT3(0x1 << 3)static int a;

      void set_bit3(void){ a |= BIT3;} void clear_bit3(void){ a &= ~BIT3;}

      一些人喜歡為設置和清除值而定義一個掩碼同時定義一些說明常數(shù),這也是可以接受的。我希望看到幾個要點:說明常數(shù)、|=和&=~操作。

      訪問固定的內(nèi)存位置(Accessing fixed memory locations)

      10.嵌入式系統(tǒng)經(jīng)常具有要求程序員去訪問某特定的內(nèi)存位置的特點。在某工程中,要求設置一絕對地址為0x67a9的整型變量的值為0xaa66。編譯器是一個純粹的ANSI編譯器。寫代碼去完成這一任務。這一問題測試你是否知道為了訪問一絕對地址把一個整型數(shù)強制轉換(typecast)為一指針是合法的。這一問題的實現(xiàn)方式隨著個人風格不同而不同。典型的類似代碼如下: int *ptr;ptr =(int *)0x67a9;*ptr = 0xaa55;

      A more obscure approach is: 一個較晦澀的方法是:

      *(int * const)(0x67a9)= 0xaa55;

      即使你的品味更接近第二種方案,但我建議你在面試時使用第一種方案。

      中斷(Interrupts)

      11.中斷是嵌入式系統(tǒng)中重要的組成部分,這導致了很多編譯開發(fā)商提供一種擴展—讓標準C支持中斷。具代表事實是,產(chǎn)生了一個新的關鍵字 __interrupt。下面的代碼就使用了__interrupt關鍵字去定義了一個中斷服務子程序(ISR),請評論一下這段代碼的。

      __interrupt double compute_area(double radius){ double area = PI * radius * radius;printf(“nArea = %f”, area);return area;}

      這個函數(shù)有太多的錯誤了,以至讓人不知從何說起了:

      1)ISR 不能返回一個值。如果你不懂這個,那么你不會被雇用的。

      2)ISR 不能傳遞參數(shù)。如果你沒有看到這一點,你被雇用的機會等同第一項。

      3)在許多的處理器/編譯器中,浮點一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點運算。此外,ISR應該是短而有效率的,在ISR中做浮點運算是不明智的。

      4)與第三點一脈相承,printf()經(jīng)常有重入和性能上的問題。如果你丟掉了第三和第四點,我不會太為難你的。不用說,如果你能得到后兩點,那么你的被雇用前景越來越光明了。

      代碼例子(Code examples).下面的代碼輸出是什么,為什么?

      void foo(void){ unsigned int a = 6;int b =-20;(a+b > 6)? puts(“> 6”): puts(“<= 6”);} 這個問題測試你是否懂得C語言中的整數(shù)自動轉換原則,我發(fā)現(xiàn)有些開發(fā)者懂得極少這些東西。不管如何,這無符號整型問題的答案是輸出是 “>6”。原因是當表達式中存在有符號類型和無符號類型時所有的操作數(shù)都自動轉換為無符號類型。因此-20變成了一個非常大的正整數(shù),所以該表達式計算出的結果大于6。這一點對于應當頻繁用到無符號數(shù)據(jù)類型的嵌入式系統(tǒng)來說是豐常重要的。如果你答錯了這個問題,你也就到了得不到這份工作的邊緣。

      13.評價下面的代碼片斷:

      unsigned int zero = 0;unsigned int compzero = 0xFFFF;/*1's complement of zero */

      對于一個int型不是16位的處理器為說,上面的代碼是不正確的。應編寫如下:

      unsigned int compzero = ~0;這一問題真正能揭露出應試者是否懂得處理器字長的重要性。在我的經(jīng)驗里,好的嵌入式程序員非常準確地明白硬件的細節(jié)和它的局限,然而PC機程序往往把硬件作為一個無法避免的煩惱。

      到了這個階段,應試者或者完全垂頭喪氣了或者信心滿滿志在必得。如果顯然應試者不是很好,那么這個測試就在這里結束了。但如果顯然應試者做得不錯,那么我就扔出下面的追加問題,這些問題是比較難的,我想僅僅非常優(yōu)秀的應試者能做得不錯。提出這些問題,我希望更多看到應試者應付問題的方法,而不是答案。不管如何,你就當是這個娛樂吧...動態(tài)內(nèi)存分配(Dynamic memory allocation)

      14.盡管不像非嵌入式計算機那么常見,嵌入式系統(tǒng)還是有從堆(heap)中動態(tài)分配內(nèi)存的過程的。那么嵌入式系統(tǒng)中,動態(tài)分配內(nèi)存可能發(fā)生的問題是什么?

      這里,我期望應試者能提到內(nèi)存碎片,碎片收集的問題,變量的持行時間等等。這個主題已經(jīng)在ESP雜志中被廣泛地討論過了(主要是 P.J.Plauger, 他的解釋遠遠超過我這里能提到的任何解釋),所有回過頭看一下這些雜志吧!讓應試者進入一種虛假的安全感覺后,我拿出這么一個小節(jié)目: 下面的代碼片段的輸出是什么,為什么?

      char *ptr;if((ptr =(char *)malloc(0))== NULL)puts(“Got a null pointer”);else puts(“Got a valid pointer”);

      這是一個有趣的問題。最近在我的一個同事不經(jīng)意把0值傳給了函數(shù)malloc,得到了一個合法的指針之后,我才想到這個問題。這就是上面的代碼,該代碼的輸出是“Got a valid pointer”。我用這個來開始討論這樣的一問題,看看被面試者是否想到庫例程這樣做是正確。得到正確的答案固然重要,但解決問題的方法和你做決定的基本原理更重要些。

      Typedef Typedef 在C語言中頻繁用以聲明一個已經(jīng)存在的數(shù)據(jù)類型的同義字。也可以用預處理器做類似的事。例如,思考一下下面的例子:

      #define dPS struct s * typedef struct s * tPS;

      以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個指向結構s指針。哪種方法更好呢?(如果有的話)為什么?

      這是一個非常微妙的問題,任何人答對這個問題(正當?shù)脑颍┦菓敱还驳?。答案是:typedef更好。思考下面的例子:

      dPS p1,p2;tPS p3,p4;第一個擴展為

      struct s * p1, p2;.上面的代碼定義p1為一個指向結構的指,p2為一個實際的結構,這也許不是你想要的。第二個例子正確地定義了p3 和p4 兩個指針。

      晦澀的語法.C語言同意一些令人震驚的結構,下面的結構是合法的嗎,如果是它做些什么?

      int a = 5, b = 7, c;c = a+++b;

      這個問題將做為這個測驗的一個愉快的結尾。不管你相不相信,上面的例子是完全合乎語法的。問題是編譯器如何處理它?水平不高的編譯作者實際上會爭論這個問題,根據(jù)最處理原則,編譯器應當能處理盡可能所有合法的用法。因此,上面的代碼被處理成:

      c = a++ + b;

      因此, 這段代碼持行后a = 6, b = 7, c = 12。

      如果你知道答案,或猜出正確答案,做得好。如果你不知道答案,我也不把這個當作問題。我發(fā)現(xiàn)這個問題的最大好處是這是一個關于代碼編寫風格,代碼的可讀性,代碼的可修改性的好的話題。

      第四篇:嵌入式系統(tǒng)c語言編程該怎么學?

      雪中悍刀行 http://bmdqw.com/

      嵌入式系統(tǒng)c語言編程該怎么學?

      C語言博大精深,玩了很長時間了,一直徘徊在入門處。看了很多別人的編程經(jīng)驗,加上項目程序越做越大,直到這半年來突然有很多體會,明天就要回家了,下午閑來無事也試著總結一些心得體會,喜歡對師弟妹們的學習有所幫助。

      首先要說說編程的幾個重要原則,看了很多別人的編程經(jīng)驗,更多的是說技巧。技巧能顯著提高程序的效率,固然重要但是技巧的掌握靠了還是大量的工程實踐,只有在有一定功底后才可以去追求這些編程技巧。但是編程的原則卻是要在學習一開始就要認真貫徹,才能養(yǎng)成良好的編程習慣,苦練內(nèi)功后練上層功夫才不會走火入魔。

      言歸正傳,嵌入式系統(tǒng)C語言編程需要遵守什么樣的原則呢?隨著時代和技術的不斷發(fā)展,這個問題也許仁者見仁智者見智了,但是總結起來大家還是有很多共識。根據(jù)目前提倡的軟件工程的做法,和我們教研室的做法,列舉最重要原則:

      一、模塊劃分.C語言作為一種結構化的程序設計語言,在模塊的劃分上主要依據(jù)功能:

      (1)一個功能模塊即是一個.c文件和一個.h文件的結合,.h文件中是對于該模塊功能函數(shù)和使變 量的聲明

      (2)該模塊提供給其它模塊調(diào)用的外部函數(shù)及數(shù)據(jù)都需要在.h中文件中以extern關鍵字聲明

      (3)模塊內(nèi)的函數(shù)和全局變量只能在.c文件定義

      (4)不允許在.h文件中定義變量(定義變量和聲明變量的區(qū)別在于定義會產(chǎn)生內(nèi)存分配的操作,而聲明則只是告訴包含該聲明的模塊在連接階段從其它模塊尋找外部函數(shù)和變量)。

      二、一個嵌入式系統(tǒng)程序包括兩類模塊分三層編寫:

      (1)硬件驅動模塊,一種特定硬件對應一個模塊,包括了片內(nèi)集成的硬件模塊和外部擴展的(2)軟件功能模塊,軟件功能模塊是建立在硬件驅動模塊上的與硬件無關的邏輯功能。

      (3)三層編寫即HAL(硬件應用層),API(應用函數(shù)包),APP(邏輯應用層)注:名稱是借用的,表大個意思而已。HAL就是硬件驅動模塊和系統(tǒng)硬件密切相關,API可以是建立在HAL上的硬件應用服務程序也可以是通用的函數(shù)模塊,APP則是最終構成嵌入式系統(tǒng)應用的功能邏輯關系。HAL和API是為了方便技術積累和提高開發(fā)效率而分開了,APP則是針對特殊應用而定制的。

      三、中斷服務程序的要求:

      (1)不能返回值

      (2)不能向ISR傳遞參數(shù)

      (3)ISR應該盡可能的短小精悍,不允許有等待信號的操作

      四、編程風格問題

      五、需要學會熟練應用的C語言的基本手法

      (1)數(shù)據(jù)指針,不能僅僅只會使用數(shù)組

      (2)宏定義,定義寄存器地址,定義宏函數(shù)等

      (3)函數(shù)指針的應用

      (4)條件編譯,在帶操作系統(tǒng)的應用時經(jīng)常要用

      六、不要偷懶,寫好必要的注釋

      七、做到以上幾點時就可以吸收高超的編程技巧了。轉載請保留連接

      本文由004km.cn整理

      第五篇:C語言嵌入式系統(tǒng)編程修煉之道

      C語言嵌入式系統(tǒng)編程修煉之道收藏

      C語言嵌入式系統(tǒng)編程修煉之道——背景篇...1 C語言嵌入式系統(tǒng)編程修煉之道——軟件架構篇...4 1.模塊劃分...4 2.多任務還是單任務...5 3.單任務程序典型架構...6 4.中斷服務程序...7 5.硬件驅動模塊...9 6.C的面向對象化...10 總結...10 C語言嵌入式系統(tǒng)編程修煉之道——內(nèi)存操作篇...12 1.數(shù)據(jù)指針...12 2.函數(shù)指針...13 3.數(shù)組vs.動態(tài)申請...14 4.關鍵字const 15 5.關鍵字volatile.16 6.CPU字長與存儲器位寬不一致處理...17 總結...18 C語言嵌入式系統(tǒng)編程修煉之道——屏幕操作篇...19 1.漢字處理...19 2.系統(tǒng)時間顯示...20 3.動畫顯示...21 4.菜單操作...22 5.模擬MessageBox函數(shù)...24 總結...26 C語言嵌入式系統(tǒng)編程修煉之道——鍵盤操作篇...27 1.處理功能鍵...27 2.處理數(shù)字鍵...28 3.整理用戶輸入...29 總結...30 C語言嵌入式系統(tǒng)編程修煉之道——性能優(yōu)化篇...31 1.使用宏定義...31 2.使用寄存器變量...31 3.內(nèi)嵌匯編...32 4.利用硬件特性...32 5.活用位操作...33 總結

      C語言嵌入式系統(tǒng)編程修煉之道——背景篇 不同于一般形式的軟件編程,嵌入式系統(tǒng)編程建立在特定的硬件平臺上,勢必要求其編程語言具備較強的硬件直接操作能力。無疑,匯編語言具備這樣的特質。但是,歸因于匯編語言開發(fā)過程的復雜性,它并不是嵌入式系統(tǒng)開發(fā)的一般選擇。而與之相比,C語言——一種“高級的低級”語言,則成為嵌入式系統(tǒng)開發(fā)的最佳選擇。筆者在嵌入式系統(tǒng)項目的開發(fā)過程中,一次又一次感受到C語言的精妙,沉醉于C語言給嵌入式開發(fā)帶來的便利。本文的目的在于進行“C語言嵌入式系統(tǒng)開發(fā)的內(nèi)功心法”秀,一共包括25招。

      圖1給出了本文的討論所基于的硬件平臺,實際上,這也是大多數(shù)嵌入式系統(tǒng)的硬件平臺。它包括兩部分:

      (1)

      以通用處理器為中心的協(xié)議處理模塊,用于網(wǎng)絡控制協(xié)議的處理;(2)

      以數(shù)字信號處理器(DSP)為中心的信號處理模塊,用于調(diào)制、解調(diào)和數(shù)/模信號轉換。

      本文的討論主要圍繞以通用處理器為中心的協(xié)議處理模塊進行,因為它更多地牽涉到具體的C語言編程技巧。而DSP編程則重點關注具體的數(shù)字信號處理算法,主要涉及通信領域的知識,不是本文的討論重點。

      著眼于討論普遍的嵌入式系統(tǒng)C編程技巧,系統(tǒng)的協(xié)議處理模塊沒有選擇特別的CPU,而是選擇了眾所周知的CPU芯片——80186,每一位學習過《微機原理》的讀者都應該對此芯片有一個基本的認識,且對其指令集比較熟悉。80186的字長是16位,可以尋址到的內(nèi)存空間為1MB,只有實地址模式。C語言編譯生成的指針為32位(雙字),高16位為段地址,低16位為段內(nèi)編譯,一段最多64KB。

      圖1 系統(tǒng)硬件架構

      協(xié)議處理模塊中的FLASH和RAM幾乎是每個嵌入式系統(tǒng)的必備設備,前者用于存儲程序,后者則是程序運行時指令及數(shù)據(jù)的存放位置。系統(tǒng)所選擇的FLASH和RAM的位寬都為16位,與CPU一致。

      實時鐘芯片可以為系統(tǒng)定時,給出當前的年、月、日及具體時間(小時、分、秒及毫秒),可以設定其經(jīng)過一段時間即向CPU提出中斷或設定報警時間到來時向CPU提出中斷(類似鬧鐘功能)。

      NVRAM(非易失去性RAM)具有掉電不丟失數(shù)據(jù)的特性,可以用于保存系統(tǒng)的設置信息,譬如網(wǎng)絡協(xié)議參數(shù)等。在系統(tǒng)掉電或重新啟動后,仍然可以讀取先前的設置信息。其位寬為8位,比CPU字長小。文章特意選擇一個與CPU字長不一致的存儲芯片,為后文中一節(jié)的討論創(chuàng)造條件。

      UART則完成CPU并行數(shù)據(jù)傳輸與RS-232串行數(shù)據(jù)傳輸?shù)霓D換,它可以在接收到[1~MAX_BUFFER]字節(jié)后向CPU提出中斷,MAX_BUFFER為UART芯片存儲接收到字節(jié)的最大緩沖區(qū)。

      鍵盤控制器和顯示控制器則完成系統(tǒng)人機界面的控制。以上提供的是一個較完備的嵌入式系統(tǒng)硬件架構,實際的系統(tǒng)可能包含更少的外設。之所以選擇一個完備的系統(tǒng),是為了后文更全面的討論嵌入式系統(tǒng)C語言編程技巧的方方面面,所有設備都會成為后文的分析目標。

      嵌入式系統(tǒng)需要良好的軟件開發(fā)環(huán)境的支持,由于嵌入式系統(tǒng)的目標機資源受限,不可能在其上建立龐大、復雜的開發(fā)環(huán)境,因而其開發(fā)環(huán)境和目標運行環(huán)境相互分離。因此,嵌入式應用軟件的開發(fā)方式一般是,在宿主機(Host)上建立開發(fā)環(huán)境,進行應用程序編碼和交叉編譯,然后宿主機同目標機(Target)建立連接,將應用程序下載到目標機上進行交叉調(diào)試,經(jīng)過調(diào)試和優(yōu)化,最后將應用程序固化到目標機中實際運行。

      CAD-UL是適用于x86處理器的嵌入式應用軟件開發(fā)環(huán)境,它運行在Windows操作系統(tǒng)之上,可生成x86處理器的目標代碼并通過PC機的COM口(RS-232串口)或以太網(wǎng)口下載到目標機上運行,如圖2。其駐留于目標機FLASH存儲器中的monitor程序可以監(jiān)控宿主機Windows調(diào)試平臺上的用戶調(diào)試指令,獲取CPU寄存器的值及目標機存儲空間、I/O空間的內(nèi)容。圖2 交叉開發(fā)環(huán)境

      后續(xù)章節(jié)將從軟件架構、內(nèi)存操作、屏幕操作、鍵盤操作、性能優(yōu)化等多方面闡述C語言嵌入式系統(tǒng)的編程技巧。軟件架構是一個宏觀概念,與具體硬件的聯(lián)系不大;內(nèi)存操作主要涉及系統(tǒng)中的FLASH、RAM和NVRAM芯片;屏幕操作則涉及顯示控制器和實時鐘;鍵盤操作主要涉及鍵盤控制器;性能優(yōu)化則給出一些具體的減小程序時間、空間消耗的技巧。

      本文即將講述的25個主題可分為兩類,一類是編程技巧,有很強的適用性;一類則介紹嵌入式系統(tǒng)編程的一般常識,具有一定的理論意義。So, let’s go.C語言嵌入式系統(tǒng)編程修煉之道——軟件架構篇 1.模塊劃分

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

      模塊即是一個.c文件和一個.h文件的結合,頭文件(.h)中是對于該模塊接口的聲明;

      (2)

      某模塊提供給其它模塊調(diào)用的外部函數(shù)及數(shù)據(jù)需在.h中文件中冠以extern關鍵字聲明;

      (3)

      模塊內(nèi)的函數(shù)和全局變量需在.c文件開頭冠以static關鍵字聲明;(4)

      永遠不要在.h文件中定義變量!定義變量和聲明變量的區(qū)別在于定義會產(chǎn)生內(nèi)存分配的操作,是匯編階段的概念;而聲明則只是告訴包含該聲明的模塊在連接階段從其它模塊尋找外部函數(shù)和變量。如: /*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文件 */ 以上程序的結果是在模塊1、2、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文件 */ 這樣如果模塊1、2、3操作a的話,對應的是同一片內(nèi)存單元。一個嵌入式系統(tǒng)通常包括兩類模塊:

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

      (2)軟件功能模塊,其模塊的劃分應滿足低偶合、高內(nèi)聚的要求。2.多任務還是單任務

      所謂“單任務系統(tǒng)”是指該系統(tǒng)不能支持多任務并發(fā)操作,宏觀串行地執(zhí)行一個任務。而多任務系統(tǒng)則可以宏觀并行(微觀上可能串行)地“同時”執(zhí)行多個任務。

      多任務的并發(fā)執(zhí)行通常依賴于一個多任務操作系統(tǒng)(OS),多任務OS的核心是系統(tǒng)調(diào)度器,它使用任務控制塊(TCB)來管理任務調(diào)度功能。TCB包括任務的當前狀態(tài)、優(yōu)先級、要等待的事件或資源、任務程序碼的起始地址、初始堆棧指針等信息。調(diào)度器在任務被激活時,要用到這些信息。此外,TCB還被用來存放任務的“上下文”(context)。任務的上下文就是當一個執(zhí)行中的任務被停止時,所要保存的所有信息。通常,上下文就是計算機當前的狀態(tài),也即各個寄存器的內(nèi)容。當發(fā)生任務切換時,當前運行的任務的上下文被存入TCB,并將要被執(zhí)行的任務的上下文從它的TCB中取出,放入各個寄存器中。嵌入式多任務OS的典型例子有Vxworks、ucLinux等。嵌入式OS并非遙不可及的神壇之物,我們可以用不到1000行代碼實現(xiàn)一個針對80186處理器的功能最簡單的OS內(nèi)核,作者正準備進行此項工作,希望能將心得貢獻給大家。

      究竟選擇多任務還是單任務方式,依賴于軟件的體系是否龐大。例如,絕大多數(shù)手機程序都是多任務的,但也有一些小靈通的協(xié)議棧是單任務的,沒有操作系統(tǒng),它們的主程序輪流調(diào)用各個軟件模塊的處理程序,模擬多任務環(huán)境。3.單任務程序典型架構

      (1)從CPU復位時的指定地址開始執(zhí)行;(2)跳轉至匯編代碼startup處執(zhí)行;

      (3)跳轉至用戶主程序main執(zhí)行,在main中完成: a.初試化各硬件設備;

      b.初始化各軟件模塊; c.進入死循環(huán)(無限循環(huán)),調(diào)用各模塊的處理函數(shù)

      用戶主程序和各模塊的處理函數(shù)都以C語言完成。用戶主程序最后都進入了一個死循環(huán),其首選方案是: while(1){ } 有的程序員這樣寫: for(;;){ } 這個語法沒有確切表達代碼的含義,我們從for(;;)看不出什么,只有弄明白for(;;)在C語言中意味著無條件循環(huán)才明白其意。下面是幾個“著名”的死循環(huán):(1)操作系統(tǒng)是死循環(huán);(2)WIN32程序是死循環(huán);(3)嵌入式系統(tǒng)軟件是死循環(huán);

      (4)多線程程序的線程處理函數(shù)是死循環(huán)。你可能會辯駁,大聲說:“凡事都不是絕對的,2、3、4都可以不是死循環(huán)”。Yes,you are right,但是你得不到鮮花和掌聲。實際上,這是一個沒有太大意義的牛角尖,因為這個世界從來不需要一個處理完幾個消息就喊著要OS殺死它的WIN32程序,不需要一個剛開始RUN就自行了斷的嵌入式系統(tǒng),不需要莫名其妙啟動一個做一點事就干掉自己的線程。有時候,過于嚴謹制造的不是便利而是麻煩。君不見,五層的TCP/IP協(xié)議棧超越嚴謹?shù)腎SO/OSI七層協(xié)議棧大行其道成為事實上的標準? 經(jīng)常有網(wǎng)友討論:

      printf(“%d,%d”,++i,i++);

      /* 輸出是什么?*/ c = a+++b;

      /* c=? */ 等類似問題。面對這些問題,我們只能發(fā)出由衷的感慨:世界上還有很多有意義的事情等著我們?nèi)ハ瘮z入的食物。實際上,嵌入式系統(tǒng)要運行到世界末日。4.中斷服務程序

      中斷是嵌入式系統(tǒng)中重要的組成部分,但是在標準C中不包含中斷。許多編譯開發(fā)商在標準C上增加了對中斷的支持,提供新的關鍵字用于標示中斷服務程序(ISR),類似于__interrupt、#program interrupt等。當一個函數(shù)被定義為ISR的時候,編譯器會自動為該函數(shù)增加中斷服務程序所需要的中斷現(xiàn)場入棧和出棧代碼。

      中斷服務程序需要滿足如下要求:(1)不能返回值;

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

      (3)ISR應該盡可能的短小精悍;

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

      在某項目的開發(fā)中,我們設計了一個隊列,在中斷服務程序中,只是將中斷類型添加入該隊列中,在主程序的死循環(huán)中不斷掃描中斷隊列是否有中斷,有則取出隊列中的第一個中斷類型,進行相應處理。/* 存放中斷的隊列 */ typedef struct tagIntQueue { int intType;

      /* 中斷類型 */ struct tagIntQueue *next;}IntQueue;

      IntQueue lpIntQueueHead;

      __interrupt ISRexample(){

      int intType;

      intType = GetSystemType();QueueAddTail(lpIntQueueHead, intType);/* 在隊列尾加入新的中斷 */ } 在主程序循環(huán)中判斷是否有中斷: While(1){ If(!IsIntQueueEmpty())

      {

      intType = GetFirstInt();

      switch(intType)

      /* 是不是很象WIN32程序的消息解析函數(shù)? */

      {

      /* 對,我們的中斷類型解析很類似于消息驅動 */

      case xxx:

      /* 我們稱其為“中斷驅動”吧? */

      break;

      case xxx:

      break;

      } }

      } 按上述方法設計的中斷服務程序很小,實際的工作都交由主程序執(zhí)行了。5.硬件驅動模塊

      一個硬件驅動模塊通常應包括如下函數(shù):(1)中斷服務程序ISR(2)硬件初始化

      a.修改寄存器,設置硬件參數(shù)(如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內(nèi)部對應寄存器使其作為控制信號;

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

      (4)提供一系列針對該設備的操作接口函數(shù)。例如,對于LCD,其驅動模塊應提供繪制像素、畫線、繪制矩陣、顯示字符點陣等函數(shù);而對于實時鐘,其驅動模塊則需提供獲取時間、設置時間等函數(shù)。6.C的面向對象化

      在面向對象的語言里面,出現(xiàn)了類的概念。類是對特定數(shù)據(jù)的特定操作的集合體。類包含了兩個范疇:數(shù)據(jù)和操作。而C語言中的struct僅僅是數(shù)據(jù)的集合,我們可以利用函數(shù)指針將struct模擬為一個包含數(shù)據(jù)和操作的“類”。下面的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);/* 行為:函數(shù)指針 */

      int a;

      /* 數(shù)據(jù) */

      int b;};我們可以利用C語言模擬出面向對象的三個特性:封裝、繼承和多態(tài),但是更多的時候,我們只是需要將數(shù)據(jù)與行為封裝以解決軟件結構混亂的問題。C模擬面向對象思想的目的不在于模擬行為本身,而在于解決某些情況下使用C語言編程時程序整體框架結構分散、數(shù)據(jù)和函數(shù)脫節(jié)的問題。我們在后續(xù)章節(jié)會看到這樣的例子。總結

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

      請記?。很浖Y構是軟件的靈魂!結構混亂的程序面目可憎,調(diào)試、測試、維護、升級都極度困難。

      一個高尚的程序員應該是寫出如藝術作品般程序的程序員。

      C語言嵌入式系統(tǒng)編程修煉之道——內(nèi)存操作篇 1.數(shù)據(jù)指針

      在嵌入式系統(tǒng)的編程中,常常要求在特定的內(nèi)存單元讀寫內(nèi)容,匯編有對應的MOV指令,而除C/C++以外的其它編程語言基本沒有直接訪問絕對地址的能力。在嵌入式系統(tǒng)的實際調(diào)試中,多借助C語言指針所具有的對絕對地址單元內(nèi)容的讀寫能力。以指針直接操作內(nèi)存多發(fā)生在如下幾種情況:

      (1)

      某I/O芯片被定位在CPU的存儲空間而非I/O空間,而且寄存器對應于某特定地址;

      (2)

      兩個CPU之間以雙端口RAM通信,CPU需要在雙端口RAM的特定單元(稱為mail box)書寫內(nèi)容以在對方CPU產(chǎn)生中斷;

      (3)

      讀取在ROM或FLASH的特定單元所燒錄的漢字和英文字模。譬如:

      unsigned char *p =(unsigned char *)0xF000FF00;*p=11;以上程序的意義為在絕對地址0xF0000+0xFF00(80186使用16位段地址和16位偏移地址)寫入11。在使用絕對地址指針時,要注意指針自增自減操作的結果取決于指針指向的數(shù)據(jù)類別。上例中p++后的結果是p= 0xF000FF01,若p指向int,即: int *p =(int *)0xF000FF00;p++(或++p)的結果等同于:p = p+sizeof(int),而p—(或—p)的結果是p = p-sizeof(int)。同理,若執(zhí)行:

      long int *p =(long int *)0xF000FF00;則p++(或++p)的結果等同于:p = p+sizeof(long int),而p—(或—p)的結果是p = p-sizeof(long int)。

      記?。篊PU以字節(jié)為單位編址,而C語言指針以指向的數(shù)據(jù)類型長度作自增和自減。理解這一點對于以指針直接操作內(nèi)存是相當重要的。2.函數(shù)指針

      首先要理解以下三個問題:

      (1)C語言中函數(shù)名直接對應于函數(shù)生成的指令代碼在內(nèi)存中的地址,因此函數(shù)名可以直接賦給指向函數(shù)的指針;

      (2)調(diào)用函數(shù)實際上等同于“調(diào)轉指令+參數(shù)傳遞處理+回歸位置入?!?,本質上最核心的操作是將函數(shù)生成的目標代碼的首地址賦給CPU的PC寄存器;(3)因為函數(shù)調(diào)用的本質是跳轉到某一個地址單元的code去執(zhí)行,所以可以“調(diào)用”一個根本就不存在的函數(shù)實體,暈?請往下看: 請拿出你可以獲得的任何一本大學《微型計算機原理》教材,書中講到,186 CPU啟動后跳轉至絕對地址0xFFFF0(對應C語言指針是0xF000FFF0,0xF000為段地址,0xFFF0為段內(nèi)偏移)執(zhí)行,請看下面的代碼:

      typedef void(*lpFunction)();

      /* 定義一個無參數(shù)、無返回類型的 */ /* 函數(shù)指針類型 */ lpFunction lpReset =(lpFunction)0xF000FFF0;

      /* 定義一個函數(shù)指針,指向*/ /* CPU啟動后所執(zhí)行第一條指令的位置 */ lpReset();

      /* 調(diào)用函數(shù) */ 在以上的程序中,我們根本沒有看到任何一個函數(shù)實體,但是我們卻執(zhí)行了這樣的函數(shù)調(diào)用:lpReset(),它實際上起到了“軟重啟”的作用,跳轉到CPU啟動后第一條要執(zhí)行的指令的位置。

      記?。汉瘮?shù)無它,唯指令集合耳;你可以調(diào)用一個沒有函數(shù)體的函數(shù),本質上只是換一個地址開始執(zhí)行指令!3.數(shù)組vs.動態(tài)申請

      在嵌入式系統(tǒng)中動態(tài)內(nèi)存申請存在比一般系統(tǒng)編程時更嚴格的要求,這是因為嵌入式系統(tǒng)的內(nèi)存空間往往是十分有限的,不經(jīng)意的內(nèi)存泄露會很快導致系統(tǒng)的崩潰。

      所以一定要保證你的malloc和free成對出現(xiàn),如果你寫出這樣的一段程序: char * function(void){

      char *p;

      p =(char *)malloc(…);

      if(p==NULL)?;

      ?

      /* 一系列針對p的操作 */ return p;} 在某處調(diào)用function(),用完function中動態(tài)申請的內(nèi)存后將其free,如下: char *q = function();? free(q);上述代碼明顯是不合理的,因為違反了malloc和free成對出現(xiàn)的原則,即“誰申請,就由誰釋放”原則。不滿足這個原則,會導致代碼的耦合度增大,因為用戶在調(diào)用function函數(shù)時需要知道其內(nèi)部細節(jié)!

      正確的做法是在調(diào)用處申請內(nèi)存,并傳入function函數(shù),如下: char *p=malloc(…);if(p==NULL)?;function(p);? free(p);p=NULL;而函數(shù)function則接收參數(shù)p,如下: void function(char *p){ ?

      /* 一系列針對p的操作 */ } 基本上,動態(tài)申請內(nèi)存方式可以用較大的數(shù)組替換。對于編程新手,筆者推薦你盡量采用數(shù)組!嵌入式系統(tǒng)可以以博大的胸襟接收瑕疵,而無法“海納”錯誤。畢竟,以最笨的方式苦練神功的郭靖勝過機智聰明卻范政治錯誤走反革命道路的楊康。

      給出原則:

      (1)盡可能的選用數(shù)組,數(shù)組不能越界訪問(真理越過一步就是謬誤,數(shù)組越過界限就光榮地成全了一個混亂的嵌入式系統(tǒng));

      (2)如果使用動態(tài)申請,則申請后一定要判斷是否申請成功了,并且malloc和free應成對出現(xiàn)!4.關鍵字const const意味著“只讀”。區(qū)別如下代碼的功能非常重要,也是老生長嘆,如果你還不知道它們的區(qū)別,而且已經(jīng)在程序界摸爬滾打多年,那只能說這是一個悲哀: const int a;int const a;const int *a;int * const a;int const * a const;(1)關鍵字const的作用是為給讀你代碼的人傳達非常有用的信息。例如,在函數(shù)的形參前添加const關鍵字意味著這個參數(shù)在函數(shù)體內(nèi)不會被修改,屬于“輸入?yún)?shù)”。在有多個形參的時候,函數(shù)的調(diào)用者可以憑借參數(shù)前是否有const關鍵字,清晰的辨別哪些是輸入?yún)?shù),哪些是可能的輸出參數(shù)。

      (2)合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數(shù),防止其被無意的代碼修改,這樣可以減少bug的出現(xiàn)。const在C++語言中則包含了更豐富的含義,而在C語言中僅意味著:“只能讀的普通變量”,可以稱其為“不能改變的變量”(這個說法似乎很拗口,但卻最準確的表達了C語言中const的本質),在編譯階段需要的常數(shù)仍然只能以#define宏定義!故在C語言中如下程序是非法的: const int SIZE = 10;char a[SIZE];/* 非法:編譯階段不能用到變量 */ 5.關鍵字volatile C語言編譯器會對用戶書寫的代碼進行優(yōu)化,譬如如下代碼: int a,b,c;a = inWord(0x100);/*讀取I/O空間0x100端口的內(nèi)容存入a變量*/ b = a;a = inWord(0x100);/*再次讀取I/O空間0x100端口的內(nèi)容存入a變量*/ c = a;很可能被編譯器優(yōu)化為: int a,b,c;a = inWord(0x100);/*讀取I/O空間0x100端口的內(nèi)容存入a變量*/ b = a;c = a;但是這樣的優(yōu)化結果可能導致錯誤,如果I/O空間0x100端口的內(nèi)容在執(zhí)行第一次讀操作后被其它程序寫入新值,則其實第2次讀操作讀出的內(nèi)容與第一次不同,b和c的值應該不同。在變量a的定義前加上volatile關鍵字可以防止編譯器的類似優(yōu)化,正確的做法是: volatile int a;

      volatile變量可能用于如下幾種情況:

      (1)并行設備的硬件寄存器(如:狀態(tài)寄存器,例中的代碼屬于此類);(2)一個中斷服務子程序中會訪問到的非自動變量(也就是全局變量);(3)多線程應用中被幾個任務共享的變量。6.CPU字長與存儲器位寬不一致處理

      在背景篇中提到,本文特意選擇了一個與CPU字長不一致的存儲芯片,就是為了進行本節(jié)的討論,解決CPU字長與存儲器位寬不一致的情況。80186的字長為16,而NVRAM的位寬為8,在這種情況下,我們需要為NVRAM提供讀寫字節(jié)、字的接口,如下: typedef unsigned char BYTE;typedef unsigned int WORD;

      /* 函數(shù)功能:讀NVRAM中字節(jié)

      * 參數(shù):wOffset,讀取位置相對NVRAM基地址的偏移

      * 返回:讀取到的字節(jié)值 */ extern BYTE ReadByteNVRAM(WORD wOffset){

      LPBYTE lpAddr =(BYTE*)(NVRAM + wOffset * 2);/* 為什么偏移要×2? */

      return *lpAddr;}

      /* 函數(shù)功能:讀NVRAM中字

      * 參數(shù):wOffset,讀取位置相對NVRAM基地址的偏移

      * 返回:讀取到的字 */ extern WORD ReadWordNVRAM(WORD wOffset){

      WORD wTmp = 0;

      LPBYTE lpAddr;

      /* 讀取高位字節(jié) */

      lpAddr =(BYTE*)(NVRAM + wOffset * 2);

      /* 為什么偏移要×2? */

      wTmp +=(*lpAddr)*256;

      /* 讀取低位字節(jié) */

      lpAddr =(BYTE*)(NVRAM +(wOffset +1)* 2);

      /* 為什么偏移要×2? */

      wTmp += *lpAddr;

      return wTmp;}

      /* 函數(shù)功能:向NVRAM中寫一個字節(jié)

      *參數(shù):wOffset,寫入位置相對NVRAM基地址的偏移 *

      byData,欲寫入的字節(jié) */ extern void WriteByteNVRAM(WORD wOffset, BYTE byData){

      … }

      /* 函數(shù)功能:向NVRAM中寫一個字 */ *參數(shù):wOffset,寫入位置相對NVRAM基地址的偏移 *

      wData,欲寫入的字 */ extern void WriteWordNVRAM(WORD wOffset, WORD wData){

      … } 子貢問曰:Why偏移要乘以2? 子曰:請看圖1,16位80186與8位NVRAM之間互連只能以地址線A1對其A0,CPU本身的A0與NVRAM不連接。因此,NVRAM的地址只能是偶數(shù)地址,故每次以2為單位前進!

      圖1 CPU與NVRAM地址線連接

      子貢再問:So why 80186的地址線A0不與NVRAM的A0連接? 子曰:請看《IT論語》之《微機原理篇》,那里面講述了關于計算機組成的圣人之道??偨Y

      本篇主要講述了嵌入式系統(tǒng)C編程中內(nèi)存操作的相關技巧。掌握并深入理解關于數(shù)據(jù)指針、函數(shù)指針、動態(tài)申請內(nèi)存、const及volatile關鍵字等的相關知識,是一個優(yōu)秀的C語言程序設計師的基本要求。當我們已經(jīng)牢固掌握了上述技巧后,我們就已經(jīng)學會了C語言的99%,因為C語言最精華的內(nèi)涵皆在內(nèi)存操作中體現(xiàn)。

      我們之所以在嵌入式系統(tǒng)中使用C語言進行程序設計,99%是因為其強大的內(nèi)存操作能力!

      如果你愛編程,請你愛C語言; 如果你愛C語言,請你愛指針; 如果你愛指針,請你愛指針的指針!

      C語言嵌入式系統(tǒng)編程修煉之道——屏幕操作篇 1.漢字處理

      現(xiàn)在要解決的問題是,嵌入式系統(tǒng)中經(jīng)常要使用的并非是完整的漢字庫,往往只是需要提供數(shù)量有限的漢字供必要的顯示功能。例如,一個微波爐的LCD上沒有必要提供顯示“電子郵件”的功能;一個提供漢字顯示功能的空調(diào)的LCD上不需要顯示一條“短消息”,諸如此類。但是一部手機、小靈通則通常需要包括較完整的漢字庫。

      如果包括的漢字庫較完整,那么,由內(nèi)碼計算出漢字字模在庫中的偏移是十分簡單的:漢字庫是按照區(qū)位的順序排列的,前一個字節(jié)為該漢字的區(qū)號,后一個字節(jié)為該字的位號。每一個區(qū)記錄94個漢字,位號則為該字在該區(qū)中的位置。因此,漢字在漢字庫中的具體位置計算公式為:94*(區(qū)號-1)+位號-1。減1是因為數(shù)組是以0為開始而區(qū)號位號是以1為開始的。只需乘上一個漢字字模占用的字節(jié)數(shù)即可,即:(94*(區(qū)號-1)+位號-1)*一個漢字字模占用字節(jié)數(shù),以16*16點陣字庫為例,計算公式則為:(94*(區(qū)號-1)+(位號-1))*32。漢字庫中從該位置起的32字節(jié)信息記錄了該字的字模信息。

      對于包含較完整漢字庫的系統(tǒng)而言,我們可以以上述規(guī)則計算字模的位置。但是如果僅僅是提供少量漢字呢?譬如幾十至幾百個?最好的做法是: 定義宏:

      # define EX_FONT_CHAR(value)

      # define EX_FONT_UNICODE_VAL(value)(value), # define EX_FONT_ANSI_VAL(value)(value), 定義結構體:

      typedef struct _wide_unicode_font16x16 { WORD value;

      /* 內(nèi)碼 */ BYTE data[32];/* 字模點陣 */ }Unicode;#define CHINESE_CHAR_NUM ?

      /* 漢字數(shù)量 */ 字模的存儲用數(shù)組:

      Unicode chinese[CHINESE_CHAR_NUM] = { {

      EX_FONT_CHAR(“業(yè)”)

      EX_FONT_UNICODE_VAL(0x4e1a)

      {0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0x44, 0x46, 0x24, 0x4c, 0x24, 0x48, 0x14, 0x50, 0x1c, 0x50,0x14, 0x60, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00}

      },{

      EX_FONT_CHAR(“中”)

      EX_FONT_UNICODE_VAL(0x4e2d)

      {0x01, 0x00, 0x01, 0x00, 0x21, 0x08, 0x3f, 0xfc, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08,0x3f, 0xf8, 0x21, 0x08, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00}

      },{

      EX_FONT_CHAR(“云”)

      EX_FONT_UNICODE_VAL(0x4e91)

      {0x00, 0x00, 0x00, 0x30, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xff, 0xfe, 0x03, 0x00, 0x07, 0x00,0x06, 0x40, 0x0c, 0x20, 0x18, 0x10, 0x31, 0xf8, 0x7f, 0x0c, 0x20, 0x08, 0x00, 0x00}

      },{

      EX_FONT_CHAR(“件”)

      EX_FONT_UNICODE_VAL(0x4ef6)

      {0x10, 0x40, 0x1a, 0x40, 0x13, 0x40, 0x32, 0x40, 0x23, 0xfc, 0x64, 0x40, 0xa4, 0x40, 0x28, 0x40, 0x2f, 0xfe,0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40}

      } } 要顯示特定漢字的時候,只需要從數(shù)組中查找內(nèi)碼與要求漢字內(nèi)碼相同的即可獲得字模。如果前面的漢字在數(shù)組中以內(nèi)碼大小順序排列,那么可以以二分查找法更高效的查找到漢字的字模。

      這是一種很有效的組織小漢字庫的方法,它可以保證程序有很好的結構。2.系統(tǒng)時間顯示

      從NVRAM中可以讀取系統(tǒng)的時間,系統(tǒng)一般借助NVRAM產(chǎn)生的秒中斷每秒讀取一次當前時間并在LCD上顯示。關于時間的顯示,有一個效率問題。因為時間有其特殊性,那就是60秒才有一次分鐘的變化,60分鐘才有一次小時變化,如果我們每次都將讀取的時間在屏幕上完全重新刷新一次,則浪費了大量的系統(tǒng)時間。

      一個較好的辦法是我們在時間顯示函數(shù)中以靜態(tài)變量分別存儲小時、分鐘、秒,只有在其內(nèi)容發(fā)生變化的時候才更新其顯示。extern void DisplayTime(…){

      static BYTE byHour,byMinute,bySecond;

      BYTE byNewHour, byNewMinute, byNewSecond;

      byNewHour = GetSysHour();

      byNewMinute = GetSysMinute();

      byNewSecond = GetSysSecond();

      if(byNewHour!= byHour)

      { ?

      /* 顯示小時 */ byHour = byNewHour;}

      if(byNewMinute!= byMinute)

      { ?

      /* 顯示分鐘 */ byMinute = byNewMinute;}

      if(byNewSecond!= bySecond)

      { ?

      /* 顯示秒鐘 */ bySecond = byNewSecond;} } 這個例子也可以順便作為C語言中static關鍵字強大威力的證明。當然,在C++語言里,static具有了更加強大的威力,它使得某些數(shù)據(jù)和函數(shù)脫離“對象”而成為“類”的一部分,正是它的這一特點,成就了軟件的無數(shù)優(yōu)秀設計。3.動畫顯示

      動畫是無所謂有,無所謂無的,靜止的畫面走的路多了,也就成了動畫。隨著時間的變更,在屏幕上顯示不同的靜止畫面,即是動畫之本質。所以,在一個嵌入式系統(tǒng)的LCD上欲顯示動畫,必須借助定時器。沒有硬件或軟件定時器的世界是無法想像的:

      (1)

      沒有定時器,一個操作系統(tǒng)將無法進行時間片的輪轉,于是無法進行多任務的調(diào)度,于是便不再成其為一個多任務操作系統(tǒng);

      (2)

      沒有定時器,一個多媒體播放軟件將無法運作,因為它不知道何時應該切換到下一幀畫面;

      (3)

      沒有定時器,一個網(wǎng)絡協(xié)議將無法運轉,因為其無法獲知何時包傳輸超時并重傳之,無法在特定的時間完成特定的任務。

      因此,沒有定時器將意味著沒有操作系統(tǒng)、沒有網(wǎng)絡、沒有多媒體,這將是怎樣的黑暗?所以,合理并靈活地使用各種定時器,是對一個軟件人的最基本需求!在80186為主芯片的嵌入式系統(tǒng)中,我們需要借助硬件定時器的中斷來作為軟件定時器,在中斷發(fā)生后變更畫面的顯示內(nèi)容。在時間顯示“xx:xx”中讓冒號交替有無,每次秒中斷發(fā)生后,需調(diào)用ShowDot: void ShowDot(){ static BOOL bShowDot = TRUE;

      /* 再一次領略static關鍵字的威力 */ if(bShowDot)

      { showChar(‘:’,xPos,yPos);} else

      { showChar(‘ ’,xPos,yPos);

      } bShowDot =!bShowDot;} 4.菜單操作

      無數(shù)人為之絞盡腦汁的問題終于出現(xiàn)了,在這一節(jié)里,我們將看到,在C語言中哪怕用到一丁點的面向對象思想,軟件結構將會有何等的改觀!筆者曾經(jīng)是個笨蛋,被菜單搞暈了,給出這樣的一個系統(tǒng): 圖1 菜單范例

      要求以鍵盤上的“←→”鍵切換菜單焦點,當用戶在焦點處于某菜單時,若敲擊鍵盤上的OK、CANCEL鍵則調(diào)用該焦點菜單對應之處理函數(shù)。我曾經(jīng)傻傻地這樣做著:

      /* 按下OK鍵 */ void onOkKey(){ /* 判斷在什么焦點菜單上按下Ok鍵,調(diào)用相應處理函數(shù) */ Switch(currentFocus){ case MENU1:

      menu1OnOk();

      break;case MENU2:

      menu2OnOk();

      break;? } } /* 按下Cancel鍵 */ void onCancelKey(){ /* 判斷在什么焦點菜單上按下Cancel鍵,調(diào)用相應處理函數(shù) */ Switch(currentFocus){ case MENU1:

      menu1OnCancel();

      break;case MENU2:

      menu2OnCancel();

      break;? } } 終于有一天,我這樣做了:

      /* 將菜單的屬性和操作“封裝”在一起 */ typedef struct tagSysMenu

      {

      char *text;

      /* 菜單的文本 */

      BYTE xPos;/* 菜單在LCD上的x坐標 */

      BYTE yPos;/* 菜單在LCD上的y坐標 */

      void(*onOkFun)();

      /* 在該菜單上按下ok鍵的處理函數(shù)指針 */

      void(*onCancelFun)();/* 在該菜單上按下cancel鍵的處理函數(shù)指針 */ }SysMenu, *LPSysMenu;當我定義菜單時,只需要這樣: static SysMenu menu[MENU_NUM] = {

      {

      “menu1”, 0, 48, menu1OnOk, menu1OnCancel

      } ,{

      “ menu2”, 7, 48, menu2OnOk, menu2OnCancel

      } ,{

      “ menu3”, 7, 48, menu3OnOk, menu3OnCancel

      } ,{

      “ menu4”, 7, 48, menu4OnOk, menu4OnCancel

      }

      … };OK鍵和CANCEL鍵的處理變成: /* 按下OK鍵 */ void onOkKey(){

      menu[currentFocusMenu].onOkFun();

      } /* 按下Cancel鍵 */ void onCancelKey(){ menu[currentFocusMenu].onCancelFun();

      } 程序被大大簡化了,也開始具有很好的可擴展性!我們僅僅利用了面向對象中的封裝思想,就讓程序結構清晰,其結果是幾乎可以在無需修改程序的情況下在系統(tǒng)中添加更多的菜單,而系統(tǒng)的按鍵處理函數(shù)保持不變。面向對象,真神了!5.模擬MessageBox函數(shù)

      MessageBox函數(shù),這個Windows編程中的超級猛料,不知道是多少入門者第一次用到的函數(shù)。還記得我們第一次在Windows中利用MessageBox輸出“Hello,World!”對話框時新奇的感覺嗎?無法統(tǒng)計,這個世界上究竟有多少程序員學習Windows編程是從MessageBox(“Hello,World!”,?)開始的。在我本科的學校,廣泛流傳著一個詞匯,叫做“‘Hello,World’級程序員”,意指入門級程序員,但似乎“‘Hello,World’級”這個說法更搞笑而形象。

      圖2 經(jīng)典的Hello,World!圖2給出了兩種永恒經(jīng)典的Hello,World對話框,一種只具有“確定”,一種則包含“確定”、“取消”。是的,MessageBox的確有,而且也應該有兩類!這完全是由特定的應用需求決定的。

      嵌入式系統(tǒng)中沒有給我們提供MessageBox,但是鑒于其功能強大,我們需要模擬之,一個模擬的MessageBox函數(shù)為:

      /****************************************** /*

      函數(shù)名稱:

      MessageBox /*

      功能說明:

      彈出式對話框,顯示提醒用戶的信息 /*

      參數(shù)說明:

      lpStr---提醒用戶的字符串輸出信息

      /*

      TYPE---輸出格式(ID_OK = 0, ID_OKCANCEL = 1)/*

      返回值:

      返回對話框接收的鍵值,只有兩種 KEY_OK, KEY_CANCEL /****************************************** typedef enum TYPE

      { ID_OK,ID_OKCANCEL

      }MSG_TYPE;extern

      BYTE MessageBox(LPBYTE lpStr, BYTE TYPE){

      BYTE keyValue =-1;

      ClearScreen();

      /* 清除屏幕 */

      DisplayString(xPos,yPos,lpStr,TRUE);/* 顯示字符串 */

      /* 根據(jù)對話框類型決定是否顯示確定、取消 */

      switch(TYPE)

      {

      case

      ID_OK:

      DisplayString(13,yPos+High+1, “ 確定 ”, 0);

      break;

      case

      ID_OKCANCEL:

      DisplayString(8, yPos+High+1, “ 確定 ”, 0);

      DisplayString(17,yPos+High+1, “ 取消 ”, 0);

      break;

      default:

      break;

      }

      DrawRect(0, 0, 239, yPos+High+16+4);/* 繪制外框 */

      /* MessageBox是模式對話框,阻塞運行,等待按鍵 */

      while((keyValue!= KEY_OK)||(keyValue!= KEY_CANCEL))

      { keyValue = getSysKey();} /* 返回按鍵類型 */ if(keyValue== KEY_OK){ return ID_OK;} else { return ID_CANCEL;} } 上述函數(shù)與我們平素在VC++等中使用的MessageBox是何等的神似???實現(xiàn)這個函數(shù),你會看到它在嵌入式系統(tǒng)中的妙用是無窮的??偨Y

      本篇是本系列文章中技巧性最深的一篇,它提供了嵌入式系統(tǒng)屏幕顯示方面一些很巧妙的處理方法,靈活使用它們,我們將不再被LCD上凌亂不堪的顯示內(nèi)容所困擾。

      屏幕乃嵌入式系統(tǒng)生存之重要輔助,面目可憎之顯示將另用戶逃之夭夭。屏幕編程若處理不好,將是軟件中最不系統(tǒng)、最混亂的部分,筆者曾深受其害。

      C語言嵌入式系統(tǒng)編程修煉之道——鍵盤操作篇 1.處理功能鍵

      功能鍵的問題在于,用戶界面并非固定的,用戶功能鍵的選擇將使屏幕畫面處于不同的顯示狀態(tài)下。例如,主畫面如圖1: 圖1 主畫面

      當用戶在設置XX上按下Enter鍵之后,畫面就切換到了設置XX的界面,如圖2:

      圖2 切換到設置XX畫面

      程序如何判斷用戶處于哪一畫面,并在該畫面的程序狀態(tài)下調(diào)用對應的功能鍵處理函數(shù),而且保證良好的結構,是一個值得思考的問題。

      讓我們來看看WIN32編程中用到的“窗口”概念,當消息(message)被發(fā)送給不同窗口的時候,該窗口的消息處理函數(shù)(是一個callback函數(shù))最終被調(diào)用,而在該窗口的消息處理函數(shù)中,又根據(jù)消息的類型調(diào)用了該窗口中的對應處理函數(shù)。通過這種方式,WIN32有效的組織了不同的窗口,并處理不同窗口情況下的消息。

      我們從中學習到的就是:

      (1)將不同的畫面類比為WIN32中不同的窗口,將窗口中的各種元素(菜單、按鈕等)包含在窗口之中;

      (2)給各個畫面提供一個功能鍵“消息”處理函數(shù),該函數(shù)接收按鍵信息為參數(shù);

      (3)在各畫面的功能鍵“消息”處理函數(shù)中,判斷按鍵類型和當前焦點元素,并調(diào)用對應元素的按鍵處理函數(shù)。

      /* 將窗口元素、消息處理函數(shù)封裝在窗口中 */ struct windows {

      BYTE currentFocus;

      ELEMENT element[ELEMENT_NUM];

      void(*messageFun)(BYTE keyValue);

      … };/* 消息處理函數(shù) */ void messageFunction(BYTE keyValue){

      BYTE i = 0;

      /* 獲得焦點元素 */

      while((element [i].ID!= currentFocus)&&(i < ELEMENT_NUM))

      {

      i++;

      }

      /* “消息映射” */

      if(i < ELEMENT_NUM)

      {

      switch(keyValue)

      {

      case OK:

      element[i].OnOk();

      break;

      }

      } } 在窗口的消息處理函數(shù)中調(diào)用相應元素按鍵函數(shù)的過程類似于“消息映射”,這是我們從WIN32編程中學習到的。編程到了一個境界,很多東西都是相通的了。其它地方的思想可以拿過來為我所用,是為編程中的“拿來主義”。

      在這個例子中,如果我們還想玩得更大一點,我們可以借鑒MFC中處理MESSAGE_MAP的方法,我們也可以學習MFC定義幾個精妙的宏來實現(xiàn)“消息映射”。2.處理數(shù)字鍵

      用戶輸入數(shù)字時是一位一位輸入的,每一位的輸入都對應著屏幕上的一個顯示位置(x坐標,y坐標)。此外,程序還需要記錄該位置輸入的值,所以有效組織用戶數(shù)字輸入的最佳方式是定義一個結構體,將坐標和數(shù)值捆綁在一起: /* 用戶數(shù)字輸入結構體 */ typedef struct tagInputNum

      {

      BYTE byNum;/* 接收用戶輸入賦值 */

      BYTE xPos;

      /* 數(shù)字輸入在屏幕上的顯示位置x坐標 */

      BYTE yPos;

      /* 數(shù)字輸入在屏幕上的顯示位置y坐標 */

      }InputNum, *LPInputNum;那么接收用戶輸入就可以定義一個結構體數(shù)組,用數(shù)組中的各位組成一個完整的數(shù)字:

      InputNum inputElement[NUM_LENGTH];/* 接收用戶數(shù)字輸入的數(shù)組 */ /* 數(shù)字按鍵處理函數(shù) */ extern void onNumKey(BYTE num){

      if(num==0|| num==1)/* 只接收二進制輸入 */

      { /* 在屏幕上顯示用戶輸入 */ DrawText(inputElement[currentElementInputPlace].xPos, inputElement[currentElementInputPlace].yPos, “%1d”, num);

      /* 將輸入賦值給數(shù)組元素 */

      inputElement[currentElementInputPlace].byNum = num;

      /* 焦點及光標右移 */

      moveToRight();

      } } 將數(shù)字每一位輸入的坐標和輸入值捆綁后,在數(shù)字鍵處理函數(shù)中就可以較有結構的組織程序,使程序顯得很緊湊。3.整理用戶輸入

      繼續(xù)第2節(jié)的例子,在第2節(jié)的onNumKey函數(shù)中,只是獲取了數(shù)字的每一位,因而我們需要將其轉化為有效數(shù)據(jù),譬如要轉化為有效的XXX數(shù)據(jù),其方法是:

      /* 從2進制數(shù)據(jù)位轉化為有效數(shù)據(jù):XXX */ void convertToXXX(){

      BYTE i;

      XXX = 0;

      for(i = 0;i < NUM_LENGTH;i++)

      {

      XXX += inputElement[i].byNum*power(2, NUM_LENGTH1);

      }

      } 反之,我們也可能需要在屏幕上顯示那些有效的數(shù)據(jù)位,因為我們也需要能夠反向轉化:

      /* 從有效數(shù)據(jù)轉化為2進制數(shù)據(jù)位:XXX */ void convertFromXXX(){

      BYTE i;

      XXX = 0;

      for(i = 0;i < NUM_LENGTH;i++)

      {

      inputElement[i].byNum = XXX / power(2, NUM_LENGTH1)% 2;

      }

      } 當然在上面的例子中,因為數(shù)據(jù)是2進制的,用power函數(shù)不是很好的選擇,直接用“<< >>”移位操作效率更高,我們僅是為了說明問題的方便。試想,如果用戶輸入是十進制的,power函數(shù)或許是唯一的選擇了??偨Y

      本篇給出了鍵盤操作所涉及的各個方面:功能鍵處理、數(shù)字鍵處理及用戶輸入整理,基本上提供了一個全套的按鍵處理方案。對于功能鍵處理方法,將LCD屏幕與Windows窗口進行類比,提出了較新穎地解決屏幕、鍵盤繁雜交互問題的方案。

      計算機學的許多知識都具有相通性,因而,不斷追趕時髦技術而忽略基本功的做法是徒勞無意的。我們最多需要“精通”三種語言(精通,一個在如今的求職簡歷里泛濫成災的詞語),最佳拍檔是匯編、C、C++(或JAVA),很顯然,如果你“精通”了這三種語言,其它語言你應該是可以很快“熟悉”的,否則你就沒有“精通”它們。

      C語言嵌入式系統(tǒng)編程修煉之道——性能優(yōu)化篇 1.使用宏定義

      在C語言中,宏是產(chǎn)生內(nèi)嵌代碼的唯一方法。對于嵌入式系統(tǒng)而言,為了能達到性能要求,宏是一種很好的代替函數(shù)的方法。

      寫一個“標準”宏MIN,這個宏輸入兩個參數(shù)并返回較小的一個:

      錯誤做法:

      #define MIN(A,B)(A <= B ? A : B)正確做法:

      #define MIN(A,B)((A)<=(B)?(A):(B))對于宏,我們需要知道三點:(1)宏定義“像”函數(shù);

      (2)宏定義不是函數(shù),因而需要括上所有“參數(shù)”;(3)宏定義可能產(chǎn)生副作用。下面的代碼:

      least = MIN(*p++, b);將被替換為:

      ((*p++)<=(b)?(*p++):(b))發(fā)生的事情無法預料。

      因而不要給宏定義傳入有副作用的“參數(shù)”。2.使用寄存器變量

      當對一個變量頻繁被讀寫時,需要反復訪問內(nèi)存,從而花費大量的存取時間。為此,C語言提供了一種變量,即寄存器變量。這種變量存放在CPU的寄存器中,使用時,不需要訪問內(nèi)存,而直接從寄存器中讀寫,從而提高效率。寄存器變量的說明符是register。對于循環(huán)次數(shù)較多的循環(huán)控制變量及循環(huán)體內(nèi)反復使用的變量均可定義為寄存器變量,而循環(huán)計數(shù)是應用寄存器變量的最好候選者。(1)

      只有局部自動變量和形參才可以定義為寄存器變量。因為寄存器變量屬于動態(tài)存儲方式,凡需要采用靜態(tài)存儲方式的量都不能定義為寄存器變量,包括:模塊間全局變量、模塊內(nèi)全局變量、局部static變量;

      (2)

      register是一個“建議”型關鍵字,意指程序建議該變量放在寄存器中,但最終該變量可能因為條件不滿足并未成為寄存器變量,而是被放在了存儲器中,但編譯器中并不報錯(在C++語言中有另一個“建議”型關鍵字:inline)。

      下面是一個采用寄存器變量的例子: /* 求1+2+3+?.+n的值 */ WORD Addition(BYTE n){ register i,s=0;for(i=1;i<=n;i++){ s=s+i;} return s;} 本程序循環(huán)n次,i和s都被頻繁使用,因此可定義為寄存器變量。3.內(nèi)嵌匯編

      程序中對時間要求苛刻的部分可以用內(nèi)嵌匯編來重寫,以帶來速度上的顯著提高。但是,開發(fā)和測試匯編代碼是一件辛苦的工作,它將花費更長的時間,因而要慎重選擇要用匯編的部分。

      在程序中,存在一個80-20原則,即20%的程序消耗了80%的運行時間,因而我們要改進效率,最主要是考慮改進那20%的代碼。

      嵌入式C程序中主要使用在線匯編,即在C程序中直接插入_asm{ }內(nèi)嵌匯編語句:

      /* 把兩個輸入?yún)?shù)的值相加,結果存放到另外一個全局變量中 */ int result;

      void Add(long a, long *b)

      {

      _asm

      {

      MOV

      AX, a

      MOV

      BX, b

      ADD

      AX, [BX]

      MOV

      result, AX

      }

      }

      4.利用硬件特性

      首先要明白CPU對各種存儲器的訪問速度,基本上是:

      CPU內(nèi)部RAM > 外部同步RAM > 外部異步RAM > FLASH/ROM 對于程序代碼,已經(jīng)被燒錄在FLASH或ROM中,我們可以讓CPU直接從其中讀取代碼執(zhí)行,但通常這不是一個好辦法,我們最好在系統(tǒng)啟動后將FLASH或ROM中的目標代碼拷貝入RAM中后再執(zhí)行以提高取指令速度; 對于UART等設備,其內(nèi)部有一定容量的接收BUFFER,我們應盡量在BUFFER被占滿后再向CPU提出中斷。例如計算機終端在向目標機通過RS-232傳遞數(shù)據(jù)時,不宜設置UART只接收到一個BYTE就向CPU提中斷,從而無謂浪費中斷處理時間;

      如果對某設備能采取DMA方式讀取,就采用DMA讀取,DMA讀取方式在讀取目標中包含的存儲信息較大時效率較高,其數(shù)據(jù)傳輸?shù)幕締挝皇菈K,而所傳輸?shù)臄?shù)據(jù)是從設備直接送入內(nèi)存的(或者相反)。DMA方式較之中斷驅動方式,減少了CPU 對外設的干預,進一步提高了CPU與外設的并行操作程度。5.活用位操作

      使用C語言的位操作可以減少除法和取模的運算。在計算機程序中數(shù)據(jù)的位是可以操作的最小數(shù)據(jù)單位,理論上可以用“位運算”來完成所有的運算和操作,因而,靈活的位操作可以有效地提高程序運行的效率。舉例如下: /* 方法1 */ int i,j;i = 879 / 16;j = 562 % 32;

      /* 方法2 */ int i,j;i = 879 >> 4;j = 562-(562 >> 5 << 5);對于以2的指數(shù)次方為“*”、“/”或“%”因子的數(shù)學運算,轉化為移位運算“<< >>”通??梢蕴岣咚惴ㄐ省R驗槌顺\算指令周期通常比移位運算大。

      C語言位運算除了可以提高運算效率外,在嵌入式系統(tǒng)的編程中,它的另一個最典型的應用,而且十分廣泛地正在被使用著的是位間的與(&)、或(|)、非(~)操作,這跟嵌入式系統(tǒng)的編程特點有很大關系。我們通常要對硬件寄存器進行位設置,譬如,我們通過將AM186ER型80186處理器的中斷屏蔽控制寄存器的第低6位設置為0(開中斷2),最通用的做法是: #define INT_I2_MASK

      0x0040

      wTemp = inword(INT_MASK);outword(INT_MASK, wTemp &~INT_I2_MASK);而將該位設置為1的做法是:

      #define INT_I2_MASK

      0x0040

      wTemp = inword(INT_MASK);outword(INT_MASK, wTemp | INT_I2_MASK);判斷該位是否為1的做法是:

      #define INT_I2_MASK

      0x0040

      wTemp = inword(INT_MASK);if(wTemp & INT_I2_MASK){

      ?

      /* 該位為1 */ } 上述方法在嵌入式系統(tǒng)的編程中是非常常見的,我們需要牢固掌握??偨Y

      在性能優(yōu)化方面永遠注意80-20準備,不要優(yōu)化程序中開銷不大的那80%,這是勞而無功的。

      宏定義是C語言中實現(xiàn)類似函數(shù)功能而又不具函數(shù)調(diào)用和返回開銷的較好方法,但宏在本質上不是函數(shù),因而要防止宏展開后出現(xiàn)不可預料的結果,對宏的定義和使用要慎而處之。很遺憾,標準C至今沒有包括C++中inline函數(shù)的功能,inline函數(shù)兼具無調(diào)用開銷和安全的優(yōu)點。

      使用寄存器變量、內(nèi)嵌匯編和活用位操作也是提高程序效率的有效方法。除了編程上的技巧外,為提高系統(tǒng)的運行效率,我們通常也需要最大可能地利用各種硬件設備自身的特點來減小其運轉開銷,例如減小中斷次數(shù)、利用DMA傳輸方式等。

      下載C語言 C++ volatile關鍵字作用與嵌入式系統(tǒng)程序員(精選五篇)word格式文檔
      下載C語言 C++ volatile關鍵字作用與嵌入式系統(tǒng)程序員(精選五篇).doc
      將本文檔下載到自己電腦,方便修改和收藏,請勿使用迅雷等下載。
      點此處下載文檔

      文檔為doc格式


      聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻自行上傳,本網(wǎng)站不擁有所有權,未作人工編輯處理,也不承擔相關法律責任。如果您發(fā)現(xiàn)有涉嫌版權的內(nèi)容,歡迎發(fā)送郵件至:645879355@qq.com 進行舉報,并提供相關證據(jù),工作人員會在5個工作日內(nèi)聯(lián)系你,一經(jīng)查實,本站將立刻刪除涉嫌侵權內(nèi)容。

      相關范文推薦

        C語言關鍵字與標識符總結(大全五篇)

        C語言關鍵字(32個) int基本整型數(shù)據(jù)2字節(jié)(1 byte = 8 bits) long長整型數(shù)據(jù) char單字節(jié)整型數(shù)據(jù)1字節(jié) float定義單精度浮點型數(shù)據(jù) double定義雙精度浮點型數(shù)據(jù)4字節(jié) short短整型......

        詳細解析C語言Sizeof關鍵字_(程序員筆試常見問題)[小編推薦]

        sizeof,一個其貌不揚的家伙,引無數(shù)菜鳥竟折腰,小蝦我當初也沒少犯迷糊,秉著“ 辛苦我一個,幸福千萬人”的偉大思想,我決定將其盡可能詳細的總結一下。 但當我總結的時候才發(fā)現(xiàn),這個......

        嵌入式系統(tǒng)應用與學習心得體會

        μC/OS嵌入式實時操作系統(tǒng)的應用和學習心得 μC/OS-II 是一種基于優(yōu)先級的搶占式多任務實時操作系統(tǒng),包含了實時內(nèi)核、任務管理、時間管理、任務間通信同步(信號量,郵箱,消息......

        微處理器系統(tǒng)結構與嵌入式系統(tǒng)教學大綱

        《微處理器系統(tǒng)原理及嵌入式系統(tǒng)設計》課程教學大綱 課程編號:20082008 學 時 數(shù):80 適用專業(yè):通信工程、網(wǎng)絡工程、信息工程 學 分 數(shù):5 開課學期:第5 學期 先修課程:數(shù)字邏......

        單片機與嵌入式系統(tǒng)實驗報告三

        武夷學院實驗報告課程名稱:__單片機與嵌入式技術__ 項目名稱:鍵盤中斷編程姓名:_安小圣_ 專業(yè):_計科_ 班級:_10(2)班__學號:_20104061049___同組成員_____無____1 注:1、實驗準備部分......

        C語言測試:嵌入式程序員必須知道的16個問題

        C語言測試是招聘嵌入式系統(tǒng)程序員過程中必須而且有效的方法。這些年,我既參加也組織了許多這種測試,在這過程中我意識到這些測試能為帶面試者和被面試者提供許多有用信息,此外,......

        C語言筆試題目及參考答案-嵌入式程序員-安徽用友軟件有限公司

        約定: 1) 下面的測試題中, 認為所有必須的頭文件都已經(jīng)正確的包含了 2) 數(shù)據(jù)類型 char 一個字節(jié) 1 byte int 兩個字節(jié) 2 byte (16位系統(tǒng), 認為整型是2個字節(jié)) long int 四個......

        7年經(jīng)驗總結,C語言嵌入式系統(tǒng)_編程規(guī)范_編程思想

        嵌入式系統(tǒng)編程規(guī)范 李紅志 程序的可讀性、可擴展性、可復用性、易維護性、 語法是代碼的入門,算法是代碼的靈魂。 第1章 編程常見錯誤 1.1、 語法錯誤 1、 錯用函數(shù)數(shù)據(jù)類......