2013年8月30日 星期五

嵌入式C語言

  • 使用指標控制硬体的方式
  • 位元運算的常用的語法
  • 嵌入式中的大/小位元組的排列及對齊
  1. 記憶体指標運算

在嵌入式系統中,對於記憶体位址可分為:

  • 系統記憶体RAM、ROM...
  • 處理器內部的暫存器映射
  • 處理器外部的暫存器映射

image

 

基本運算有兩種:取指令和讀寫記憶体。

而在C語言中,用來存取記憶体的是用指標。

而除了用來指向變數/記憶体配置.

   1: int a;
   2: int *p=&a;
   3:  
   4: void *p=malloc(sizeof(int)*10);

除可以用來指向一個絕對的數值。因為在嵌入式中,輸出裝置暫存器和外部零件的記憶体映射的位址空間都可能都是固定的,因為可以使用一個"固定(絕對)的指標"來處理。



   1: unsigned char *p=(unsigned char*) 0x00040;
   2: *p=0xf0;
   3: 等同
   4: *(unsigned char*) 0x00040=0xf0;
   5: 讀取:
   6: unsigned char a;
   7: a=*(unsigned char*)0x0040;



  1. 指標的類型:

    unsigned char * ,是指定的位址寫入一個位元組(8 bits);若是要寫入16 bits則需宣告成unsigned short *。


    image


    由於對記憶体位址的運算中,都將記憶体的資料視為bit組合,沒有符號位元的楖念。故應使用unsigned 形式的資料對記憶体運算。以及在32位元的系統中,compiler會視"int"及"long"為32 bits。


    在使用C語言時,其對8/16及32bit 讀寫運算可寫成macro



       1: #define write8b(addr,data) *(volatile unsigned char* (addr))=(unsigned char)data
       2: #define read8b(addr) (*(volatile unsigned char *(addr))) 
       3: #define write16b(addr,data) *(volatile unsigned short* (addr))=(unsigned short)data
       4: #define read16b(addr) (*(volatile unsigned short *(addr)))
       5: #define write32b(addr,data) *(volatile unsigned long* (addr))=(unsigned long)data
       6: #define read32b(addr) (*(volatile unsigned long *(addr)))

  2. 指標的增量:

    在對指標變數的運算中,有時需對指標變數進行加滅運算:



       1: unsigned char* p=(unsigend char*) 0x0040;
       2: p++; //p=0x0041;
       3: *p=0xf0;

    其指標的加減的含義:其增/減的單位視其指標的類型決定。故



       1: unsigned int*p =0x0040;
       2: p++; //p=0x0044;
       3: *p=0x0000_0000;

  3. 指標的類型的轉換


在C語言中,指標的類型可以在使用時進行轉換。指標本來是一個"32bit unsigned int"。因此,各種指標都可以相互轉換,而且"指標在轉換過程中並沒有任何實質性的變化,只是告訴compiler,目前的指標指向何種的記憶体區域"



   1: unsigned int *p=(unsigned int*) 0x0040;
   2: *(unsigned char)p=0xf0;

volatile的使用


volatile在C語中,會將變數可能會被意想不到地改變,故其compiler就不會去假設其變數的值,即最佳化在用這個變數時都必須每次去讀取它的值.故其使用情況



  1. 並列設備的硬体暫存器(例如:狀態暫存器)
  2. 在中斷服務副程中會存取到的非自動變數(即全域變數)
  3. 多執行緒應用中被幾個task並用的變數

一般情況下,變數是存在記憶休中但也可能存在暫存器register中。且當變數沒被改變時,其存取變數時,只會去存取暫存器中的變數。



   1: void test()
   2: {
   3:  volatile char temp;
   4: }


此時,當變數temp被宣告成volatile型態時,它就不會被最佳化,所以每次都需到記憶体中存取。但在事實上,其temp是建立在stack上,並不會"被外部改變"。故一般容易被更改的變數都是"以指標指向的內容"。



   1: int *temp,a1,a2;
   2: temp==(unsigned int*) 0x0040;
   3: a1=*temp;
   4: a2=*temp;
   5: //---------------------//
   6: //可能在經優化後
   7: //a1=*temp;
   8: //a2=a1; //,error *temp已改變
   9: //故我們需要將其temp 宣告成volatie


其上述的情況,是有可能出現在"執行中該程式被外部中斷,後續的程式碼沒有立即執行,因此出現不同情況"。


 


嵌入式系統指標的實際應用


針對LPC2000系統處理器的暫存器實現埠的高低點電位轉換。



   1: //pin selection controller
   2: #define PINSEL0 (*((VOLATILE UNSIGNED LONG*)0xE002C000))
   3: //I/O special register
   4: #define IO0PIN (*((volatile unsigned long*) 0xE0028000))
   5: #define IO0SET (*((volatile unsigned long*) 0xE0028004))
   6: #define IO0DIR (*((volatile unsigned long*) 0xE0028008))
   7: #define IO0CLR (*((volatile unsigned long*) 0xE002800C))


   1: void DelayNs(uint32 dly)
   2: {
   3:     uint32 i;
   4:     for(;dly>0;dly--)
   5:     {
   6:         for(i=0;i<4000;i++)
   7:             ;
   8:     }
   9: }
  10:  
  11: int main(void)
  12: {
  13:     PINSEL0=0x00000000;// select gpio
  14:     IO0DIR=0x00000080;// port 0.6 is output
  15:     while(1){
  16:         IO0SET=0x00000080;//p0.6=1
  17:         DelayNs(199);
  18:         IO0CLR=0x00000080;//p0.6=1
  19:         DelayNs(199);
  20:     }
  21:  
  22: }


 


位元運算


位元運算元按二進制進行處理,包括有 &、|、~、^、<< 及>>。



  1. 位元運算的意義:

    硬体暫存器可能由若干位元組成,且各位元有RW、OR 及 OW 或是均不可。其硬体的暫存器又可分為



    • 資料暫存器(RW):由整數個byte組成
    • 狀態暫存器(OR): 一般是唯讀的,以程式來查詢來獲得當前硬体狀態。
    • 控制暫存器(RW,OW): 一般是可讀寫的,但部份是唯寫。

  2. 位元運算的語法 :


      • 位元組:在嵌入式中,很多暫存器可能是32/16位元的。有時只需對其中寫入或讀取單個位元組

        • 讀取:

             1: #define GETBYTE0 (x) (x & 0x000000ff)
             2: #define GETBYTE1 (x) ((x>>8) & 0x000000ff)
             3: #define GETBYTE2 (x) ((x>>16) & 0x000000ff)
             4: #define GETBYTE3 (x) ((x>>24) & 0x000000ff)


        • Setting



      •    1: #define GETBYTE0 (x) (x & 0x000000ff)
           2: #define GETBYTE1 (x) ((x>>8) & 0x000000ff)
           3: #define GETBYTE2 (x) ((x>>16) & 0x000000ff)
           4: #define GETBYTE3 (x) ((x>>24) & 0x000000ff)
           5:  
           6: #define SETBYTE0 (x) x|=0x000000ff
           7: #define SETBYTE1 (x) x|=0x0000ff00
           8: #define SETBYTE2 (x) x|=0x00ff0000
           9: #define SETBYTE3 (x) x|=0xff000000
          10:  
          11: #define CLRBYTE0 (x) x&=0xffffff00
          12: #define CLRBYTE1 (x) x&=0xffff00ff
          13: #define CLRBYTE2 (x) x&=0xff00ffff
          14: #define CLRBYTE3 (x) x&=0x00ffffff


      • 整個字的設置: 在嵌入式中相對應的暫存器中,有時需對整個暫存器進行設置,例如初始化階段,整個暫存器的所有位元組都需設置初始值。 由於不同位完可能代表不同的域。一般是按照位元的組合來處理,但寫程式時不需計算這些值,而由compiler處理。如下




        • [1:0] is 01b



        • [5:2] is 1110b



        • [7:6] is 10b



        • [10:7] is 13



        • [30:11] is 0b



        • [31] is 1b



      • x=(0x1) | (0xe<<2) | (0x2<<6) | (13<<7) | (1<<31) ; 每組位元都可以透過移位元來設置;



      • 需要設置的數值 << 移位元的數目


  3. 指定位元和位元組運算 : 需設置和讀取暫存器的指定位元和位元組。對於單個位元的運算時,取單個位元、單個位元設為0及設為1

    • 取register中第10bit 是否為1

         1: if(x &(1<<10))
         2: {
         3: 值為1的處理;
         4: }
         5: else
         6: {
         7: 為0時處理
         8: }
         9:  
        10: //設0或1的macro
        11: #define SETBIT(x,n) x=x|(1<<n);
        12: #define CLRBIT(x,n) x=x&~(1<<n);
        13:  
        14: //位元組的運算
        15: //1.擷取一個31 bit到 第n bits的值
        16: #define TOPBITS(value32u,n) (value32u >> (n))
        17: //2.擷取一個從m 到 n的位元
        18: #define BITS(value32u,m,n) ((unsigned int)(value32u<<(31-(n)))>>((31-(n))+(m)))
        19: 故若需查詢一個32bits 暫存器中的bit 6,7狀態
        20: status=BITS(x,6,7)
        21: switch(status)
        22: {    
        23:     case 0x0:
        24:     //......
        25:     break;
        26:     case 0x1:
        27:     //....
        28:     break;
        29:     default:
        30:     //.......
        31:     break;
        32: }



Big/Little Endian及對齊問題


處理器在處理多個位元組整体存取(long,int,short)時,就會涉及各架構的排列法的問題。



   1: char a1,a2,a3,a4;
   2: unsigned char *p;
   3: unsigned long a=0x76543210;
   4: p=(unsigned char*)&a;
   5: c1=*p;
   6: c2=*(p+1);
   7: c3=*(p+2);
   8: c4=*(p+3);



big endian:(start)


c1=0x76;c2=0x54; c3=0x32;c4=0x10;


little endian:


c4=0x76;c3=0x54; c2=0x32;c1=0x10;



   1: typedef struct _Byte4{
   2: unsigned char Byte0;
   3: unsigned char Byte1;
   4: unsigned char Byte2;
   5: unsigned char Byte3;
   6: }Byte4;
   7:  
   8: typedf union _Data32{
   9: unsigned long data;
  10: Byte4 dataByte;
  11: }Data32;
  12:  
  13: int main()
  14: {
  15: Data32 a;
  16: a.data=0x11223344;
  17: printf("Databyte (0.1,2,3):(%x,%x,%x,%x)\n",
  18:         a.dataByte.Byte0,a.dataByte.Byte1,a.dataByte.Byte2,a.dataByte.Byte3);

其中以union 共存Data32來看其存取不同。不論是little/big endian其Byte0均為低位址。而其用對unsigned long data,設定其值,再用其union中的byte來看其順序;


little endian (0,1,2,3):  : 44,33,22,11


big endian(0,1,2,3): 11,22,33,44


 


記憶体對齊問題:


資料在記憶体的位址(4byte data(int,long) 為4個倍數; 2byte data(short) 則為2的倍數)。


在區域變數中,其資料在stack(由高到低)中建立時其會依其類型來做對齊。不足時,會自己補齊


如 sp=0; build 3 variable: long,char,short


long ,sp=4;


char,sp=5;


short ,sp=8; //from 6 ,not 5


區域變數在stack會由compiler控制,但是在程式中則不一定了。



   1: unsigned long i=0x01020304;
   2: unsigned char *p;
   3: p=(unsigned char*) &i;
   4: p++;
   5: (unsigned short*) p=0xffff;

我們所要的結果應為0x01ffff04;


但首先,設sp=4;


1.p++; sp=5;


2.short 為2 的倍數,故不能對其做寫入動作。


非對齊的記憶体運算在不同的系統結構中會產生不同的結果。


結構成員的對齊問題:


一般下,compiler會對struct先做對齊動作:



   1: typedef struct _S1{
   2: char m1;
   3: int m2;
   4: char m3;
   5: short m4;
   6: }S1;

若執行sizeof(S1) != 1+4+1+2.=12


而是對其struct中內容變數,會去做對齊動作。


image


注意其各變數的位址。及其struct size。



   1: struct S2{
   2: int m1;
   3: char m2;
   4: };

sizeof(S2)=8, 以其int為做為大小的倍數。即以最大型態為主。


 



   1: struct S6{
   2: short m[8];
   3: };
   4:  
   5: struct S6_1{
   6: S6 m1;
   7: char m2;
   8: }



S6 size==2x8=16;


S6_1 size=18. 可視為展開.


S6_1{ short m[8];char m2}


 


而在想要用指標來存取時struct element時,如S1



   1: S1 data;
   2: //....
   3: S1 * p1=&data;
   4: int *p2=(int*)((unsigned int)p1+sizeof(char));
   5: printf("data address =%x\n",p2);

struct address+struct first element .來存取第二個element.但因pad使其發生


1.非4的倍數address 存取 , int


2.非second element的位址


解決方法:


a.針對其pad


b.改變順序



   1: /*typedef struct _S1{
   2: char m1;
   3: int m2;
   4: char m3;
   5: short m4;
   6: }S1;*/
   7:  
   8: typedef struct _S1{
   9: char m1;
  10: char pad1;
  11: short pad2;
  12: int m2;
  13: char m3;
  14: char pad3;
  15: short m4;
  16: }S1_2;
  17:  
  18: typedef struct _S1{
  19: char m1;
  20: char m3;
  21: short m4;
  22: int m2;
  23: }S1_3;
  24:  
  25:  

沒有留言:

張貼留言