關於ARM上編程的

ARM彙編優化
 要做程序的優化,最徹底的方法當然是彙編!還有除了彙編以外(除了二進制)能讓你對你的處理器有更全面的控制嗎?!對於ARM彙編,作爲一個初學者,也就只好先補補基礎了@_@。
     首先,程序段的定義從AREA 開始,它命名一個代碼區域,注意,用非阿拉伯數字作爲名字時,應該用|把名字包起來,CODE關鍵字聲明程序(猜測),readonly聲明訪問權限(猜測)。EXPORT 來表示某個可以用作外部連接的符號(簡單點,應該就是函數名?)。END用來結尾。
#eg:
    AREA    |.text|, CODE, READONLY
    EXPORT   square
 
    ; int square(int i)
 
square      ;armcc把不縮進的正文作爲一個標號定義
           MUL    r1,r0,r0
           MOV   r0,r1           ;ARM乘法指令有一個限制,就是目標寄存器不能和第一個參數寄存器相同
           MOV   pc,lr            ;對Thumb指令,應該改爲BX lr
           END
 
      使用import,可以聲明其他文件中定義的標號,要用ARM C庫的話,就import |Lib$Request$armlib|, WEAK表示本行的標號如果找不到,不會報告連接錯誤。如果程序包含主程序main,那麼要引入標號__main,代表C庫初始化的開始。RN可以讓用戶給寄存器命名。
#eg:
     AREA            |.text|, CODE,READONLY
 
     EXPORT     main
 
     IMPORT     |Lib$$Request$$armlib|, WEAK
     IMPORT     __main             ;C library entry
     IMPORT     printf                ;prints to stdout
 
i    RN   4
 
        ;int main(void)
main
          STMFD      sp!,{i,lr}
          MOV         i,#0
loop
          ADR          r0,print_string
          MOV         r1,i
          MUL          r2,i,i
          BL            printf
          ADD         i,i,#1
          CMP          i,#10
          BLT          loop
          LDMFD      sp!,{i,pc}
 
print_string
         DCB         " Square of %d is %d/n", 0
         END
 
MAP(別名^)和FIELD(別名#),可以在堆棧中爲變量和數組定義和分配空間。
 
              MAP     0                             ;map symbols to offsets starting at offset 0?
a            FIELD    4                             ;a is 4 bytes integer(at offset 0)
b            FIELD    2                            ;b is bytes integer(at offset 4)
c            FIELD    64                           ;c is an array of 64 characters (at offset 6)
length     FIELD   0                             ;length records the current offset reached?
 
MACRO用來聲明宏:
 
             MACRO
             CHECKSUM $ alignment
checksum_$ alignment
            LDR         w,[data],#4
l0         ;loop
           IF $ alignment<>0
               ADD     sum,sum,w,LSR #8, $alignement
               LDR      w,[data],#4
               SUBS    N,N,#1
               ADD     sum,sum,w,LSL#32-8* $ alignment
           ELSE
               ADD     sum,sum,w
               LDR      w,[data],#4
               SUBS    N,N,#1
           ENDIF
           BGT         %BT l0
           MOV         pc,lr
           MEND
 
      針對彙編的優化主要是面向硬件的。首先是流水線,有些load指令要需要多個週期來完成,可以通過調整指令的順序(當然要保證邏輯)來改善性能。另外,儘量讓程序只是用寄存器,方法是搞清楚數據佔用寄存器的時間關係,實現寄存器有效的分時複用。另外,可以將長度較小的變量合併到一個32位寄存器中保存,以節省寄存器。由於PC可以通過程序操作,對於條件指令,可以直接用PC與形成分支的參數作運算來尋找對應的分支:
            ;int switch_relative(int x)
switch_relative
            CMP          x,#8
            ADDLT      pc,pc,x,LSL,#2
            B              method_d      ;利用流水線,如果PC還是按順序那麼default分支的預取址就不
                                                ;會被沖掉
            B              method_0
            B              method_1
 

 

 

 ARM上的C編程
1.arm c編譯器默認char類型是8位無符號的,與其它編譯器有點不同
2.局部變量最好用int型,因爲寄存器是32位的,如果變量不是32位的就需要額外的指令限制範圍.
  例如: 變量i,操作i++ ,如果int i, 則只需add r1,r1,#1  如果char i,則變成add r1,r1,#1 
and r1,r1, 0xff  .多了一條指令

3.循環最好用do{}while()型的,相比for(;;)型循環每次循環可以節省3條指令

4.函數參數也最好用int 型的,例如 short add(short x,short y)
      編譯器爲了保證輸入參數的是short型的會添加額外的指令,比如確保x是short型的,需要
      mov r0,r0,lsr #16    mov r0,r0,asr #16

5.函數參數最好不要超過4個,因爲前4個參數是通過寄存器r0-r3傳遞的,超過4個後的參數使用堆棧傳遞,速度慢多了.

6.適當的展開循環.循環有一定的開銷,在一個循環中多做幾遍操作,減少循環的次數可以減少循環的開銷.
 例如:  i = 0;         int i;
               do                                        do{
              {                                             i++;
                 i++;                                     i++;
              }while(i<64)   改爲             i++;}while(i<64)
    當然,這樣做也增加了代碼長度.

7.使用減計數到0的循環結構,這樣就不用用寄存器保存終止值.

8.使用無符號的循環計數值,循環條件是i!=0,而不是i>0, 這樣循環的開銷只有2條指令 


 ARM上的彙編優化小方法
1. 加減法,邏輯操作佔一個週期,目的地址是PC寄存器時增加一個週期。分支指令佔3個週期。在cache命中的情況下,16位和8位的裝載指令(LDRH、LDRH等)佔一個週期,但緊跟的2個週期不能使用裝入的數據。32位裝載指令佔一個週期,緊跟的一個週期不能使用裝載數據。如果裝載入PC,同樣要增加2個週期。
             LDR    r1,[r2]        ADD  r1,r1,r3       ADD  r4,r4,r5    佔4個週期
改變次序後
             LDR    r1,[r2]        ADD  r4,r4,r5        ADD  r1,r1,r3    佔3個週期

2. load指令佔時間比較長,在循環中可以使用預載的方法將load與跳轉指令放在一起,減少流水線的斷流。
例如:
loop
    LDRB   r2,[r1]
     ...............           //do
    B            loop
更改爲
   LDRB   r2,[r1]
loop
    ..............            //do
    LDRB   r2,[r1]
    B          loop

3. 循環展開時,可以在計算i步時就加載i+1步的數據,在i步的結果還沒準備好時執行i+1步計算。

4. ARM只有16個可見寄存器,其中14個通用寄存器,1個堆棧指針r13,1個程序計數器r15。在圖像處理的應用中很多是8位的操作數,可以利用32爲寄存器一次進行兩組運算。
例如:加操作      100 + 50    和       2 + 3
    位                     24                16               8                 0
    操作數1           0                100              0                 2
    操作數2           0                 50                0                3
    結果                 0                 150              0                5

5. 寄存器數量不夠時,可用32爲寄存器保存兩個16位變量和4個8位變量。

 


初入嵌入式軟件開發不久,最近在看UC/OS2的內核源碼,感覺很有意思,記下一些學習過程,歡迎大家拍磚。

 
嵌入式軟件經常要同時完成若干任務,可以在無人干預的情況下應對所有的事件及異常,並且可以根據事件的輕重緩急自動保證最先完成最緊急的任務。
 
嵌入式軟件由RTOS跟其上跑的應用部分軟件組成,應用部分軟件可簡單看成一個個任務,每個任務可以對相關的外界產生的事件或是異常響應。而RTOS的核心功能就是管理各個任務,並建立起任務和外界事件的聯繫。
 
一個典型的任務示意如下:
 
void Task()
{
       for (;;)
       {
              //Do something initial;
              OSFlagPend();
              OSMboxPend();
              OSMutexPend();
              OSQPend();
              OSSempend();
 
              OSTaskPend(priority);
              OSTaskDel(priority);
              OSTimeDly();
             
              /* application code */
       }
}
 
首先任務應該包含一個無限循環,這個循環意在一直處理跟任務自己感興趣的事件。可以看到上面的示意代碼裏有一串的Pend調用,這些Pend函數就是每個RTOS都會提供的,使用了這些Pend調用後RTOS就可以讓這個任務與它感興趣的事件建立起聯繫。如果有它感興趣的事件發生,就會向下執行到application code來處理這個事件。處理完以後又進入循環回等待它感興趣事件的狀態,直到有它感興趣的事件發生,然後又處理,如此周而復此。如果沒有這個任務感興趣事件發生會怎麼呢?那RTOS就會掛起這個任務,去執行其它的任務處理其自身感興趣的事件。如果沒有任何事件發生會怎麼樣呢?這種情況下通常RTOS會運行一個自建的任務TaskIdle(), 這個任務什麼事也不幹,通常是給一個整數進行自加動作。
上面就是嵌入式軟件開發最根本的東西,但RTOS是究竟是怎樣工作的呢?後續日子裏我們再一起學習分享吧。^0^ 

發佈了9 篇原創文章 · 獲贊 2 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章