ARM匯編學習筆記

ARM 182瀏覽

 這兩天參加了一個編寫操作系統的項目,因為要做很多底層的東西,而且這個操作系統是嵌入式的,所以開始學習ARM匯編,發現ARM匯編和一般PC平臺上的匯編有很多不同,但主要還是關鍵字和偽碼上的,其編程思想還是相同的。現將一些學習感悟部分列出來,希望能給有問題的人一點幫助。
    1、ARM匯編的格式:
    在ARM匯編里,有些字符是用來標記行號的,這些字符要求頂格寫;有些偽碼是需要成對出現的,示例ENTRY和END,就需要對齊出現,也就是說他們要么都頂格,要么都空相等的空,否則編譯器將報錯。常量定義需要頂格書寫,不然,編譯器同樣會報錯。
    2、字符串變量的值是一系列的字符,并且使用雙引號作為分界符,如果要在字符串中使用雙引號,則必須連續使用兩個雙引號。
    3、在使用LDR時,當格式是LDR r0,=0x022248,則第二個參數表示地址,即0x022248,同樣的,當src變量代表一個數組時,需要將r0寄存器指向src則需要這樣賦值:LDR r0,=src     當格式是LDR r0,[r2],則第二個參數表示寄存器,我的理解是[]符號表示取內容,r2本身表示一個寄存器地址,取內容候將其存取r0這個寄存器中。
    4、在語句:
       CMP r0,#num
       BHS stop
       書上意思是:如果r0寄存器中的值比num大的話,程序就跳轉到stop標記的行。但是,實際測試的時候,我發現如果r0和num相等也能跳轉到stop標記的行,也就是說只要r0小于num才不會跳轉。
   
     下面就兩個具體的例子談談ARM匯編(這是我昨天好不容易看懂的,呵呵)。
     第一個是使用跳轉表解決分支轉移問題的例程,源代碼如下(保存的時候請將文件后綴名改為s):  
      AREA JumpTest,CODE,READONLY
      CODE32
 num  EQU  4

 ENTRY
 
start
      MOV  r0, #4
      MOV  r1, #3
      MOV  r2, #2
      MOV  r3, #0
 
      CMP  r0,  #num
      BHS  stop
 
      ADR  r4, JumpTable
 
      CMP  r0, #2
      MOVEQ  r3, #0
      LDREQ  pc, [r4,r3,LSL #2]
 
      CMP  r0, #3
      MOVEQ  r3, #1
      LDREQ  pc, [r4,r3,LSL #2]
 
      CMP  r0, #4
      MOVEQ  r3, #2
      LDREQ  pc, [r4,r3,LSL #2]
 
      CMP  r0, #1
      MOVEQ  r3, #3
      LDREQ  pc, [r4,r3,LSL #2]
 
DEFAULT
      MOVEQ  r0, #0
 
SWITCHEND

stop
      MOV  r0, #0x18
      LDR  r1, =0x20026
      SWI  0x123456
 
JumpTable
      DCD  CASE1
      DCD  CASE2
      DCD  CASE3
      DCD  CASE4
      DCD  DEFAULT
 
CASE1
      ADD  r0, r1, r2
      B  SWITCHEND
 
CASE2
      SUB  r0, r1, r2
      B  SWITCHEND
 
CASE3
      ORR  r0, r1, r2
      B  SWITCHEND
 
CASE4
      AND  r0, r1, r2
      B  SWITCHEND
 END
    程序其實很簡單,可見我有多愚笨!還是簡要介紹一下這段代碼吧。首先用AREA偽代碼加上CODE,表明下面引出的將是一個代碼段(于此相對的還有數據段DATA),ENTRY 和END成對出現,說明他們之間的代碼是程序的主體。start段給寄存器初始化。ADR  r4, JumpTable一句是將相當于數組的JumpTable的地址付給r4這個寄存器。

    stop一段是用來是程序退出的,第一個語句“MOV r0,#0x18”將r0賦值為0x18,這個立即數對應于宏angel_SWIreason_ReportException。表示r1中存放的執行狀態。語句“LDR r1,=0x20026”將r1的值設置成ADP_Stopped_ApplicationExit,該宏表示程序正常退出。然后使用SWI,語句“SWI 0x123456”結束程序,將CPU的控制權交回調試器手中。

    在JumpTable表中,DCD類型的數組包含四個字,所以,當實現CASE跳轉的時候,需要將給出的索引乘上4,才是真正前進的地址數。

 

    再看一個用匯編實現冒泡排序的例程:

     AREA Sort,CODE,READONLY
 ENTRY
 
start
     MOV r4,#0
     LDR r6,=src
     ADD r6,r6,#len
 
outer
     LDR r1,=src
 
inner
     LDR r2,[r1]
     LDR r3,[r1,#4]
     CMP r2,r3
     STRGT r3,[r1]
     STRGT r2,[r1,#4]
     ADD r1,r1,#4
     CMP r1,r6
     BLT inner
 
     ADD r4,r4,#4
     CMP r4,#len
     SUBLE r6,r6,#4
     BLE outer
 
stop
     MOV r0,#0x18
     LDR r1,=0x20026
     SWI 0x123456
 
     AREA Array,DATA,READWRITE
src DCD 2,4,10,8,14,1,20
len EQU 7*4
     END
     用匯編實現循環需要跳轉指令,但是因為ARM系統只有一個CPSR寄存器,所以要實現雙重循環還是有些難度。上面這個代碼還是有相當大的借鑒意義。程序不難讀懂,和C語言的冒泡排序基本思路是完全一樣的。

 

 

 

Load CodeWarrior from the Start Menu.
Create a new project (File | New), select ARM Executable Image and give it the name "hello".
Create a new assembler source file (File | New Text File) and paste the following code in it.
            ; Hello world in ARM assembler

            AREA text, CODE
            ; This section is called "text", and contains code

            ENTRY

            ; Print "Hello world"

            ; Get the offset to the string in r4.
            adr       r4, hello           ;; "address in register"

loop        ; "loop" is a label and designates an address
            ; Call putchar to display each character
            ; to illustrate how a loop works

            ldrb      r0, [r4], #1        ; Get next byte and post-index r4
            cmp       r0, #0              ; Stop when we hit a null
            beq       outputstring        ;; "branch if equal" = cond. goto

            bl        putchar            
            b         loop                ;; "branch" =  goto

outputstring
            ; Alternatively, use putstring to write out the
            ; whole string in one go
            adr       r0, hello
            bl        putstring           ;; "branch+link" = subroutine call

finish
            ; Standard exit code: SWI 0x123456, calling routine 0x18
            ; with argument 0x20026
            mov       r0, #0x18
            mov       r1, #0x20000        ; build the "difficult" number...
            add       r1, r1, #0x26       ; ...in two steps
            SWI       0x123456            ;; "software interrupt" = sys call

hello
            DCB       "Hello World/n",0

            END

 

 

 

 

 

 

 

  

 

從下面的一個ARM 匯編小程序要弄懂的以下三個問題:

1).在ARM狀態轉到THUNB狀態和BX的應用

2).匯編的架構

3).SWI指令的使用

    AREA    ADDREG,CODE,READONLY

     ENTRY

MAIN

      ADR  r0,ThunbProg + 1  ;(為什么要加1呢?因為BX指令跳轉到指定的地址執行程序   時,   若   (BX{cond}  Rm)Rm的位[0]為1,則跳轉時自動將CPSR中的標志T置位即把目標 代碼解釋為 Thunb代碼)

        BX    r0

       CODE16

ThunbProg

       mov r2,#2

      mov r3,#3

     add r2,r2,r3

    ADR r0,ARMProg

    BX  ro

    CODE32

ARMProg

     mov r4,#4

    mov r5,#5

    add  r4,r4,r5

             stop   mov r0,#0x18

              LDR  r1,=0x20026

             SWI   0x123456

             END

SWI--軟中斷指令:

SWI指令用于產生軟中斷,從擁護模式變換到管理模式,CPSR保存到管理模式的SPSR中.

 SWI{cond}      immed_24      ;immed_24為軟中斷號(服務類型)

使用SWI指令時,通常使用以下兩種方法進行傳遞參數,SWI 異常中斷處理程序就可以提供相關的服務,這兩種方法均是用戶軟件協定.SWI異常中斷處理程序要通過讀取引起軟中斷的SWI指令,以取得24位立即數.

(1) 指令中的24位立即數指定了用戶請求的服務類型,參數通過通用寄存器傳遞.

 mov   r0,#34    ;設置子功能號位34

   SWI   12     ;調用12號軟中斷

(2) 指令中的24位立即數被忽略,用戶請求的服務類型有寄存器RO的值決定,參數通過其他的通用寄存器傳遞.

 mov  r0,#12         ;調用12號軟中斷

 mov r1,#34         ;設置子功能號位34

 SWI  0

在SWI異常中斷處理程序中,取出SWI立即數的步驟為:首先確定引起軟中斷的SWI指令是ARM指令還是Thunb指令,這可通過對SPSR訪問得到;然后取得該SWI指令的地址,這可通過訪問LR寄存器得到;接著讀出指令,分解出立即數.如如下程序:

T_bit              EQU                    0X20

SWI_Handler

                STMFD      SP!,{R0-R3,R12,LR}                  ;現場保護

               MRS           R0,SPSR                                   ;讀取SPSR

              STMFD       SP!,{R0}                                     :保存SPSR

              TST             R0,#T_bit                       

            LDRNEH        R0,[LR,#-2]                       ;若是Thunb指令,讀取指令碼(16位)

   BICNE             R0,#0XFF00                     :取得Thunb指令的8位立即數

   LDREQ           R0,[LR,#-4]                      ;若是ARM指令,讀取指令碼(32位)

   BICEQ            R0,#0XFF000000           ;取得ARM指令的24位立即數

   ....

   LDMFD          SP!,{R0-R3,R12,PC}^    ;SWI異常中斷返回

    

 

 

ARM匯編的SWI指令軟中斷

從下面的一個ARM 匯編小程序要弄懂的以下三個問題:

1).在ARM狀態轉到THUNB狀態和BX的應用

2).匯編的架構

3).SWI指令的使用

    AREA    ADDREG,CODE,READONLY

     ENTRY

MAIN

      ADR  r0,ThunbProg + 1  ;(為什么要加1呢?因為BX指令跳轉到指定的地址執行程序   時,   若   (BX{cond}  Rm)Rm的位[0]為1,則跳轉時自動將CPSR中的標志T置位即把目標 代碼解釋為 Thunb代碼)

        BX    r0

       CODE16

ThunbProg

       mov r2,#2

      mov r3,#3

     add r2,r2,r3

    ADR r0,ARMProg

    BX  ro

    CODE32

ARMProg

     mov r4,#4

    mov r5,#5

    add  r4,r4,r5

             stop   mov r0,#0x18

              LDR  r1,=0x20026

             SWI   0x123456

             END

SWI--軟中斷指令:

SWI指令用于產生軟中斷,從擁護模式變換到管理模式,CPSR保存到管理模式的SPSR中.

 SWI{cond}      immed_24      ;immed_24為軟中斷號(服務類型)

使用SWI指令時,通常使用以下兩種方法進行傳遞參數,SWI 異常中斷處理程序就可以提供相關的服務,這兩種方法均是用戶軟件協定.SWI異常中斷處理程序要通過讀取引起軟中斷的SWI指令,以取得24位立即數.

(1) 指令中的24位立即數指定了用戶請求的服務類型,參數通過通用寄存器傳遞.

 mov   r0,#34    ;設置子功能號位34

   SWI   12     ;調用12號軟中斷

(2) 指令中的24位立即數被忽略,用戶請求的服務類型有寄存器RO的值決定,參數通過其他的通用寄存器傳遞.

 mov  r0,#12         ;調用12號軟中斷

 mov r1,#34         ;設置子功能號位34

 SWI  0

在SWI異常中斷處理程序中,取出SWI立即數的步驟為:首先確定引起軟中斷的SWI指令是ARM指令還是Thunb指令,這可通過對SPSR訪問得到;然后取得該SWI指令的地址,這可通過訪問LR寄存器得到;接著讀出指令,分解出立即數.如如下程序:

T_bit              EQU                    0X20

SWI_Handler

                STMFD      SP!,{R0-R3,R12,LR}            ;現場保護

               MRS           R0,SPSR                           ;讀取SPSR

              STMFD       SP!,{R0}                            :保存SPSR

              TST             R0,#T_bit                       

            LDRNEH        R0,[LR,#-2]              ;若是Thunb指令,讀取指令碼(16)

   BICNE             R0,#0XFF00                  :取得Thunb指令的8位立即數

   LDREQ           R0,[LR,#-4]                ;若是ARM指令,讀取指令碼(32位)

   BICEQ            R0,#0XFF000000           ;取得ARM指令的24位立即數

   ....

   LDMFD          SP!,{R0-R3,R12,PC}^    ;SWI異常中斷返回

    

Thu Oct 12 2006

 

軟件中斷SWI的實現
在需要軟件中斷處調用

__SWI  0xNum           ;Num為SWI中斷處理模塊的編號,見表SwiFunction

;軟件中斷
SoftwareInterrupt
        CMP     R0, #12                         ;R0中的SWI編號是否大于最大值

/* 下面這句語句把 (LDRLO地址+ 8 + R0*4) 的地址裝載到PC寄存器,舉示例果上面的 Num="1",也就是R0 = 1, 假設LDRLO這條指令的地址是0x00008000,那么根據ARM體系的2級流水線 PC寄存器里指向是下兩條指令 于是PC = 0x00008008  也就是偽指令DCD     TASK_SW 聲明的標號TASK_SW  的地址,注意DCD     TASK_SW 這條指令本身不是ARM能執行的指令,也不會占有地址,這條指令靠匯編器匯編成可執行代碼,它的意義就是聲明 TASK_SW的地址,  , [PC, R0, LSL #2] 這個尋址方式就是 PC + R0的值左移2位的值( 0x01<<2  => 0x04 ),這樣PC的值就是0x0000800C, 即ENTER_CRITICAL的地址于是ARM執行該標號下的任務 */

        LDRLO   PC, [PC, R0, LSL #2]       
        MOVS    PC, LR

SwiFunction
        DCD     TASK_SW                ;0
        DCD     ENTER_CRITICAL         ;1
        DCD     EXIT_CRITICAL            ;2
        DCD     ISRBegin                 ;3
        DCD     ChangeToSYSMode         ;4
        DCD     ChangeToUSRMode         ;5
        DCD     __OSStartHighRdy        ;6
        DCD     TaskIsARM               ;7
        DCD     TaskIsTHUMB             ;8
        DCD     OSISRNeedSwap           ;9
        DCD     GetOSFunctionAddr       ;10
        DCD     GetUsrFunctionAddr      ;11

TASK_SW
        MRS     R3, SPSR                        ;保存任務的CPSR
        MOV     R2, LR                          ;保存任務的PC
       
        MSR     CPSR_c, #(NoInt | SYS32Mode)    ;切換到系統模式
        STMFD   SP!, {R2}                       ;保存PC到堆棧
        STMFD   SP!, {R0-R12, LR}               ;保存R0-R12,LR到堆棧
                                                ;因為R0~R3沒有保存有用數據,所以可以這樣做
        B       OSIntCtxSw_0                    ;真正進行任務切換

ENTER_CRITICAL
                                                ;OsEnterSum++
        LDR     R1, =OsEnterSum
        LDRB    R2, [R1]
        ADD     R2, R2, #1
        STRB    R2, [R1]
                                                ;關中斷
        MRS     R0, SPSR
        ORR     R0, R0, #NoInt
        MSR     SPSR_c, R0
        MOVS    PC, LR

 

 

 

批量數據加載/存儲指令實驗     2007-08-22 12:08:06
大 中 小
標簽:arm指令 ldm/stm
這個程序用批量傳輸指令傳輸數據,一次可傳8個字:

        AREA Block, CODE, READONLY      ; name this block of code

num     EQU     20              ; Set number of words to be copied

        ENTRY                   ; mark the first instruction to call

start
        LDR     r0, =src        ; r0 = pointer to source block
        LDR     r1, =dst        ; r1 = pointer to destination block
        MOV     r2, #num        ; r2 = number of words to copy

        MOV     sp, #0x400      ; set up stack pointer (r13)
blockcopy     
        MOVS    r3,r2, LSR #3   ; number of eight word multiples
        BEQ     copywords               ; less than eight words to move ?

        STMFD   sp!, {r4-r11}   ; save some working registers
octcopy
        LDMIA   r0!, {r4-r11}   ; load 8 words from the source
        STMIA   r1!, {r4-r11}   ; and put them at the destination
        SUBS    r3, r3, #1              ; decrement the counter
        BNE     octcopy         ; ... copy more

        LDMFD   sp!, {r4-r11}   ; dont need these now - restore originals

copywords
        ANDS    r2, r2, #7              ; number of odd words to copy
        BEQ     stop                    ; No words left to copy ?
wordcopy
        LDR     r3, [r0], #4    ; a word from the source
        STR     r3, [r1], #4    ; store a word to the destination
        SUBS    r2, r2, #1              ; decrement the counter
        BNE     wordcopy                ; ... copy more

stop
        MOV     r0, #0x18               ; angel_SWIreason_ReportException
        LDR     r1, =0x20026    ; ADP_Stopped_ApplicationExit
        SWI     0x123456                ; ARM semihosting SWI

 

下面的這個程序實現同樣的功能,每次只能傳一個字:

        AREA BlockData, DATA, READWRITE

src     DCD     1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4
dst     DCD     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

        END

 

        AREA Word, CODE, READONLY       ; name this block of code

num     EQU     20              ; Set number of words to be copied

        ENTRY                   ; mark the first instruction to call

start
        LDR     r0, =src        ; r0 = pointer to source block
        LDR     r1, =dst        ; r1 = pointer to destination block
        MOV     r2, #num        ; r2 = number of words to copy
      
wordcopy
        LDR     r3, [r0], #4    ; a word from the source
        STR     r3, [r1], #4    ; store a word to the destination
        SUBS    r2, r2, #1      ; decrement the counter
        BNE     wordcopy        ; ... copy more

stop
        MOV     r0, #0x18       ; angel_SWIreason_ReportException
        LDR     r1, =0x20026    ; ADP_Stopped_ApplicationExit
        SWI     0x123456        ; ARM semihosting SWI

        AREA BlockData, DATA, READWRITE

src     DCD     1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4
dst     DCD     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

        END

 

 

當處理器工作在ARM狀態時,幾乎所有的指令均根據CPSR中條件碼的狀態和指令的條件域有條件的執行。當指令的執行條件滿足時,指令被執行,否則指令被忽略。
每一條ARM指令包含4位的條件碼,位于指令的最高4位[31:28]。條件碼共有16種,每種條件碼可用兩個字符表示,這兩個字符可以添加在指令助記符的后面和指令同時使用。示例,跳轉指令B可以加上后綴EQ變為BEQ表示“相等則跳轉”,即當CPSR中的Z標志置位時發生跳轉。

1、 B指令
B指令的格式為:
B{條件} 目標地址
B指令是最簡單的跳轉指令。一旦遇到一個 B 指令,ARM 處理器將立即跳轉到給定的目標地址,從那里繼續執行。注意存儲在跳轉指令中的實際值是相對當前PC值的一個偏移量,而不是一個絕對地址,它的值由匯編器來計算(參考尋址方式中的相對尋址)。它是 24 位有符號數,左移兩位后有符號擴展為 32 位,表示的有效偏移為 26 位(前后32MB的地址空間)。以下指令:
B Label ;程序無條件跳轉到標號Label處執行
CMP R1,#0 ;當CPSR寄存器中的Z條件碼置位時,程序跳轉到標號Label處執行
BEQ Label

3.3.6 批量數據加載/存儲指令
ARM微處理器所支持批量數據加載/存儲指令可以一次在一片連續的存儲器單元和多個寄存器之間傳送數據,批量加載指令用于將一片連續的存儲器中的數據傳送到多個寄存器,批量數據存儲指令則完成相反的操作。常用的加載存儲指令如下:
— LDM 批量數據加載指令
— STM 批量數據存儲指令
LDM(或STM)指令
LDM(或STM)指令的格式為:
LDM(或STM){條件}{類型} 基址寄存器{!},寄存器列表{∧}
LDM(或STM)指令用于從由基址寄存器所指示的一片連續存儲器到寄存器列表所指示的多個寄存器之間傳送數據,該指令的常見用途是將多個寄存器的內容入棧或出棧。其中,{類型}為以下幾種情況:
IA 每次傳送后地址加1;
IB 每次傳送前地址加1;
DA 每次傳送后地址減1;
DB 每次傳送前地址減1;
FD 滿遞減堆棧;
ED 空遞減堆棧;
FA 滿遞增堆棧;
EA 空遞增堆棧;
{!}為可選后綴,若選用該后綴,則當數據傳送完畢之后,將最后的地址寫入基址寄存器,否則基址寄存器的內容不改變。
基址寄存器不允許為R15,寄存器列表可以為R0~R15的任意組合。
{∧}為可選后綴,當指令為LDM且寄存器列表中包含R15,選用該后綴時表示:除了正常的數據傳送之外,還將SPSR復制到CPSR。同時,該后綴還表示傳入或傳出的是用戶模式下的寄存器,而不是當前模式下的寄存器。
指令示例:
STMFD R13!,{R0,R4-R12,LR} ;將寄存器列表中的寄存器(R0,R4到R12,LR)存入堆棧。
LDMFD R13!,{R0,R4-R12,PC} ;將堆棧內容恢復到寄存器(R0,R4到R12,LR)。

 

 

 

 

 

ARM匯編的SWI指令軟中斷 [轉貼 2007-05-25 11:21:49]  
 
從下面的一個ARM 匯編小程序要弄懂的以下三個問題:

1).在ARM狀態轉到THUNB狀態和BX的應用

2).匯編的架構

3).SWI指令的使用

    AREA ADDREG,CODE,READONLY

    ENTRY

MAIN

                ADR r0,ThunbProg 1 ;(為什么要加1呢?因為BX指令跳轉到指定的地址執行程序 時, 若   (BX{cond} Rm)Rm的位[0]為1,則跳轉時自動將CPSR中的標志T置位即把目標 代碼解釋為 Thunb代碼)

                BX r0

                CODE16

ThunbProg

                mov r2,#2

    mov r3,#3

    add r2,r2,r3

    ADR r0,ARMProg

    BX ro

    CODE32

ARMProg

    mov r4,#4

    mov r5,#5

    add r4,r4,r5

stop         mov r0,#0x18

                LDR r1,=0x20026
   
                SWI 0x123456

END

 SWI--軟中斷指令:

 SWI指令用于產生軟中斷,從擁護模式變換到管理模式,CPSR保存到管理模式的SPSR中.

 SWI{cond} immed_24 ;immed_24為軟中斷號(服務類型)

使用SWI指令時,通常使用以下兩種方法進行傳遞參數,SWI 異常中斷處理程序就可以提供相關的服務,這兩種方法均是用戶軟件協定.SWI異常中斷處理程序要通過讀取引起軟中斷的SWI指令,以取得24位立即數.

(1) 指令中的24位立即數指定了用戶請求的服務類型,參數通過通用寄存器傳遞.

        mov r0,#34 ;設置子功能號位34

        SWI 12 ;調用12號軟中斷

(2) 指令中的24位立即數被忽略,用戶請求的服務類型有寄存器R0的值決定,參數通過其他的通用寄存器傳遞.

        mov r0,#12 ;調用12號軟中斷

        mov r1,#34 ;設置子功能號位34

        SWI  0

在SWI異常中斷處理程序中,取出SWI立即數的步驟為:首先確定引起軟中斷的SWI指令是ARM指令還是Thunb指令,這可通過對SPSR訪問得到;然后取得該SWI指令的地址,這可通過訪問LR寄存器得到;接著讀出指令,分解出立即數.如如下程序:

           T_bit EQU 0X20
  
            SWI_Handler
  
            STMFD SP!,{R0-R3,R12,LR} ;現場保護
  
            MRS R0,SPSR ;讀取SPSR
  
            STMFD SP!,{R0} :保存SPSR
  
            TST R0,#T_bit
  
            LDRNEH R0,[LR,#-2] ;若是Thunb指令,讀取指令碼(16位)

   BICNE R0,#0XFF00 :取得Thunb指令的8位立即數

   LDREQ R0,[LR,#-4] ;若是ARM指令,讀取指令碼(32位)

   BICEQ R0,#0XFF000000 ;取得ARM指令的24位立即數

   ....

   LDMFD SP!,{R0-R3,R12,PC}^ ;SWI異常中斷返回

 

 

 

基于s3c2410軟中斷服務的uC/OS-II任務切換
 
 
 
1.關于軟中斷指令
  軟件中斷指令(SWI)可以產生一個軟件中斷異常,這為應用程序調用系統例程提供了一種機制。
語法:
       SWI   {<cond>}  SWI_number
SWI執行后的寄存器變化:
lr_svc = SWI指令后面的指令地址
spsr_svc = cpsr
pc = vectors + 0x08
cpsr模式 = SVC
cpsr I = 1(屏蔽IRQ中斷)
 
   處理器執行SWI指令時,設置程序計數器pc為向量表的0x08偏移處,同事強制切換處理器模式到SVC模式,以便操作系統例程可以在特權模式下被調用。
   每個SWI指令有一個關聯的SWI號(number),用于表示一個特定的功能調用或特性。
【例子】 一個ARM工具箱中用于調試SWI的例子,是一個SWI號為0x123456的SWI調用。通常SWI指令是在用戶模式下執行的。
SWI執行前:
    cpsr = nzcVqift_USER
    pc = 0x00008000
    lr = 0x003fffff   ;lr = 4
    r0 = 0x12
 
執行指令:
    0x00008000   SWI    0x123456
 
SWI執行后:
    cpsr = nzcVqIft_SVC
    spsr = nzcVqift_USER
    pc = 0x00000008
    lr = 0x00008004
    r0 = 0x12
   SWI用于調用操作系統的例程,通常需要傳遞一些參數,這可以通過寄存器來完成。在上面的例子中,r0
用于傳遞參數0x12,返回值也通過寄存器來傳遞。
   處理軟件中斷調用的代碼段稱為中斷處理程序(SWI Handler)。中斷處理程序通過執行指令的地址獲取軟件中斷號,指令地址是從lr計算出來的。
   SWI號由下式決定:
   SWI_number = <SWI instruction> AND NOT<0xff000000>
   其中SWI instruction就是實際處理器執行的32位SWI指令
 
   SWI指令編碼為:
   31 - 28  27 - 24  23 - 0
     cond   1 1 1 1  immed24
   指令的二進制代碼的bit23-bit0是24bit的立即數,即SWI指令的中斷號,通過屏蔽高8bit即可獲得中斷號。lr寄存器保存的是中斷返回指令的地址,所以 [lr - 4] 就是執行SWI的執行代碼。通過load指令拷貝整個SWI指令到寄存器,使用BIC屏蔽指令的高8位,獲取SWI中斷號。
  
    ;read the SWI instruction
    LDR  r10, [lr, #-4]
    BIC  r10, r10, #0xff000000
 
2. 周立功移植uC/OS-II到s3c2410的軟中斷服務級的任務切換
uC/OS-II的任務調度函數
   uC/OS-II的任務級的調度是由函數OS_Sched( )完成的。
 
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
    OS_CPU_SR cpu_sr;
#endif
    INT8U y;

    OS_ENTER_CRITICAL();
    if ((OSIntNesting == 0) && (OSLockNesting == 0)) { /* Sched. only if all ISRs done & not locked */
        y = OSUnMapTbl[OSRdyGrp]; /* Get pointer to HPT ready to run */
        OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
        if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
            OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
            OSCtxSwCtr++; /* Increment context switch counter */
            OS_TASK_SW(); /* Perform a context switch */
        }
    }
    OS_EXIT_CRITICAL();
}
 

   詳細解釋可以參考《嵌入式實時操作系統 uC/OS-II》,os_sched函數在確定所有就緒任務的最高優先級高于當前任務優先級時進行任務切換,通過OS_TASK_SW( )宏來調用。
   OS_TASK_SW( )宏實際上定義的是SWI軟中斷指令。見OS_CPU.H文件的代碼:

__swi(0x00) void OS_TASK_SW(void); /* 任務級任務切換函數 */
__swi(0x01) void _OSStartHighRdy(void); /* 運行優先級最高的任務 */
__swi(0x02) void OS_ENTER_CRITICAL(void); /* 關中斷 */
__swi(0x03) void OS_EXIT_CRITICAL(void); /* 開中斷 */

__swi(0x40) void *GetOSFunctionAddr(int Index); /* 獲取系統服務函數入口 */
__swi(0x41) void *GetUsrFunctionAddr(int Index);/* 獲取自定義服務函數入口 */
__swi(0x42) void OSISRBegin(void); /* 中斷開始處理 */
__swi(0x43) int OSISRNeedSwap(void); /* 判斷中斷是否需要切換 */

__swi(0x80) void ChangeToSYSMode(void); /* 任務切換到系統模式 */
__swi(0x81) void ChangeToUSRMode(void); /* 任務切換到用戶模式 */
__swi(0x82) void TaskIsARM(INT8U prio); /* 任務代碼是ARM代碼 */
__swi(0x83) void TaskIsTHUMB(INT8U prio); /* 任務代碼是THUMB */
 

__swi(0x00) void OS_TASK_SW(void); 是與ADS相關的代碼,通過反匯編可以看到,調用OS_TASK_SW實際上被替換成swi 0x00 軟中斷指令。執行此執行,pc會跳轉到向量表的0x08偏移處。

中斷向量表:(見Startup.s文件)

CODE32
        AREA vectors,CODE,READONLY
; 異常向量表
Reset
        LDR PC, ResetAddr
        LDR PC, UndefinedAddr
        LDR PC, SWI_Addr
        LDR PC, PrefetchAddr
        LDR PC, DataAbortAddr
        DCD IRQ_Addr
        LDR PC, IRQ_Addr
        LDR PC, FIQ_Addr

ResetAddr     DCD ResetInit
UndefinedAddr DCD Undefined
SWI_Addr      DCD SoftwareInterrupt
PrefetchAddr  DCD PrefetchAbort
DataAbortAddr DCD DataAbort
Nouse         DCD 0
IRQ_Addr      DCD IRQ_Handler
FIQ_Addr      DCD FIQ_Handler
 

執行SWI 0x00指令后,pc會跳轉到SoftwareInterrupt代碼處開始執行:

見Os_cpu_a.s文件的SoftwareInterrupt函數:

 

SoftwareInterrupt
        LDR SP, StackSvc ; 重新設置堆棧指針
        STMFD {R0-R3, R12, LR}
        MOV R1, SP ; R1指向參數存儲位置

        MRS R3, SPSR
        TST R3, #T_bit ; 中斷前是否是Thumb狀態
        LDRNEH R0, [LR,#-2] ; 是: 取得Thumb狀態SWI指令
        BICNE R0, R0, #0xff00
        LDREQ R0, [LR,#-4] ; 否: 取得arm狀態SWI指令
        BICEQ R0, R0, #0xFF000000    ; 如上面所述,此處通過屏蔽SWI指令的高8位來獲取SWI號,r0 = SWI號,R1指向參數存儲位置
        CMP R0, #1
        LDRLO PC, =OSIntCtxSw  ;為0時跳轉到OSIntCtxSwdi地址處
        LDREQ PC, =__OSStartHighRdy ; 為1時,跳轉到__OSStartHighRdy地址處。SWI 0x01為第一次任務切換

        BL SWI_Exception  ;進入中斷號散轉函數
       
        LDMFD {R0-R3, R12, PC}^
       
StackSvc DCD (SvcStackSpace + SVC_STACK_LEGTH * 4 - 4)

 

以上就是任務切換軟中斷級服務的實現。
 
 

 

 

利用arm 組語的PRE-INDEX 與POST-INDEX ADDRESSING,上課時CODING完成的範例--1+2+3+...+10之和 分類:IT技術分享2008/01/30 17:17於今日 97/01/30 上課時實作的程式範例,先貼上程式碼。

因是上課當場coding完成的,so沒有加上註解^^

範例一:使用 POST-INDEX ADDRESSING實作的code
=======================================
    AREA  ASM6,CODE,READONLY

    ENTRY
START
    LDR R0,=ARR1
    MOV R1,#0
LOOP
    LDR R2,[R0],#4
    ADD R1,R1,R2
    CMP R2,#0
    BNE LOOP

STOP
    LDR R0,=0X18
    LDR R1,=0X20026
    SWI 0X123456

    AREA  ARR,DATA,READWRITE
ARR1  DCD   1,2,3,4,5,6,7,8,9,10,0
    END

範例二:使用 PRE-INDEX ADDRESSING實作的code
=======================================
AREA    ASM8,CODE,READONLY

    ENTRY
START
    LDR R0,=ARR1
    MOV R1,#0
    MOV R3,#0
LOOP
    LDR R2,[R0,R1,LSL #2]
    ADD R1,R1,#1
    ADD R3,R3,R2
    CMP R2,#0
    BNE LOOP

STOP
    LDR R0,=0X18
    LDR R1,=0X20026
    SWI 0X123456

    AREA  ARR,DATA,READWRITE
ARR1  DCD   1,2,3,4,5,6,7,8,9,10,0
    END

 

http://www.akaedu.org/bbs/redirect.php?tid=231&goto=lastpost

 

常用ARM指令
1、        內存訪問指令
基本指令:
LDR:memory -> register (memory包括映射到內存空間的非通用寄存器)
STR:register  -> memory
語法:
        op{cond }{B}{T}                Rd ,                [Rn ]
op{cond }{B}                        Rd ,                [Rn ,                FlexOffset ]{!}
op{cond }{B}                        Rd ,                label
op{cond }{B}{T}                Rd ,                [Rn ],        FlexOffset
op:基本指令,如LDR、STR
cond:條件執行后綴
B:字節操作后綴
T:用戶指令后綴
Rd:源寄存器,對于LDR指令,Rd將保存從memory中讀取的數值;對于STR指令,Rd保存著將寫入memory的數值
Rn:指針寄存器
FlexOffset:偏移量
例子:
ldr                r0,                [r1]                                        ;r1作為指針,該指針指向的數存入r0
str                r0,                [r1,                #4]                ;r1+4作為指針,r0的值存入該地址
        str                r0,                [r1,                #4]!                ;同上,并且r1 = r1 + 4
        ldr                r1,                =0x08100000                ;立即數0x08100000存到r1
        ldr                r1,                [r2],                #4                        ;r2+4作為指針,指向的值存入r1,并且r2=r2+4
【label的使用】
addr1                                                        ;定義一個名為“addr1”的label,addr1 = 當前地址
        dcd                0                                        ;在當前地址出定義一個32bit的變量
~ ~ ~
        ldr                r1,                label1    ;r1 = addr1,r1即可以作為var1的指針
        ldr                r0,                [r1]
        add        r0,                r0,                #1
        str                r0,                [r1]                        ;變量var1的值加1
【FlexOffset的使用】
        FlexOffset可以是立即數,也可以是寄存器,還可以是簡單的表達式
       
2、        多字節存取指令(常用于堆棧操作)
基本指令:
        LDM:memory ――> 多個寄存器
        STM:多個寄存器 ――> memory
語法:
        op{cond }mode        Rn{!},                reglist {^}
        mode:指針更新模式,對應于不同類型的棧。最常用的是“FD”模式,相當于初始棧指針在高位,壓棧后指針值減小。
        Rn:指針寄存器
        !:最后的指針值將寫入Rn中
        reglist:要操作的寄存器列表,如{r0-r8, r10}
        ^ :完成存取操作后從異常模式下返回
例子:
;異常處理程序:
        sub                lr,                lr,                #4                        ; lr – 4是異常處理完后應該返回的地方
;保存r0~r12和lr寄存器的值到堆棧并更新堆棧指針。
        stmfd                sp!,                {r0-r12, lr}       
;異常處理
                ldmfd        sp!,                {r0-r12, pc}^         ;從堆棧中恢復r0~r12,返回地址賦給pc指針,使程序返回到異常發生前所執行的地方,^標記用來使CPU退出異常模式,進入普通狀態。
                                                                                       

3、        算術運算指令
基本指令:
        ADD:加
        SUB:減
語法:
        op{cond }{S}                Rd,                Rn,                Operand2
        S:是否設置狀態寄存器(CPSR),如:N(有符號運算結果得負數)、Z(結果得0)、C(運算的進位或移位)、V(有符號數的溢出)等等。
        Rd:保存結果的寄存器
        Rn:運算的第一個操作數
        Operand2:運算的第二個操作數,這個操作數的值有一些限定:如可以是8位立即數(例:0xa8)或一個8為立即數的移位(例:0xa800,而0xa801就不符合)。也可以是寄存器,或寄存器的移位(如“r2,  lsl  #4”)。
例子:
        add                r0,                r1,                r2                                        ; r0 = r1 + r2
        adds                r0,                r1,                #0x80                                ; r0 = r1 + 0x80,并設置狀態寄存器
        subs                r0,                r1,                #2000                                ; r0 = r1 – 2000,并設置狀態寄存器

4、        邏輯運算指令
基本指令:
        AND:與
        ORR:或
        EOR:異或
        BIC:位清0
語法:
        op{cond }{S}                Rd,                Rn,                Operand2
        語法類似算術運算指令
例子:
        ands                r0,        r1,        #0xff00                        ; r0 = r1 and 0xff00,并設置狀態寄存器
        orr                r0,                r1,                r2                                        ; r0 = r1 and r2
        bics                r0,                r1,                #0xff00                        ; r0 = r1 and ! (0xff00)
        ands                r0,                r1,                #0xffff00ff                ; 錯誤

5、        MOV指令
語法:
MOV{cond}{S}        Rd,                Operand2
例子:
        mov                r0,                #8                                                ; r0 = 8
        mov                r0,                r1                                                ; r0 = r1
不同于LDR、STR指令,該指令可以寄存器間賦值

6、        比較指令
基本指令:
        CMP:比較兩個操作數,并設置狀態寄存器
語法:
        CMP{cond }                Rn,                Operand2
例子:
        cmp                r0,                r1                                                ; 計算r0 – r1,并設置狀態寄存器,由狀態寄存器可以知r0是否大于、小于或等于r1
        cmp                r0,                #0                                                ;

7、        跳轉指令
基本指令:
        B:跳轉
        BL:跳轉并將下一指令的地址存入lr寄存器
語法:
        op{cond}                label
        label:要跳向的地址
例子:
loop1
        ~ ~ ~
        b                loop1                                ; 跳到地址loop1處
        bl                sub1                                ; 將下一指令地址寫入lr,并跳至sub1
        ~ ~ ~
sub1
        ~ ~ ~
        mov        pc,        lr                        ; 從sub1中返回
【使用本地label(local label)】
        本地label可以在一個程序段內多次使用,用數字作為label的名稱,也可以在數字后面跟一些字母。引用本地label的語法是:        %{F|B}{A|T}n{routname},其中F代表向前搜索本地label,B代表向后搜索,A/T不常使用。
例子
100        ; 定義本地label,名稱為“100”
~ ~ ~
100                                                                                ; 第二次定義本地label,名稱為“100”
        ~ ~ ~
        b                %f100                                                ; 向前跳到最近的“100”處
~ ~ ~
        b                %b100                                                ; 向后跳至最近的“100”處
100                                                                                ; 第三次定義本地label 100

8、        條件執行
條件:狀態寄存器中某一或某幾個比特的值代表條件,對應不同的條件后綴cond,如:
后綴 (cond)         狀態寄存器中的標記         意義
EQ                                                 Z = 1                                                                         相等
NE                                                 Z = 0                                                                        不相等
GE                                                 N和V相同                                                 >=
LT                                                 N和V不同                                                        <
GT                                                 Z        = 0, 且N和V相同                        >
LE                                                Z = 1, 或N和V不同                 <=
例子:
                cmp                r0,                r1                ;比較r0和r1
                blgt                sub1                        ;如果r0>r1,跳轉到sub1,否則不操作
;――――――――――――――――――――
                ;一段循環代碼
                ldr                        r2,                =8                ;r2 = 8
        loop
                ;這里可以進行一些循環內的操作
                subs                r2,                r2,                #1                ;r2 = r2 –1,并設置狀態位
                bne                loop                                        ;如果r2不等于0,則繼續循環
;――――――――――――――――――――
                mov                r0,                #1                                ; r0 = 1
                cmp                r2,                #8                                ;        比較r2和8
                movlt        r0,                #2                                ; 如果r2<8,r0 = 2
       
ARM匯編程序結構
;――――――――――――――――――――
AREA  EX2,  CODE,  READONLY
;AREA指令定義一個程序段,名稱為EX2,屬性為:CODE、READONLY
        INCLUDE         Common.inc        ;包含匯編頭文件
        IMPORT         sub1                                        ;引用外部符號
        EXPORT                prog1                        ;向外輸出符號
ENTRY                                                         ;ENTRY指令定義程序的開始
start                                                                                ;此處定義了一個label start
MOV         r0,        #10                                       
MOV         r1,        #3
ADD         r0,        r0,        r1                                 ;r0 =r0 +r1
prog1                                                                        ;此處定義了一個label prog1
MOV         r0,        #0x18                                
LDR         r1,        =0x20026                        
SWI         0x123456                                
END                                                                 ;END指令表示程序段的結束
;――――――――――――――――――――
宏的使用
定義宏:
MACRO                                                ;宏的起始
{label}        macroname        para1,para2……
;代碼
MEND                                                        ;宏結束
引用宏:
                marconame         para1,para2……
例子
;定義一個宏,完成兩個寄存器內容交換
                MACRO
                swap                $w1,                $w2,                $w3
                        mov                $w3,                $w1
                        mov                $w1,                $w2
                        mov                $w2,                $w3
                MEND

;使用這個宏
ldr                        r0,                =1
ldr                        r1,                =2
swap                r0,                r1,                r2                ;此處調用了宏swap,運行完后r0、r1的值交換了
一般可以把宏寫在宏文件(.mac文件)中,在程序里用INCLUDE指令包含宏文件

 

 

 

 

 
最超值的ARM7/ARM9開發板系列
AVR單片機開發板與仿真器
 
本章節主要介紹ARM 處理器的基本程序設計方法,包含ARM 指令實驗,Thumb 指令實驗和ARM 處理器工作模式實驗。

4.1 ARM 指令實驗
4.1.1 實驗說明
  實驗目的:    透過實驗掌握ARM 組譯指令的使用方法。

         實驗設備: 硬件使用PC 主機,軟件使用Embest IDE 2003 整合開發環境,Windows 98/2000/NT/XP。

         實驗內容:    使用簡單ARM 組譯指令,操作寄存器和內存區作互相的數據交換。

4.1.2 實驗原理
ARM 處理器共有37個寄存器:31個通用寄存器,包括程序計數器(PC)。這些寄存器都是32 位的。6個狀態寄存器。這些寄存器也是32 位的,但是只是使用了其中的12 位。

ARM 通用寄存器

通用寄存器(R0~R15)可分為3 類:

不分組寄存器R0~R7;

分組寄存器R8~R14;

程序計數器R15。

 

1)不分組寄存器R0~R7

R0~R7 是不分組寄存器。這意味著在所有處理器模式下,它們都存取一樣的32 位寄存器。它們是真正的通用寄存器,沒有架構所隱含的特殊用途。

 

2)分組寄存器R8~R14

R8~R14 是分組寄存器。它們存取的物理寄存器取決于當前的處理器模式。若要存取特定的物理寄存器而不依賴當前的處理器模式,則要使用規定的各字。

寄存器R8~R12 各有兩組物理寄存器:一組為FIQ 模式,另一組為除了FIQ以外的所有模式。寄存器R8~R12 沒有任何指定的特殊用途。只是使用R8~R14來簡單地處理中斷。寄存器R13,R14 各有6 個分組的物理寄存器。1 個用于用戶模式和系統模式,其它5 個分別用于5 種異常模式。寄存器R13 通常用做堆迭指標,稱為SP。每種異常模式都有自己的R13。寄存器R14 用作子程序鏈接寄存器,也稱為LR。

 

3) 程序計數器R15

寄存器R15 用做程序計數器(PC)。程序狀態寄存器在所有處理器模式下都可以存取當前的程序狀態寄存器CPSR。CPSR 包含條件碼標志位,中斷禁止位,當前處理器模式以及其它狀態和控制信息。每種異常模式都有一個程序狀態保存寄存器SPSR。當例外出現時,SPSR 用于保留CPSR的狀態。

CPSR 和SPSR 的格式如下:

31
 30
 29
 28
 27
 26         8
 7
 6
 5
 4
 3
 2
 1
 0
 
N
 Z
 C
 V
 Q
 DNM(RAZ)
 I
 F
 T
 M
 M
 M
 M
 M
 

 

條件碼標志:N,Z,C,V 大多數指令可以測試這些條件碼標志以決定程序指令如何執行

控制位:最低8 位I,F,T 和M 位用做控制位。當異常出現時改變控制位。當處理器在特權模式下也可以由軟件改變。

中斷禁止位:I 置1 則禁止IRQ 中斷。F 置1 則禁止FIQ 中斷。T 位:T=0 指示ARM 執行。T=1 指示Thumb 執行。在這些架構系統中,可自由地使用能在ARM 和Thumb 狀態之間切換的指令。

模式位:M0, M1, M2, M3 和M4 (M[4:0]) 是模式位.這些位

決定處理器的工作模式.如表2-1 所示。

表4-1 ARM 工作模式M[4:0]

M[4:0]
 模式
 可存取的寄存器
 
0b10000
 用戶模式
 PC, R14~R0,CPSR
 
0b10001
 FIQ模式
 PC, R14_fiq~R8_fiq,R7~R0,CPSR,SPSR_fiq
 
0b10010
 IRQ模式
 PC, R14_irq~R8_fiq,R12~R0,CPSR,SPSR_irq
 
0b10011
 管理模式
 PC, R14_svc~R8_svc,R12~R0,CPSR,SPSR_svc
 
0b10111
 中止
 PC, R14_abt~R8_abt,R12~R0,CPSR,SPSR_abt
 
0b11011
 未定義
 PC, R14_und~R8_und,R12~R0,CPSR,SPSR_und
 
0b11111
 系統
 PC, R14~R0,CPSR
 

其它位程序狀態寄存器的其它位保留,用作以后的擴充。

 

4.1.3. 實驗操作步驟
1. 執行ADS1.2開發環境,打開實驗系統例程目錄下ARMcode_test 子目錄下的ARMcode.mcp 工程文件。

2. 透過操作菜單欄或使用快捷命令編譯鏈接項目。

3. 選擇Debug 菜單Remote Connect 進行連接軟件仿真器,執行Download命令下載程序,并打開寄存器窗口。

4. 單步執行程序并觀察和記錄寄存器R0-R15 的值變化。

5. 結合實驗內容和相關數據,觀察程序執行,透過實驗加深理解ARM指令的使用。

 

 

 

4.1.4  試驗程序代碼
;本程序將數據區從數據區SRC復制到目標數據區DST。復制時,以8個字節為單位進行。對于

;最后所剩不足的8個字節的數據,以字為單位進行復制,這時程序跳轉到copywords處執行。

;在進行以8個字為單位的數據復制時,保存了所有的8個工作寄存器。

 

;設置本段程序的名稱(Block)及屬性

 AREA Block,CODE,READONLY

 

                    

;設置將要復制的字數(定義變量num,并賦值為20)

num EQU 20                

 

;程序入口標志

       ENTRY

start

 

;r0寄存器指向源數據區(SRC 標識的地址放入R0)

       LDR       r0,=src   

 

;r1寄存器指向目標數據區(DST 標識的地址放入R1)      

       LDR       r1,=dst   

      

;r2指定將要復制的字數(裝載num 的值到R2)  

       MOV      r2, #num

             

;設置數據棧指針(R13),用于保存工作寄存器數值(設定SP堆棧開始地址為0x400)

       MOV      sp, #0x400

 

             

;進行以8個字節為單位的數據復制

blockcopy

 

;需要進行的以8個字為單位的復制次數( R2 右移3 位后的值放入R3)

       MOVS r3,r2, LSR #3    

;對于剩下的不足8個字的數據,跳轉到copywords,以字為單位復制(判斷是否為0,為0 跳移)

      

       BEQ copywords                  

;保存工作寄存器(把R4 到R11 的值保存到SP 標識的堆棧中)

       STMFD sp!, {r4-r11}         

 

octcopy

;從數據區讀取8個字節的數據,放到8個寄存器中,并更新目標數據區指針r0(把R0 中的地址標識的內容順序裝載到R4 到R11 中)

 

       LDMIA r0!, {r4-r11}          

;將這8個字數據寫入到目標數據區,并更新目標數據區指針r1(把R4 到R11 的值順序保存到以R1 起始地址的內存中)

       STMIA r1!, {r4-r11}           

 

;將塊的復制次數減1 (R3 -1 計數)

       SUBS r3, r3, #1                  

 

;循環,直到完成以8個字為單位的塊復制

       BNE octcopy                      

 

;需要注意的是,LDMIA 或者STMIA 指令執行后,R0,R1 的值產生變化,每一次寄存器操作,R0 或者R1 的值會自動增加一個字節的量,這里操作了8 個寄存器,R0 或者R1 的值也相應增加了8 個字節

 

 

;恢復工作寄存器值(把剛才保存的SP 堆棧中的值恢復到R4 到R11 中)

       LDMFD sp!, {r4-r11}  

       

copywords

;剩下不足8個字的數據的字數(邏輯與,把R2 前7 位扔掉)

       ANDS r2, r2, #7          

 

;數據復制完成(判斷是否為0,為0 跳移)

       BEQ stop                           

 

wordcopy

;從源數據區讀取18個字的數據,放到r3寄存器中,并更新目標數據區指針r0(把R0 表示地址的內容的后4 位全部拷貝到R3)

       LDR r3, [r0], #4   

 

;將這r3中數據寫入到目標數據區中,并更新目標數據區指針r1 (把R3 的內容,放入以R1 為起始地址的4 位內存中)      

       STR r3, [r1], #4           

      

;將字數減1;(R2 -1 放回R2)

       SUBS r2, r2, #1           

 

;循環,直到完成以字為單位的數據復制(判斷是否為0,不為0 跳移,同樣的,這里R0,R1 操作后,R0,R1 會自動加上便宜量)   

       BNE wordcopy                  

                                         

stop

;從應用程序中退出

       MOV      r0,   #0x18

       LDR       r1,   =0x20026

       SWI 0x123456

;定義數據區bloackdata

 

       AREA     Bloackdata,     DATA,    READWRITE

;定義源數據區src及目標數據區dst

src   DCD       1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4

dst   DCD       0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

 

;結束匯編

       END

4.1.5 實驗練習題
1. 撰寫程序循環對R4~R11 進行8 次累加,R4~R11 起始值為1~8,每次加操作后把R4~R11 的內容放入SP 堆棧中,SP 初始設定為0x800。最后把R4~R11 用LDMFD 指令清空值為0。

2. 對于每一種ARM 的尋址方法,簡短的寫出相對的應用程序片段。

4.2 ARM 處理器工作模式實驗
4.2.1. 實驗說明
         實驗目的:透過實驗掌握ARM 處理器工作模式的切換。

         實驗設備:軟件需要ADS1.2開發環境,Windows 98/2000/NT/XP。

         實驗內容:透過ARM 組譯指令,在各種處理器模式下切換并觀察各種模式下緩存器的區別;掌握ARM 不同模式的進入與退出。

4.2.2. 實驗原理
ARM 處理器模式

ARM 架構支持下表2-2 所列的7 種處理器模式。

工作模式
 描述
 
用戶模式(User,usr)
 正常程序執行的模式
 
快速中斷模式(FIQ,fig)
 用于高速數據傳輸和數據處理
 
外部中斷模式(IRQ,irq)
 用于通常的中斷處理
 
特權模式(管理模式)(Supervisor,sve)
 共操作系統使用的一種模式
 
數據訪問中止模式(Abort,abt)
 用于虛擬存儲及存儲保護
 
未定義指令中斷模式(Undefind,und)
 用于支持通過軟件方針的協處理器
 
系統模式(System,sys)
 用于運行特權的操作系統任務
 

 

 

大多數應用程序在用戶模式下執行。當處理器工作在用戶模式時,正在執行的程序不能存取某些被保護的系統資源,也不能改變模式,除非例外(exception)發生。這允許適當撰寫操作系統來控制系統資源的使用。

除用戶模式外的其它模式稱未特權模式。它們可以自由的存取系統資源和改變模式。其中的5 種稱為異常模式,即

n         FIQ(Fash Interrupt request);

n         IRQ(Interrupt ReQuest);

n         管理(Supervisor);

n         中止(Abort) ;

n         未定義(Underfined) 。

當特定的異常出現時,進入相應的模式。每種模式都有某些附加的寄存器,以避免異常出現時用戶模式的狀態不可靠。

剩下的模式是系統模式。僅ARM 架構V4 以及以上的版本有該模式。不能由于任何異常而進入該模式。它與用戶模式有完全相同的寄存器。然而它是特權模式,不受用戶模式的限制。它供需要存取系統資源的操作系統工作使用,單希望避免使用與例外模式有關的附加寄存器。避免使用附加寄存器保證了當任何異常出現時,都不會使工作的狀態不可靠。

4.2.3. 實驗操作步驟
1.開發環境,打開實驗系統例程目錄下ARMMode_test/ARMMode.MCP 項目,并編譯鏈接項目。

3. 單步執行程序并觀察和記錄CPSP 和SPSR 緩存器值的變化;并觀察在相應模式下執行程序后對應緩存器值的變化。

4. 結合實驗內容和相關數據,觀察程序執行,透過實驗加深理解和掌握

4.2.4  試驗程序代碼
;設置本段程序的名稱(Block)及屬性

 AREA Block,CODE,READONLY

 

;程序入口標志

       ENTRY

start

 

       B     Reset_Handler

      

Undefined_Handler

       B Undefined_Handler

       B SWI_Handler

      

Prefetch_Handler

       B Prefetch_Handler

      

Abort_Handler

       B Abort_Handler

       NOP ;空操作

      

IRQ_Handler

       B IRQ_Handler

      

FIQ_Handler

       B FIQ_Handler

      

SWI_Handler

       mov pc, lr

;前面部分是處理程序,主要處理各種模式的入端口跳移

 

Reset_Handler

 

;into System mode

       MRS R0,CPSR      ;復制CPSR 到R0

       BIC R0,R0,#0x1F ;清除R0 的后5 位

       ORR R0,R0,#0x1F       ;設定R0 的最后5 位為11111

       MSR       CPSR_c,R0    ;把R0 裝載到CPSR,切換到系統模式

       MOV R0, #1         ;對系統模式下的R0 賦值,下面的R1~R15 一樣

       MOV R1, #2

       MOV R2, #3

       MOV R3, #4

       MOV R4, #5

       MOV R5, #6

       MOV R6, #7

       MOV R7, #8

       MOV R8, #9

       MOV R9, #10

       MOV R10, #11

       MOV R11, #12

       MOV R12, #13

       MOV R13, #14

       MOV R14, #15

;into FIQ mode

       MRS R0,CPSR

       BIC R0,R0,#0x1F

       ORR R0,R0,#0x11 ;設定R0 的最后5 位為10001

       MSR CPSR_c,R0 ;把R0 裝載到CPSR,切換到Fiq 模式

       MOV R8, #16 ;給Fiq 模式的特有緩存器R8 賦值, 下面的R9~R14 一樣

       MOV R9, #17

       MOV R10, #18     

       MOV R11, #19

       MOV R12, #20

       MOV R13, #21

       MOV R14, #22

;into SVC mode

       MRS R0,CPSR

       BIC R0,R0,#0x1F

       ORR R0,R0,#0x13 ;設定R0 的最后5 位為10011

       MSR CPSR_c,R0   ;把R0 裝載到CPSR,切換到Svc 模式

       MOV R13, #23      ;給SVC 模式的特有緩存器R13 賦值, 下面的R14 一樣

       MOV R14, #24

;into Abort mode

       MRS R0,CPSR

       BIC R0,R0,#0x1F

       ORR R0,R0,#0x17 ;設定R0 的最后5 位為10111

       MSR CPSR_c,R0 ;把R0 裝載到CPSR,切換到Abort 模式

       MOV R13, #25 ;給Abort 模式的特有緩存器R13 賦值, 下面的R14 一樣

       MOV R14, #26

;into IRQ mode

       MRS R0,CPSR

       BIC R0,R0,#0x1F

       ORR R0,R0,#0x12 ;設定R0 的最后5 位為10010

       MSR CPSR_c,R0 ;把R0 裝載到CPSR,切換到IRQ 模式

       MOV R13, #27 ;給IRQ 模式的特有緩存器R13 賦值, 下面的R14一樣

       MOV R14, #28

;into UNDEF mode

       MRS R0,CPSR

       BIC R0,R0,#0x1F

       ORR R0,R0,#0x1b        ;設定R0 的最后5 位為11011

       MSR CPSR_c,R0                 ;把R0 裝載到CPSR,切換到UNDEF 模式

       MOV R13, #29             ;給UNDEF 模式的特有緩存器R13 賦值, 下面的R14 一樣

       MOV R14, #30

       B Reset_Handler ;跳移到最開始地方循環

       END

4.2.5. 實驗練習題
1. 參考例子,把其中系統模式程序更改為使用者模式程序,編譯除錯,觀察執行結果,檢查是否正確,如果有有錯誤分析其原因。(提示:不能從使用者模式直接切換到其它模式,可以先使用SWI 指令切換到管理模式)。

 

 

 

 

 

 

 

 

  山東大學嵌入式系統原理與接口技術課程試卷(A)                       2007——2008 學年     1  學期

題號 一 二 三 四 五 六 七 八 九 十 總分
得分                      

 
 

得分 閱卷人
   

 
 
 
 

單項選擇題(每空2分,共10分)
 
1、對寄存器R1的內容乘以4的正確指令是( )。
   ①LSR R1,#2        ②LSL R1,#2  

   ③MOV R1,R1, LSL #2          ④MOV R1,R1, LSR #2

2、下面指令執行后,改變R1寄存器內容的指令是(     )。

   ①TST R1,#2  ②ORR  R1,R1,R1   ③CMP R1,#2    ④EOR  R1,R1,R1

3、  MOV   R1,#0x1000  

     LDR   R0,[R1],#4

執行上述指令序列后,R1寄存器的值是(    )。

①0x1000     ②0x1004     ③0x0FFC      ④0x4

4、當進行數據寫操作時,可能Cache未命中,根據Cache執行的操作不同,將Cache分為兩類(           )

①數據Cache和指令Cache   ②統一Cache和獨立Cache  ③寫通Cache和寫回Cache  ④讀操作分配Cache和寫操作分配Cache

5、一個異步傳輸過程:設每個字符對應8個信息位、偶校驗、2個停止位,如果波特率為2400,那么每秒鐘能傳輸的最大字符數為(                 )個。

① 200,② 218,③ 240,④ 2400
 得分 閱卷人
   

 
 
 
 
二、填空(共18分)
1、嵌入式處理器可分為以下4類:(                                         )。

2、ARM處理器總共有(  )個寄存器,這些寄存器按其在用戶編程中的功能可劃分為:(          )和(        ),這些寄存器根據ARM處理器不同工作模式,可將全部寄存器分成( )組,在使用中有(                                       )特點。

3、ARM 4種存儲周期的基本類型分別為:(                                     )。

4、S3C44B0X UART單元發送器能夠檢測的四種異步串行通信數據錯誤為(                                                                          )。

得分 閱卷人
   

 
 

簡答題:(每空6分,共30分)
1、從硬件系統來看,嵌入式系統由哪幾部分組成?畫出簡圖。

 

 

 

 

 

 

 

 

 

基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
基于ARM的嵌入式系統程序開發要點(一)
—— 嵌入式程序開發過程
ARM 系列微處理器作為全球16/32位RISC處理器市場的領先者,在許多領
域內得到了成功的應用.近年來,ARM在國內的應用也得到了飛速的發展,越
來越多的公司和工程師在基于ARM的平臺上面開發自己的產品.
與傳統的4/8位單片機相比,ARM的性能和處理能力當然是遙遙領先的,但
與之相應,ARM的系統設計復雜度和難度,較之傳統的設計方法也大大提升了.
本文旨在通過討論系統程序設計中的幾個基本方面,來說明基于ARM的嵌入式
系統程序開發的一些特點,并提出和解決了一些常見的問題.
文章分成幾個相對獨立的章節刊載.第一部分討論基于ARM的嵌入式程序
開發和移植過程中的一些基本概念.
1.嵌入式程序開發過程
不同于通用計算機和工作站上的軟件開發工程,一個嵌入式程序的開發過程
具有很多特點和不確定性.其中最重要的一點是軟件跟硬件的緊密耦合特性.
(不帶操作系統支持) (帶操作系統支持)
圖-1:兩類不同的嵌入式系統結構模型
這是兩類簡化的嵌入式系統層次結構圖.由于嵌入式系統的靈活性和多樣
性,上面圖中各個層次之間缺乏統一的標準,幾乎每一個獨立的系統都不一樣.
這樣就給上層的軟件設計人員帶來了極大地困難.第一,在軟件設計過程中過多
地考慮硬件,給開發和調試都帶來了很多不便;第二,如果所有的軟件工作都需
要在硬件平臺就緒之后進行,自然就延長了整個的系統開發周期.這些都是應該
從方法上加以改進和避免的問題.
為了解決這個問題,工程和設計人員提出了許多對策.首先在應用與驅動(或
API)這一層接口,可以設計成相對統一的一些接口函數,這對于具體的某一個
開發平臺或在某個公司內部,是完全做得到的.這樣一來,就大大提高了應用層
應用(Application)
驅動/板級支持包
(Driver/BSP)
硬件(Hardware)
應用(Application)
硬件抽象層(HAL)
硬件(Hardware)
操作系統(OS)
標準接口函數(API)
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
軟件設計的標準化程度,方便了應用程序在跨平臺之間的復用和移植.
對于驅動/硬件抽象這一層,因為直接驅動硬件,其標準化變得非常困難甚至
不太可能.但是為了簡化程序的調試和縮短開發周期,我們可以在特定的EDA
工具環境下面進行開發,通過后再進行移植到硬件平臺的工作.這樣既可以保證
程序邏輯設計的正確性,同時使得軟件開發可平行甚至超前于硬件開發進程.
我們把脫離于硬件的嵌入式軟件開發階段稱之為"PC軟件"的開發,可以
用下面的圖來示意一個嵌入式系統程序的開發過程.
"PC軟件"開發 移植,測試 產品發布
圖-2:嵌入式系統產品的開發過程
在"PC軟件"開發階段,可以用軟件仿真,即指令集模擬的方法,來對用
戶程序進行驗證.在ARM公司的開發工具中,ADS 內嵌的ARMulator和
RealView 開發工具中的ISS,都提供了這項功能.在模擬環境下,用戶可以設
置ARM處理器的型號,時鐘頻率等,同時還可以配置存儲器訪問接口的時序參
數.程序在模擬環境下運行,不但能夠進行程序的運行流程和邏輯測試,還能夠
統計系統運行的時鐘周期數,存儲器訪問周期數,處理器運行時的流水線狀態(有
效周期,等待周期,連續和非連續訪問周期)等信息.這些寶貴的信息是在硬件
調試階段都無法取得的,對于程序的性能評估非常有價值.
為了更加完整和真實地模擬一個目標系統,ARMulator和ISS還提供了一個
開放的API編程環境.用戶可以用標準C來描述各種各樣的硬件模塊,連同工
具提供的內核模塊一起,組成一個完整的"軟"硬件環境.在這個環境下面開發
的軟件,可以更大程度地接近最終的目標.
利用這種先進的EDA工具環境,極大地方便了程序開發人員進行嵌入式開
發的工作.當完成一個"PC軟件"的開發之后,只要進行正確的移植,一個真
正的嵌入式軟件就開發成功了.而移植過程是相對比較容易形成一套規范的流程
的,其中三個最重要的方面是:
考慮硬件對庫函數的支持
移植
移植
開發/實驗/
測試平臺
最終產品
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
符合目標系統上的存儲器資源分布
應用程序運行環境的初始化
2.開發工具環境里面的庫函數
如果用戶程序里調用了跟目標相關的一些庫函數,則在應用前需要裁剪這些
函數以適合在目標上允許的要求.主要需要考慮以下三類函數:
訪問靜態數據的函數
訪問目標存儲器的函數
使用semihosting(半主機)機制實現的函數
這里所指的C庫函數,除了ISO C標準里面定義的函數以外,還包括由編
譯工具提供的另外一些擴展函數和編譯輔助函數.
2.1 裁剪訪問靜態數據的函數
庫函數里面的靜態數據,基本上都是在頭文件里面加以定義的.比如CTYPE
類庫函數,其返回值都是通過預定義好的CTYPE屬性表來獲得的.比如,想要
改變isalpha() 函數的缺省判斷,則需要修改對應CTYPE屬性表里對字符屬性的
定義.
2.2 裁減訪問目標存儲器的函數
有一類動態內存管理函數,如malloc() 等,其本身是獨立于目標系統而運行
的;但是它所使用的存儲器空間需要根據目標來確定.所以malloc() 函數本身
并不需要裁剪或移植,但那些設置動態內存區(地址和空間)的函數則是跟目標
系統的存儲器分布直接相關的,需要進行移植.示例堆棧的初始化函數
__user_initial_stackheap(),是用來設置堆(heap)和棧(stack)地址的函數,顯
然針對每一個具體的目標平臺,該函數都需要根據具體的目標存儲器資源進行正
確移植.
下面是對示例函數__user_initial_stackheap() 進行移植的一個例子:
__value_in_regs struct __initial_stackheap __user_initial_stackheap(
unsigned R0, unsigned SP, unsigned R2, unsigned SL)
{
struct __initial_stackheap config;
config.heap_base = (unsigned int) 0x11110000;
// config.stack_base = SP; // optional
return config;
}
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
請注意上面的函數體并不完全遵循標準C的關鍵字和語法規范,使用了ARM
公司編譯器(ADS 或RealView Compilation tool) 里的C語言擴展特性.關于編譯
器特定的C語言擴展,請參考相關的編譯器說明,這里簡單介紹函數
__user_initial_stackheap() 的功能,它主要是返回堆和棧的基地址.上面的程序中
只對堆(heap) 的基地址進行了設置(設成了0x11110000),也就是說用戶把
0x11110000開始的存儲器地址用作了動態內存分配區(heap區).具體地址的確
定是要由用戶根據自己的目標系統和應用情況來確定的,至少要滿足以下條件:
0x11110000開始的地址空間有效且可寫(是RAM)
該存儲器空間不與其它功能區沖突(比如代碼區,數據區,stack區等)
因為__user_initial_stackheap() 函數的全部執行效果就是返回一些數值,所
以只要符合接口的調用標準,直接用匯編來實現看起來更加直觀一些:
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR r0,0x11110000
MOV pc,lr
如果不對這個函數進行移植,編譯過程中將使用缺省的設置,這個設置適用
于ARM公司的Integrator系列平臺.
(注意:ARM的編譯/連接工具鏈也提供了繞過庫函數來設置運行時存儲器模型
的方法,請參閱ARM公司其他的相關文檔.)
2.3 裁剪使用semihosting(半主機)機制實現的函數
庫函數里有一大部分函數是涉及到輸入/輸出流設備的,比如文件操作函數需
要訪問磁盤I/O,打印函數需要訪問字符輸出設備等.在嵌入式調試環境下,所
有的標準C庫函數都是有效且有其缺省行為的,很多目標系統硬件不能支持的
操作,都通過調試工具來完成了.比如printf() 函數,缺省的輸出設備是調試器
里面的信息輸出窗口.
但是一個真實的系統是需要脫離調試工具而獨立運行的,所以在程序的移植
過程當中,需先對這些庫函數的運行機制作一了解.
下圖說明了在ADS下面這類C庫函數的結構.
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
圖-3:C庫函數實現過程中的層次調用
如圖中例子所示,函數printf() 最終是調用了底層的輸入/輸出函數
_sys_write() 來實現輸出操作的,而_sys_write() 使用了調試工具的內部機制來把
信息輸出到調試器.
顯然這樣的函數調用過程在一個真實的嵌入式系統里是無法實現的,因為獨
立運行的嵌入式系統將不會有調試器的參與.如果在最終系統中仍然要保留
printf() 函數,而且在系統硬件中具備正確的輸出設備(如LCD等),則在移植
過程中,需要把printf() 調用的輸出設備進行重新定向.
考察printf() 函數的完整調用過程:
圖-4:printf() 的調用過程
單純考慮printf() 的輸出重新定向,可以有三種途徑實現:
改寫printf() 本身
改寫 fput()
改寫 _sys_write()
需要注意的是,越底層的函數,被其他上層函數調用的可能性越大,改變了
一個底層函數的實現,則所有調用該函數的上層函數的行為都被改變了.
以fputc() 的重新實現為例,下面是改變fputc() 輸出設備到系統串行通信端
口的實例:
int fputc(int ch, FILE *f)
ANSI C
Input/
output
Error
handling
Stack &
heap setup
Other
Semihosting Support
應用程序調用的
函數,如printf()
設備驅動程序級
使用semihosting
機制
如_sys_write()
由調試系統執行
printf() fput() _sys_wite()輸出設備
其他函數 其他函數
C 庫函數
調試輔助環境
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
{ /* e.g. write a character to an UART */
char tempch = ch;
sendchar(&tempch); // UART driver
return ch;
}
代碼中的函數sendchar() 假定是系統的串口設備驅動函數.只要新建函數
fput() 的接口符合標準,經過編譯連接后,該函數實現就覆蓋了原來缺省的函數
體,所有對該函數的調用,其行為都被新實現的函數所重新定向了.
具體哪些庫函數是跟目標相關的,這些函數之間的相互調用關系等,請參考
具體的編譯器說明.
3.Semihosting (半主機) 機制
上面提到許多庫函數在調試環境下的實現都調用了一種叫semihosting的機
制.Semihosting具體來講是指一種讓代碼在ARM 目標上運行,但使用運行了
ARM 調試器的主機上I/O 設備的方法;也就是讓ARM 目標將輸入/ 輸出請求
從應用程序代碼傳遞到運行調試器的主機的一種機制.通常這些輸入/輸出設備
包括鍵盤,屏幕和磁盤I/O.
半主機由一組已定義的SWI 操作來實現.庫函數調用相應的SWI(軟件中
斷),然后調試代理程序處理SWI 異常,并提供所需的與主機之間的通訊.
圖-5:Semihosting的實現過程
多數情況下,半主機SWI 是由庫函數內的代碼調用的.但是應用程序也可
以直接調用半主機SWI.半主機SWI 的接口函數是通用的.當半主機操作在硬
件仿真器,指令集仿真器,RealMonitor或Angel下執行時,不需要進行移植處
理.
使用單個SWI 編號請求半主機操作.其它的SWI 編號可供應用程序或操
printf()
printf("Hello world! ");
SWI
調試器
Hello world!
C 庫代碼
應用程序代碼
與運行在主機上的調試器通信
主機屏幕上顯示的文本
目標
主機
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
作系統使用.用于半主機的SWI號是:
在ARM 狀態下:0x123456
在Thumb 狀態下:0xAB
SWI 編號向調試代理程序指示該SWI 請求是半主機請求.要辨別具體的操
作類型,用寄存器r0 作為參數傳遞.r0 傳遞的可用半主機操作編號分配如下:
0x00-0x31:這些編號由ARM 公司使用,分別對應32個具體的執行函
數.
0x32-0xFF:這些編號由ARM 公司保留,以備將來用作函數擴展.
0x100-0x1FF:這些編號保留給用戶應用程序.但是,如果編寫自己的
SWI 操作,建議直接使用SWI指令和SWI編號,而不要使用半主機
SWI 編號加這些操作類型編號的方法.
0x200-0xFFFFFFFF:這些編號未定義.當前未使用并且不推薦使用這
些編號.
半主機SWI使用的軟件中斷編號也可以由用戶自定義,但若是改變了缺省
的軟中斷編號,需要:
更改系統中所有代碼(包括庫代碼)的半主機SWI 調用
重新配置調試器對半主機請求的捕捉與相應
這樣才能使用新的SWI 編號.
有關半主機SWI處理函數實現的更詳細信息,請參考ARM編譯器的相關
文檔.
4.應用環境的初始化和根據目標系統資源進行的移植
在下一期中介紹應用環境和目標系統的初始化.
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
基于ARM的嵌入式系統程序開發要點(二)
—— 系統的初始化過程
基于ARM的芯片多數為復雜的片上系統集成(SoC),這種復雜的系統里多
數的硬件模塊都是可配置的,需要由軟件來設置其需要的工作狀態.因此在用戶
的應用程序啟動之前,需要有專門的一段啟動代碼來完成對系統的初始化.由于
這類代碼直接面對處理器內核和硬件控制器進行編程,一般都使用匯編語言.系
統啟動程序所執行的操作跟具體的目標系統和開發系統相關,一般通用的內容包
括:
中斷向量表
初始化存儲器系統
初始化堆棧
初始化有特殊要求的端口,設備
初始化應用程序執行環境
改變處理器模式
呼叫主應用程序
1.中斷向量表
ARM要求中斷向量表必須放置在從0地址開始,連續8×4字節的空間內
(ARM720T和ARM9/10及以后的ARM處理器也支持從0xFFFF0000開始的高
地址向量表,在本文的其他地方對此不再另加說明).各個中斷矢量在向量表中
的位置分配如下圖:
圖1:中斷向量表
每當一個中斷發生以后,ARM處理器便強制把PC指針置為向量表中對應中
Reset 復位中斷 0x00
Undef 未定義指令中斷 0x04
Software Interrupt 軟件中斷 0x08
Prefetch Abort 指令預取異常 0x0C
Data Abort 數據異常 0x10
(Reserved) 保留 0x14
IRQ 普通外部中斷 0x18
FIQ 外部快速中斷 0x1C
… …
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
斷類型的地址值.因為每個中斷只占據向量表中1個字的存儲器空間,只能放置
1條ARM指令,所以通常在向量表中放的是跳轉指令,使程序能從向量表里跳
轉到存儲器里的其他地方,再執行中斷處理.
中斷向量表的程序實現通常如下所示:
AREA Boot, CODE, READONLY
ENTRY
B Reset_Handler ; Reset_Handler is a label
B Undef_Handler
B SWI_Handler
B PreAbort_Handler
B DataAbort_Handler
B . ; for reserved interrupt, stop here
B IRQ_Handler
B FIQ_Handler
其中的關鍵字ENTRY是指定編譯器保留這段代碼,因為編譯器可能會認為
這是一段冗余代碼而加以優化.連接的時候要確保這段代碼被鏈接在0地址處,
并且作為整個程序的入口點(關鍵字ENTRY并非總是用來設置程序入口點,所
以通常需要在連接選項里顯式地指定程序入口點).
2.初始化存儲器系統
初始化存儲器系統的編程對象是系統的存儲器控制器.存儲器控制器并不是
ARM內核的一部分,不同的系統其設計不盡相同,所以應該針對具體的要求來
完成這部分的程序設計.一般來說,下面這兩個方面是比較通用的.
2.1.存儲器類型和時序配置
一個復雜的系統可能存在多種存儲器類型的接口,需要根據實際的系統設計
對此加以正確配置.對同一種存儲器類型來說,也因為訪問速度的差異,需要不
同的時序設置.
通常Flash 和SRAM同屬于靜態存儲器類型,可以合用同一個存儲器端口;
而DRAM 因為動態刷新和地址線復用等特性,通常配有專用的存儲器端口.
存儲器端口的接口時序優化是非常重要的,影響到整個系統的性能.因為一
般系統運行的速度瓶頸都存在于存儲器訪問,所以存儲器訪問時序應盡可能地
快;但同時又要考慮由此帶來的穩定性問題.只有根據具體選定的芯片,進行多
次的測試之后,才能確定最佳的時序配置.
2.2.存儲器地址分布(memory map)
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
有些系統具有非常靈活的存儲器地址分配特性,進行存儲器初始化設計的時
候一定要根據應用程序的具體要求來完成地址分配.
一種典型的情況是啟動ROM的地址重映射(remap).如前面第1節所述,
當一個系統上電后程序將自動從0地址處開始執行,因此在系統的初始狀態,必
須保證在0地址處存在正確的代碼,即要求0地址開始處的存儲器是非易性的
ROM或Flash等.但是因為ROM或Flash的訪問速度相對較慢,每次中斷發生
后都要從讀取ROM或Flash上面的向量表開始,影響了中斷響應速度.因此有
的系統便提供一種靈活的地址重映射方法,可以把0地址重新指向到RAM中去.
在這種地址映射的變化過程當中,程序員需要仔細考慮的是程序的執行流程不能
被這種變化所打斷.比如下面這種情況:
圖2:啟動ROM的地址重映射對程序執行流程的影響
系統上電后從Flash內的0地址開始執行,啟動代碼位于地址0x100開始的
空間,當執行到地址0x200時,完成了一次地址的重映射,把原來0開始的地址
空間由Flash轉給了RAM.接下去執行的指令(這里為了簡化起見,忽略流水
線指令預取的模型)將來自從0x204開始的RAM空間.如果預先沒有對RAM
內容進行正確的設置,則里面的數據都是隨機的,這樣處理器在執行完0x200
地址處的指令之后,再往下取指執行就會出錯.解決的方法就是要使RAM在使
用之前準備好正確的內容,包括開頭的向量表部分.
有的系統不具備存儲器地址重映射的功能,所有的空間地址就相對簡單一
些,不需要考慮這方面的問題.
3.初始化堆棧
因為ARM處理器有7種執行狀態,每一種狀態的堆棧指針寄存器(SP)都
Flash
0x0100
(Reset_Handler)
B Reset_Handler
… …
.
.
.
(boot code)
.
.
(remap)
0x0000
0x0200
RAM
0x0200
remap 0x0204
Vector Table
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
是獨立的(System和User模式使用相同的SP寄存器).因此對程序中需要用到
的每一種模式都要給SP寄存器定義一個堆棧地址.方法是改變狀態寄存器CPSR
內的狀態位,使處理器切換到不同的狀態,然后給SP賦值.注意不要切換到User
模式進行User模式的堆棧設置,因為進入User模式后就不能再操作CPSR回到
別的模式了.可能會對接下去的程序執行造成影響.
一般堆棧的大小要根據需要而定,但是要盡可能給堆棧分配快速和高帶寬的
存儲器.堆棧性能的提高對系統整體性能的影響是非常明顯的.
這是一段堆棧初始化的代碼示例,其中只定義了三種模式的SP指針:
MRS R0, CPSR ; CPSR -> R0
BIC R0, R0, #MODEMASK ; 安全起見,屏蔽模式位以外的其它位
ORR R1, R0, #IRQMODE ; 把設置模式位設置成需要的模式
MSR CPSR_cxsf, R1 ; 轉到IRQ模式
LDR SP, =UndefStack ; 設置 SP_irq
ORR R1,R0,#FIQMODE
MSR CPSR_cxsf, R1 ; FIQMode
LDR SP, =FIQStack
ORR R1, R0, #SVCMODE
MSR CPSR_cxsf, R1 ; SVCMode
LDR SP, =SVCStack
注意上面的程序中使用到的3個SP寄存器是不同的物理寄存器:SP_irq,
SP_fiq和SP_svc.引用的幾個標號假設已經正確定義.
4.初始化有特殊要求的端口,設備
這要由具體的系統和用戶需求而定.一般的外設初始化可以在系統初始化之
后進行.
比較典型的應用是驅動一些簡單的輸出設備,如LED等,來指示系統啟動
的進程和狀態.
5.初始化應用程序執行環境
一個簡單的可執行程序的映像結構通常如下:
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
圖3:程序映像的結構
映像一開始總是存儲在ROM/Flash里面的,其RO部分既可以在ROM/Flash
里面執行,也可以轉移到速度更快的RAM中去;而RW和ZI這兩部分必須是
需要轉移到可寫的RAM里去的.所謂應用程序執行環境的初始化,就是完成必
要的從ROM到RAM的數據傳輸和內容清零.
不同的工具鏈會提供一些不同的機制和方法幫助用戶完成這一步操作,主要
是跟鏈接器(Linker)相關.下面是在ARM開發工具環境(ADS或RVCT)下,
一種常用存儲器模型的直接實現:
LDR r0, =|Image$$RO$$Limit ; Get pointer to ROM data
LDR r1, =|Image$$RW$$Base| ; RAM copy address
LDR r3, =|Image$$ZI$$Base| ; Zero init base => top of initialised data
CMP r0, r1 ; Check that they are different
BEQ %F1
0
CMP r1, r3 ; Copy init data
LDRCC r2, [r0], #4 ; ([r0] -> r2) and (r0+4)
STRCC r2, [r1], #4 ; (r2 -> [r1]) and (r1+4)
BCC %B0
1
LDR r1, =|Image$$ZI$$Limit| ; Top of zero init segment
MOV r2, #0
2
CMP r3, r1
STRCC r2, [r3], #4 ; (0 -> [r3]) and (r3+4)
BCC %B2
程序實現了RW數據的拷貝和ZI區域的清零功能.其中引用到的4個符號
是由連接器(linker)定義輸出的:
|Image$$RO$$Limit|:表示RO區末地址后面的地址,即RW數據源的起始地址.
|Image$$RW$$Base|:RW區在RAM里的執行區起始地址,也就是編譯選項
RW_Base指定的地址;程序里是RW數據拷貝的目標地址.
ZI (Zero initialized R/W Data)
RW (R/W Data)
RO (Code + RO Data)
編譯結果
定義時帶初始值的全局變量
只定義了變量名的全局變量
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
|Image$$ZI$$Base|:ZI區在RAM里面的起始地址.
|Image$$ZI$$Limit|:ZI區在RAM里面的結束地址后面的一個地址.
程序先把ROM里 |Image$$RO$$Limit| 開始的RW初始數據拷貝到RAM里
|Image$$RW$$Base| 開始的地址,當RAM這邊的目標地址到達
|Image$$ZI$$Base| 后就表示RW區的結束和ZI區的開始,接下去就對這片ZI
區進行清零操作,直到遇到結束地址 |Image$$ZI$$Limit|.
6.改變處理器模式
ARM處理器(V4架構以后的版本)一共有7種執行模式:
User: 用戶模式
FIQ: 快速中斷響應模式
IRQ: 一般中斷響應模式
Supervisor:超級模式
Abort: 出錯處理模式
Undef: 未定義模式
System: 系統模式
除用戶模式以外,其他6種模式都是特權模式.因為在初始化過程中許多操
作需要在特權模式下才能進行(比如CPSR的修改),所以要特別注意不能過早
地進入用戶模式.一般地,在初始化過程中會經歷以下一些模式變化:
圖4:處理器模式變換過程
在最后階段才把模式轉換到最終應用程序運行所需的模式,一般是用戶模
式.
內核級的中斷使能(CPSR的I,F位狀態)也可以考慮在這一步進行.如果
系統中另外存在一個專門的中斷控制器(多數情況下是這樣的),這么做總是安
全的,否則就需要考慮過早地打開中斷可能帶來的問題,比如在系統初始化完成
之前就觸發了有效中斷,導致系統的死機.
7.呼叫主應用程序
當所有的系統初始化工作完成之后,就需要把程序流程轉入主應用程序.最
簡單的一種情況是:
復位后的缺省模式 注意不要進入用戶模式 用戶選擇
(堆棧初始化階段)
超級模式
(Supervisor)
多種特權模式
變化
設置成用戶程
序運行模式
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
IMPORT main ; get the label main if main() is defined in other files
B man ; jump to main()
直接從啟動代碼跳入應用程序主函數入口,主函數名字可由用戶自己定義.
在ARM ADS環境中,還另外提供了一套系統級的呼叫機制.
IMPORT __main
B __main
__main()
圖5:在應用程序主函數之前插入__main
__main() 是編譯系統提供的一個函數,負責完成庫函數的初始化和第5節中
所描述的功能,最后自動跳向main() 函數.這種情況下用戶程序的主函數名字
必須得是main.
用戶可以根據需要選擇是否使用__main().如果想讓系統自動完成系統調用
(如庫函數)的初始化過程,可以直接使用__main();如果所有的初始化步驟都
是由用戶自己顯式地完成,則可以跳過__main().
當然,使用__main() 的時候,可能會涉及到一些庫函數的移植和重定向問
題.在__main() 里面的程序執行流程如下圖所示:
圖6:有系統調用參與的程序執行流程
關于在__main() 里面調用到的庫函數說明,可以參閱相關的編譯器文檔,
庫函數移植和重定向的方法,可以參考上一期文章里面的相關章節.
Image Entry Point
__main
·copy code and data
·zero initialize
__rt_entry
·initialize library functions
·call top-level constructors
(C++)
·Exit from application
·
Reset handler
·user boot code
User application
·main
__User_initial_stackheap
·set up stack & heap
啟動代碼 應用程序初始化用戶應用程序main()
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
基于ARM的嵌入式系統程序開發要點(三)
—— 如何滿足嵌入式系統的靈活需求
因為嵌入式應用領域的多樣性,每一個系統都具有各自的特點.在進行系統
程序設計的時候,一定要進行具體分析,充分利用這些特點,揚長避短.
結合ARM架構本身的一些特點,在這里討論幾個常見的要點.
1.ARM還是Thumb
在討論ARM還是Thumb之前,先說明ARM內核型號和ARM結構體系之
間的區別和聯系.
圖-1 ARM結構體系和處理器家族的演變發展
如圖-1所示,ARM的結構體系主要從版本4開始,發展到了現在的版本6,
結構體系的變化,對程序員而言最直接的影響就是指令集的變化.結構體系的演
變意味著指令集的不斷擴展,值得慶幸的是ARM結構體系的發展一直保持了向
上兼容,不會造成老版本程序在新結構體系上的不兼容.
在圖中的橫坐標上,顯示了每一個體系結構上都含有眾多的處理器型號,這
是在同一體系結構下根據硬件配置和存儲器系統的不同而作的進一步細分.需要
注意的是通常我們用來區分ARM處理器家族的ARM7,ARM9或ARM10,可
能跨越不同的體系結構.
在ARM的體系結構版本4與5中,還可以再細分出幾個小的擴展版本:V4T,
V5TE和V5TEJ,其區別如圖-2中所示,這些后綴名也反映在各自擁有的處理器
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
型號上面,可以進行直觀的分辨.V6結構體系因為包含了以前版本的所有特性,
所以不需要再進行分類.
圖-2 結構體系特征
上面介紹了整個ARM處理器家族的分布,主要是說明在一個特定的平臺上
編寫程序的時候,一定要先弄清楚目標的特性和一些細微的差別,特別是需要具
體優化特征的時候.
從ARM體系結構V4T以后,最大的變化是增加了一套16位的指令集——
Thumb.到底在一個具體應用中要否采用Thumb呢 首先我們來分析一下ARM
和Thumb各自的特點和優勢.先看下面一張性能分析圖:
圖-3 ARM和Thumb指令集的比較
圖中的縱坐標是測試向量Dhrystone在20MHz頻率下運行1秒鐘的結果,其
值越大表明性能越好;橫坐標是系統存儲器系統的數據總線寬度.結果表明:
(a) 當系統具有32位的數據總線寬度時,ARM比Thumb有更好的性能表現.
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
(b) 當系統的數據總線寬度小于32位時,Thumb比ARM的性能更好.
由此可見,并不是32位的ARM指令集性能一定強于16位的Thumb指令集,
要具體情況具體分析.考察個中的原因,其實不難發現,因為當在一個16位存
儲器系統里面取1條32位指令的時候,需要耗費2個存儲器訪問周期;比之32
位的系統,其速度正好大概下降一半左右.而16位指令在32位存儲器系統或
16位存儲器系統里的表現基本相同.正是存儲器造成的系統瓶頸導致了這個有
趣的差別.
除了在窄帶寬系統里面的性能優勢外,Thumb指令的另外一個好處是代碼尺
寸.同樣一段C代碼,用Thumb指令編譯的結果,其長度大約只占ARM編譯
結果的65%左右,可以明顯地節省存儲器空間.在大多數情況下,緊湊的代碼和
窄帶寬的存儲器系統,還會帶來功耗上的優勢.
當然,如果在32位的系統上面,并且對系統性能要求很高的情況下,ARM
是一個更好的選擇.畢竟在這種情況下,只有32位的指令集才能完全發揮32
位處理器的優勢來.
因此,選擇ARM還是Thumb,需要從存儲器開銷和性能要求兩方面加以權
衡考慮.
2.堆棧的分配
在圖-3中,橫坐標上還有一種情況,就是16位的存儲器寬度,但是堆棧空
間是32位的.這種情況下無論ARM還是Thumb,其性能表現都比單純的16位
存儲器系統情況下要好.這是因為ARM和Thumb其指令集雖然分32位和16
位,但是堆棧全部是采用32位的.因此在16位堆棧和32位堆棧的不同環境下,
其性能當然都會相差很多.這種差別還跟具體的應用程序密切相關,如果一個程
序堆棧的使用頻率相當高,則這種性能差異很大;反之則要小一些.
在基于ARM的系統中,堆棧不僅僅被用來進行諸如函數調用,中斷響應等
時候的現場保護,還是程序局部變量和函數參數傳遞(如果大于4個)的存儲空
間.所以出于系統整體性能考慮,要給堆棧分配相對訪問速度最快,數據寬度最
大的存儲器空間.
一個嵌入式系統通常存在多種多樣的存儲器類型.設計的時候一定要先清楚
每一種存儲器的訪問速度,地址分配和數據線寬度.然后根據不同程序和目標模
塊對存儲器的不同要求進行合理分配,以期達到最佳配置狀態.
3.ROM還是RAM在0地址處
顯然當系統剛啟動的時候,0地址處肯定是某種類型的ROM,里面存儲了系
統的啟動代碼.但是很多靈活的系統設計中,0地址處的存儲器類型是可映射的.
也就是說,可以通過軟件的方法,把別的存儲器(主要是快速的RAM)分配以
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
0起始的地址.
這種做法的最主要目的之一是提高系統對中斷的反應速度.因為每一個中斷
發生的時候,ARM都需要從0地址處的中斷向量表開始其中斷響應流程,顯然
把中斷向量表放在RAM里,比放在ROM里有更快的訪問速度.因此,如果系
統提供了這一類的地址重映射功能,軟件設計者一定要加以利用.
下面是一個典型的經過0地址重映射之后的存儲空間分布圖,注意盡可能把
速度要求最高的部分放置在系統里面訪問速度最快,帶寬最寬的RAM里面.
圖-4 系統存儲器分布的實例
4.存儲器地址重映射(memory remap)
存儲器地址重映射是當前很多先進控制器所具有的功能.在上一節中已經提
到了0地址處存儲器重映射的例子,簡而言之,地址重映射就是可以通過軟件配
置來改變一塊存儲器物理地址的一種機制或方法.
當一段程序對運行自己的存儲器進行重映射的時候,需要特別注意保證程序
執行流程在重映射前后的承接關系.下面是一種典型的存儲器地址重映射情況:
Peripherals
RO
Reset Handler
Heap
RW/ZI
Stack
Exception Handlers
Vector Table
Fast32-bit RAM
16-bit RAM
Flash
0x0000 0000
0x0000 4000
0x0001 0000
0x0001 8000
0x2400 0000
0x2800 0000
0x4000 0000
可以在ROM 里運行的代碼
外設寄存器
變量區和動態內存分配區
需要快速響應的代碼和數據
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
圖-5 存儲器重映射舉例1
系統上電后的缺省狀態是0地址上放有ROM,這塊ROM有兩個地址:從0
起始和從0x10000起始,里面存儲了初始化代碼.當進行地址remap以后,從0
起始的地址被定向到了RAM上,ROM則只保留有唯一的從0x10000起始的地
址了.
如果存儲在ROM 里的Reset_Handler一直在0 - 0x4000的地址上運行,則
當執行完remap以后,下面的指令將從RAM里預取,必然會導致程序執行流程
的中斷.根據系統特點,可以用下面的辦法來解決這個問題:
(1) 上電后系統從0地址開始自動執行,設計跳轉指令在remap發生前使PC
指針指向0x10000開始的ROM地址中去,因為不同地址指向的是同一塊
ROM,所以程序能夠順利執行.
(2) 這時候0 - 0x4000的地址空間空閑,不被程序引用,執行remap后把RAM
引進.因為程序一直在0x10000起始的ROM空間里運行,remap對運行
流程沒有任何影響.
(3) 通過在ROM里運行的程序,對RAM進行相應的代碼和數據拷貝,完成
應用程序運行的初始化.
下面是一段實現上述步驟的例程:
-------------------------------------------------------------------------------------------------------
ENTRY
;啟動時,從0開始,設法跳轉到"真"的ROM地址(0x10000開始的空間里)
LDR pc, =start
;insert vector table here

Start ;Begin of Reset_Handler
; 進行remap設置
remap
0x10000
0x4000
=
0x4000
0x0000
Reset Handler
Vectors
0x4000
0x0000
RAMROM
0x10000
0x10400
ROM ROM
0x10400
Vectors
Reset Handler
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
LDR r1, =Ctrl_reg ;假定控制remap的寄存器
LDR r0, [r1]
ORR r0, r0, #Remap_bit ;假定對控制寄存器進行remap設置
STR r0, [r1]
;接下去可以進行從ROM到RAM的代碼和數據拷貝
-------------------------------------------------------------------------------------------------------
除此之外,還有另外一種常見的remap方式,如下圖:
圖-6 存儲器重映射舉例2
原來RAM和ROM各有自己的地址,進行重映射以后RAM和ROM的地址
都發生了變化,這種情況下,可以采用以下的方案:
(1) 上電后,從0地址的ROM開始往下執行.
(2) 根據映射前的地址,對RAM進行必要的代碼和數據拷貝.
(3) 拷貝完成后,進行remap操作.
(4) 因為RAM在remap前準備好了內容,使得PC指針能繼續在RAM里取
到正確的指令.
不同的系統可能會有多種靈活的remap方案,根據上面提到的兩個例子,可
以總結出最根本的考慮是:要使程序指針在remap以后能繼續往下得到正確的指
令.
5. 根據目標存儲器系統分散加載映像(scatterloading)
Scatterloading文件是ARM的工具鏈里面的一個特性,作為程序編譯過程中
給連接器使用的一個參數,用來指定最終生成的目標映像文件運行時的分布狀
態.如果用戶程序映像只是如圖7所示的最簡狀態,所有的可執行代碼都集合放
置在一起,那么可以不使用Scatterloading文件,直接用連接器的命令行選項就
remap
0x20000
0x4000
=
0x4000
0x0000
Reset Handler
Vectors
0x4000
0x0000
RAMROM
0x10000
0x10400
RAM ROM
0x20400
Vectors
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
能夠完成設置:
RO = 0x00000:表示映像的第一條指令開始地址;
RW = 0x10000:表示變量區的起始地址,變量區一定要位于RAM區.
圖-7 簡單的映像分布舉例
但是一個復雜的系統可能會把映像分割成幾個部分.如圖8,系統中存在多
種類型的存儲器,不能的代碼部分根據執行性能優化的考慮分布與不同的地方.
圖-8 復雜的映像分布舉例
這時候不能通過簡單的RO,RW參數來完成實現上述配置,就要用到
scatterloading文件了.在scatterloading文件里,可以給編譯出來的各個目標模塊
RO
RW
ZI
Stack
Heap
RAM
Flash 代碼區
變量區
0x00000
0x10000
Exception Handler
RO
Reset Handler
Heap
RW & ZI
Stack
Vector table
0x0000
0x4000
0x10000
0x18000
0x20000
0x28000
32-bit fast RAM
16-bit RAM
Flash
性能要求最苛刻的部分
變量區和動態內存分配區
普通程序區
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
指定運行地址,下面的例子是針對圖8的.
FLASH 0x20000 0x8000
{
FLASH 0x20000 0x8000
{
init.o (Init, +First)
* (+RO)
}
32bitRAM 0x0000
{
vectors.o (Vect, +First)
handlers.o (+RO)
}
STACK 0x1000 UNINIT
{
stackheap.o (stack)
}
:
:
16bitRAM 0x10000
{
* (+RW,+ZI)
}
HEAP 0x15000 UNINIT
{
stackheap.o (heap)
}
}
關于scatterloading文件的詳細語法,請參閱ARM公司的相關手冊.
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
基于ARM的嵌入式系統程序開發要點(四)
—— 異常處理機制的設計
異常或中斷是用戶程序中最基本的一種執行流程或形態,這部分對ARM架
構下異常處理程序的編寫作一個全面的介紹.
ARM一共有7種類型的異常,按優先級從高到低排列如下:
Reset
Data Abort
FIQ
IRQ
Prefetch Abort
SWI
Undefined instruction
請注意在ARM的文檔中,使用術語exception 來描述異常.Exception主要
是從處理器被動接受異常的角度出發描述,而interrupt帶有向處理器主動申請的
色彩.在本文中,對"異常"和"中斷"不作嚴格區分,都是指請求處理器打斷
正常的程序執行流程,進入特定程序循環的一種機制.
1.異常響應流程
如以前介紹異常向量表時所提到過的,每一個異常發生時,總是從異常向量
表開始起跳的,最簡單的一種情況是:
圖-1 異常向量表
B
B
(Reserved)
B B
B
B
B
B
0x1C
0x18
0x14
0x10
0x0C
0x08
0x04
0x00
FIQ_Handler()
IRQ_Handler()
DataAbt_Handler()
PreAbt_Handler()
SWI_Handler()
Undef_Handler()
Reset_Handler()
中斷處理函數
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
向量表里面的每一條指令直接跳向對應的異常處理函數.其中FIQ_Handler()
可以直接從地址0x1C處開始,省下一條跳轉指令.
但是當執行跳轉的時候有2個問題需要討論:跳轉范圍和異常分支.
1.1 跳轉范圍
我們知道ARM的跳轉指令(B)是有范圍限制的(±32MB),但很多情況
下不能保證所有的異常處理函數都定位在向量表的32MB范圍內,需要大于
32MB的長跳轉,而且因為向量表空間的限制只能由一條指令完成.這可以通過
下面二種方法實現.
(a) MOV PC, #imme_value
把目標地址直接賦給PC寄存器.
但是這條指令受格式限制并不能處理任意立即數,只有當這個立即數能夠
表示為一個8-bit數值通過循環右移偶數位而得到,才是合法的.示例:
MOV PC, #0x30000000 是合法的,因為0x300000000可以通過0x03循
環右移4位而得到.
而 MOV PC, #30003000 就是非法指令.
(b) LDR PC, [PC+offset]
把目標地址先存儲在某一個合適的地址空間,然后把這個存儲器單元上的32
位數據傳送給PC來實現跳轉.
這種方法對目標地址值沒有要求,可以是任意有效地址.但是存儲目標地址
的存儲器單元必須在當前指令的±4KB空間范圍內.
注意在計算指令中引用的offset數值的時候,要考慮處理器流水線中指令預
取對PC值的影響,以圖-2的情況為例:
offset = address location - vector address - pipeline effect
= 0xFFC - 0x4 - 0x8
= 0xFF0
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
圖-2 利Literal pool實現跳轉
1.2 異常分支
ARM內核只有二個外部中斷輸入信號nFIQ和nIRQ,但對于一個系統來說,
中斷源可能多達幾十個.為此,在系統集成的時候,一般都會有一個異常控制器
來處理異常信號.
圖-3 中斷系統
這時候,用戶程序可能存在多個IRQ/FIQ的中斷處理函數,為了從向量表
開始的跳轉最終能找到正確的處理函數入口,需要設計一套處理機制和方法.
圖-4 中斷分支
LDR PC, [PC, 0xFF0]
0x30003000
Undef_Handler()
0x00
0x04
0xFFC
32MB
0x30003000
n
1
2 多
中斷源
中斷
控制器
ARM
內核
nIRQ
nFIQ
外設通信
配置/獲取信息
IRQ 0x14
IRQ_Handler_1()
IRQ_Handler_2()
...
...
IRQ_Handler_n()

基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
(a) 硬件處理
有的系統在ARM的異常向量表之外,又增加了一張由中斷控制器控制的特
殊向量表.當由外設觸發一個中斷以后,PC能夠自動跳到這張特殊向量表中去,
特殊向量表中的每個向量空間對應一個具體的中斷源.
舉例來說,下面的系統一共有20個外設中斷源,特殊向量表被直接放置在
普通向量表后面.
圖-5 額外的硬件異常向量表
當某個外部中斷觸發之后,首先觸發ARM的內核異常,中斷控制器檢測到
ARM的這種狀態變化,再通過識別具體的中斷源,使PC自動跳轉到特殊向量
表中的對應地址,從而開始一次異常響應.需要檢查具體的芯片說明,是否支持
這類特性.
(b) 軟件處理
多數情況下是用軟件來處理異常分支.因為軟件可以通過讀取中斷控制器來
獲得中斷源的詳細信息.
圖-6 軟件控制中斷分支
Int_20
.
.
.
Int_2
Int_1
FIQ
IRQ
.
.
Reset
0x70
0x6C
.
.
.
0x24
0x20
0x1C
0x18
.
.
0x00
Int_20_Handler()



Int_2_Handler()
Int_1_Handler()
(獲取狀態信息)
IRQ
… …

中斷控制器
IRQ_Handler:
Switch(int_source)
{
case 1:
case 2:

}
Int_1_Handler()
Int_2_Handler()
… …
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
因為軟件設計的靈活性,用戶可以設計出比上圖更好的流程控制方法來.下
面是一個例子:
圖-7 靈活的軟件分支設計
Int_vector_table是用戶自己開辟的一塊存儲器空間,里面按次序存放異常處
理函數的地址.IRQ_Handler()從中斷控制器獲取中斷源信息,然后再從
Int_verctor_table中的對應地址單元得到異常處理函數的入口地址,完成一次異
常響應的跳轉.這種方法的好處是用戶程序在運行過程中,能夠很方便地動態改
變異常服務內容.
2.異常處理函數的設計
2.1 異常發生時處理器的動作
當任何一個異常發生并得到響應時,ARM內核自動完成以下動作:
拷貝 CPSR 到 SPSR_
Address of Int_n_Handler()
.
.
.
Address of Int_2_Handler()
Address of Int_1_Handler()
(獲取狀態信息)
IRQ
… …

中斷控制器
IRQ_Handler():
Switch(int_source)
{
case 1:
case 2:

case n:
}
Int_1_Handler()
Int_2_Handler()
… …
Int_vector_table
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
設置適當的 CPSR 位:
改變處理器狀態進入 ARM 狀態
改變處理器模式進入相應的異常模式
設置中斷禁止位禁止相應中斷
更新 LR_
設置 PC 到相應的異常向量
注意當響應異常后,不管異常發生在ARM還是Thumb狀態下,處理器都將
自動進入ARM狀態.另一個需要注意的地方是中斷使能被自動關閉,也就是說
缺省情況下中斷是不可重入的.單純的把中斷使能位打開接受重入的中斷會帶來
新的問題,在第3部分中對此會有詳細介紹.
除這些自動完成的動作之外,如果在匯編級進行手動編程,還需要注意保存
必要的通用寄存器.
2.2 進入異常處理循環后軟件的任務
進入異常處理程序以后,用戶可以完全按照自己的意愿來進行程序設計,包
括調用Thumb狀態的函數,等等.但是對于絕大多數的系統來說,有一個步驟
必須處理,就是要把中斷控制器中對應的中斷狀態標識清掉,表明該中斷請求已
經得到響應.否則等退出中斷函數以后,又馬上會被再一次觸發,從而進入周而
復始的死循環.
2.3 異常的返回
當一個異常處理返回時,一共有3件事情需要處理:通用寄存器的恢復,狀
態寄存器的恢復以及PC指針的恢復.
通用寄存器的恢復采用一般的堆棧操作指令,而PC和CPSR的恢復可以通
過一條指令來實現,下面是3個例子:
MOVS pc, lr 或 SUBS pc, lr, #4 或LDMFD sp!, {pc}^
這幾條指令都是普通的數據處理指令,特殊之處就是把PC寄存器作為了目
標寄存器,并且帶了特殊的后綴"S"或"^",在特權模式下,"S"或"^"的作
用就是使指令在執行時,同時完成從SPSR到CPSR的拷貝,達到恢復狀態寄存
器的目的.
異常返回時另一個非常重要的問題是返回地址的確定.在2.1節中提到進入
異常時處理器會有一個保存LR的動作,但是該保存值并不一定是正確中斷的返
回地址.下面以一個簡單的指令執行流水狀態圖來對此加以說明.
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
圖-8 ARM狀態下3級指令流水線執行示例
我們知道在ARM架構里,PC值指向當前執行指令的地址加8處.也就是說,
當執行指令A(地址0x8000)時,PC等于指令C的地址(0x8008).如果指令
A是"BL"指令,則當執行時,會把PC(=0x8008)保存到LR寄存器里面,但
是接下去處理器會馬上對LR進行一個自動的調整動作:LR=LR-0x4.這樣,最
終保存在LR里面的是B指令的地址,所以當從BL返回時,LR里面正好是正
確的返回地址.
同樣的調整機制在所有LR自動保存操作中都存在,比如進入中斷響應時處
理器所做的LR保存中,也進行了一次自動調整,并且調整動作都是LR=LR-0x4.
由此我們來對不同異常類型的返回地址進行依次比較:
假設在指令B處(地址0x8004)發生了中斷響應,進入中斷響應后LR上經
過調整保存的地址值應該是C的地址0x8008.
(a) 如果發生的是軟件中斷,即B是"SWI"指令
從SWI中斷返回后下一條執行指令就是C,正好是LR寄存器保存的地址,
所以只要直接把LR恢復給PC.
(b) 如果發生的是"IRQ"或"FIQ"等指令
因為外部中斷請求中斷了B指令的執行,當中斷返回后,需要重新回到B
指令的執行,也就是返回地址應該是B(0x8004),需要把LR減4.
(c) 如果發生的是"Data Abort"
在B上進入數據異常的響應,但導致數據異常的原因卻應該是上一條指令A.
當中斷處理程序修復數據異常以后,要回到A上重新執行導致數據異常的指令,
因此返回地址應該是LR減8.
如果原來的指令執行狀態是Thumb,異常返回地址的分析與此類似,對LR
的調整正好與ARM狀態完全一致.
2.4 ARM編譯器對異常處理函數編寫的擴展
F D E
F D E
F D E
F D E
0x8000 A
0x8004 B
0x8008 C
0x800C D
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
考慮到異常處理函數在現場保護和返回地址的處理上與普通函數的不同之
處,不能直接把普通函數體連接到異常向量表上,需要在上面加一層封裝,下面
是一個例子:
IRQ_Handler ;中斷響應,從向量表直接跳來
STMFD SP!, {R0-R12, LR} ;保護現場,一般只需保護{r0-r3,lr}即可
BL IrqHandler ;進入普通處理函數,C或匯編均可
LDMFD SP!, {R0-R12, LR} ;恢復現場
SUBS PC, LR, #4 ;中斷返回,注意返回地址
為了方便使用高級語言直接編寫異常處理函數,ARM編譯器對此作了特定
的擴展,可以使用函數聲明關鍵字__irq,這樣編譯出來的函數就滿足異常響應
對現場保護和恢復的需要,并且自動加入對LR進行減4的處理,符合IRQ和
FIQ中斷處理的要求.
__irq void IRQ_Handler (void)
{…}
2.5 軟件中斷處理
軟件中斷由專門的軟中斷指令SWI觸發,SWI指令后面跟一個中斷編號,
以標識可能共存的多個軟件中斷程序.
圖-9 軟件中斷處理流程
在C程序中調用軟件中斷需要用到編譯器的擴展功能,使用關鍵字"__swi"
來聲明中斷函數.注意軟中斷號碼同時在函數定義時指定.
__swi(0x24) void my_swi (void);
這樣當調用函數my_swi的時候,就會用"SWI 0x24"來代替普通的函數調
用"BL my_swi".
分析圖9的流程,可以發現軟件中斷同樣存在著中斷分支的問題,即需要根
據中斷號碼來決定調用不同的處理程序.軟中斷號碼只存在于SWI指令碼當中,
因此需要在中斷處理程序中讀取觸發中斷的指令代碼,然后提取中斷號信息,再


SWI 0x01


用戶程序(C或匯編)

CMP swi_num
BEQ …
(Optional)
異常向量表 SWI處理程序(匯編)
SWI處理程序(C)
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
進行進一步處理.下面是軟中斷指令的編碼格式:
ARM狀態下的SWI指令編碼格式,32位長度,其中低24位是中斷編號.
Thumb狀態下的SWI指令編碼格式,16位長度,其中低8位是中斷編號.
圖-10 SWI指令編碼格式
為了在中斷處理程序里面得到SWI 指令的地址,可以利用LR寄存器.每
當響應一次SWI的時候,處理器都會自動保存并調整LR寄存器,使里面的內
容指向SWI下一條指令的地址,所以把LR里面的地址內容上溯一條指令就是
所需的SWI指令地址.需要注意的一點是當SWI指令的執行狀態不同時,其指
令地址間隔不一樣,如果進入SWI執行前是在ARM狀態下,需要通過LR-4來
獲得SWI指令地址,如果是在Thumb狀態下進入,則只要LR-2就可以了.
下面是一段提取SWI中斷號碼的例程:
MRS R0, SPSR ;檢查進入SWI響應前的狀態
TST R0, #T_bit ;是ARM還是Thumb #T_bit=0x20
LDRNEH R0, [LR, #-2] ;是Thumb,讀回SWI指令碼
BICNE R0, R0, #0xff00 ;提取低8位
LDREQ R0, [LR, #-4] ;是ARM,讀回SWI指令碼
BICEQ R0, R0, #0xff000000 ;提取低24位
;寄存器R0中的內容是正確的軟中斷編號了
3.可重入中斷設計
如2.1節所述,缺省情況下ARM中斷是不可重入的,因為一旦進入異常響
應狀態,ARM自動關閉中斷使能.如果在異常處理過程中簡單地打開中斷使能
而發生中斷嵌套,顯然新的異常處理將破壞原來的中斷現場而導致出錯.但有時
候中斷的可重入又是需要的,因此要能夠通過程序設計來解決這個問題.其中有
二點是這個問題的關鍵:
(a) 新中斷使能之前必須要保護好前一個中斷的現場信息,比如LR_irq和
SPSR_irq等,這一點容易想到也容易做到.
(b) 中斷處理過程中對BL的保護
28 24 27
SWI number
23
15 8 7 0
1 1 0 1 1 1 1 1 SWI number
31
Cond 1 1 1 1
0
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
在中斷處理函數中發生函數調用(BL)是很常見的,假設有下面一種情況:
IRQ_Handler:

BL Foo -----------> Foo:
ADD … STMFD SP!, {R0-R3, LR}
… …
LDMFD SP!, {R0-R3, PC}
上述程序,在IRQ處理函數IRQ_Handler() 中調用了函數Foo(),這是一個
普通的異常處理函數.但若是在IRQ_Handler() 里面中斷可重入的話,則可能發
生問題,考察下面的情況:
當新的中斷請求恰好在"BL Foo"指令執行完成后發生.
這時候LR寄存器(因在IRQ模式下,是LR_irq)的值將調整為BL指令的
下一條指令(ADD)地址,以期能從Foo() 正確返回;但是因為這時候發生了
中斷請求,接下去要進行新中斷的響應,處理器為了能使新中斷處理完成后能正
確返回,也將進行LR_irq保存.因為新中斷是在指令流
BL Foo --> STMFD SP!, {R0-R3, LR}
執行過程中插入的,完成跳轉操作后,進行流水線刷新,最后LR_irq保存的是
STMFD后面一條指令的地址;這樣當新中斷利用(PC = LR - 4)操作返回時,
正好可以繼續原來的流程執行STMFD指令.這二次對LR_irq的操作發生了沖
突,當新中斷返回后往下執行STMFD指令,這時候壓棧的LR已不是原來需要
的ADD指令的地址,從而使子程序Foo() 無法正確返回.
這個問題無法通過增加額外的現場保護指令來解決.一個巧妙的辦法是在重
新使能中斷之前改變處理器的模式,也就是使上面程序中的"BL Foo"指令不
要運行在IRQ模式下.這樣當新中斷發生時就不會造成LR寄存器的沖突了.考
慮ARM的所有運行模式,采用System模式是最恰當的,因為它既是特權模式,
又與中斷響應無關.
所以一個完整的可重入中斷應該遵循以下流程:
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
圖-11 可重入中斷處理流程
下面是一段實現的例程:
保護寄存器:LR,SPSR等
與中斷控制器通信(需要的話)
切換到System狀態,開中斷使能
中斷處理(現在中斷可重入)
關閉中斷使能,切換回IRQ狀態
恢復寄存器:PC,CPSR等
進入普通不可重入中斷處理
結束一次可重入中斷處理
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
基于ARM的嵌入式系統程序開發要點(五)
—— ARM/Thumb的交互工作
在前面的文章中提到過,很多情況下應用程序需要在ARM跟Thumb狀態之
間相互切換,這部分就討論交互工作的實現方法和一些注意問題.
1. 需要交互的原因
前面提到過Thumb指令在某些特殊情況下具有比ARM指令更為出色的表
現,主要是在代碼長度和窄帶寬存儲器系統性能兩方面.正因為Thumb指令在
特定環境下面的優勢,它在很多方面得到了廣泛的應用.但是因為下面一些原因,
Thumb又不可能獨立地組成一個應用系統,所以不可避免地會產生ARM與
Thumb之間交互的問題.
Thumb指令集在功能上只是ARM指令集的一個子集,某些功能只能在
ARM狀態下執行,如CPSR和協處理器的訪問.
進行異常響應時,處理器會自動進入ARM狀態.
從系統優化考慮,在寬帶存儲器上不應該放置Thumb代碼,很多窄帶
系統具有寬帶的內部存儲器.
即使是一個單純的Thumb應用系統,也必須加一個匯編的交互頭程序,
因為系統總是自動從ARM開始啟動.
2. 狀態切換的實現
處理器在ARM/Thumb之間的狀態切換是通過一條專用的跳轉交換指令BX
來實現的.BX指令以通用寄存器(R0-R15)為操作數,通過拷貝Rn到PC來
實現4GB空間范圍內的一個絕對跳轉. BX利用Rn寄存器中存儲的目標地址值
的最后一位來判斷跳轉后的狀態.
圖-1 BX指令實現狀態切換
0 31
Rn
PC
BX
ARM/Thumb選擇位:
0 - ARM
1 - Thumb
BX Rn
當前狀態是Thumb時
BX{Cond.} Rn
當前狀態是ARM時
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
無論ARM還是Thumb,其指令存儲在存儲器中都是邊界對齊的(4-Byte或
2-Byte對齊),所以在執行跳轉過程中,PC寄存器中的最低位肯定被舍棄,不起
作用.在BX指令的執行過程中,最低位正好被用作狀態判斷的標識,不會造成
存儲器訪問不對齊的錯誤.
圖2中是一段直接進行狀態切換的例程:
圖-2 ARM/Thumb交互工作的例子
我們知道ARM的狀態寄存器CPSR中,bit-5是狀態控制位T-bit,決定當前
處理器的運行狀態.如果直接修改CPSR的狀態位,也能夠達到改變處理器運行
狀態的目的,但是會帶來一個問題.因為ARM采用了多級流水線的結構,所以
在程序執行過程中指令流水線上會存在幾條預取指令(具體數目視流水線級數而
不同).當修改CPSR的T-bit以后,狀態的轉變會造成流水線上預取指令執行的
錯誤.而如果用BX指令,則執行后會進行流水線刷新動作,清除流水線上的殘
余指令,在新的狀態下重新開始指令預取,從而保證狀態轉變時候指令流的正確
銜接.
3. ARM/Thumb之間的函數調用
在無交互的子程序調用中,其過程比較簡單.實現調用通常只需要一條指
令:
BL function
實現返回也只需要從LR恢復PC即可:
MOV PC, LR
;從ARM狀態開始
CODE32 ;匯編關鍵字
ADR R0, Into_Thumb+1 ;得到目標地址,末位置1,轉向Thumb
BX R0 ;執行
… ;其他代碼
CODE16 ;匯編關鍵字
Into_Thumb ;Thumb代碼段起始地址
… ;Thumb代碼
ADR R5, Back_to_ARM ;得到目標地址,末位缺省為0,轉向ARM
BX R5 ;執行
… ;其他代碼
CODE32 ;匯編關鍵字
Back_to_ARM ;ARM代碼段起始地址

基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
如下圖所示:
圖-3 普通函數調用
如果子函數和父函數不是在同一種狀態下執行的,因為狀態切換,需要對
函數調用作更多的考慮.
(a) BL不能完成狀態切換,需要由BX來切換狀態.
(b) BX不能自動保存返回地址到LR,需要在BX之前先保存好LR.
(c) 用"BX LR"來返回,不能使用"MOV PC, LR",因為這條指令同
樣不能實現狀態切換.返回時要仔細考慮保存的LR中最低位內容是否
正確.
如果用戶直接使用匯編進行狀態交互跳轉,上述的幾個問題都需要用手工
編碼加以處理.如果用戶使用高級語言進行開發,不需要為ARM/Thumb之間的
相互調用增加額外的編碼,但是最好要對其調用過程加以了解.下面以ARM ADS
中的編譯工具為例進行說明(圖4).
(a) 兩個函數func1()和func2()被編譯成了不同的指令集(ARM或Thumb).
注意func1()和func2()在這里位于二個不同的源文件.
(b) 編譯時必須告訴編譯器和連接器足夠的信息,一方面讓編譯器能夠使用
正確的指令碼進行編譯,另一方面這樣當在不同的狀態之間發生函數調
用時,連接器將插入一段連接代碼(veneers)來實現狀態轉換.
圖-4 不同狀態間函數調用的示例
func1
連接器生成
連接代碼
File2.c File1.c
Void func1(void)
{

func2();

}
.
.
.
BL
.
.
.
.
.
BX
func2
. .
.
BX
Void func2(void)
{


}
func2
func1
Void func1(void)
{

func2();

}
.
.
.
BL func2
.
.
.
.
.
MOV PC, LR
基于ARM的嵌入式程序開發要點
ARM-CHINA-040415A
上述過程中的一個特點是func1還是使用通常的BL指令來進行子程序調用,
而func2返回時則直接使用"BX LR",沒有對LR進行判斷和最低位的設置.
這是因為當執行BL指令對LR進行保存時,其最低位會被自動設置,以滿足返
回時狀態切扼/td>

用并且不推薦使用這
些編號。
半主機SWI 使用的軟件中斷編號也可以由用戶自定義,但若是改變了缺省
的軟中斷編號,需要:
?? 更改系統中所有代碼(包括庫代碼)的半主機SWI 調用
?? 重新配置調試器對半主機請求的捕捉與相應
這樣才能使用新的SWI 編號。
有關半主機SWI 處理函數實現的更詳細信息,請參考ARM 編譯器的相關
文檔。
4.應用環境的初始化和根據目標系統資源進行的移植
在下一期中介紹應用環境和目標系統的初始化。
 
 
基于s3c2410軟中斷服務的uC/OS-II任務切換
 
 
 
1.關于軟中斷指令
  軟件中斷指令(SWI)可以產生一個軟件中斷異常,這為應用程序調用系統例程提供了一種機制。
語法:
       SWI   {<cond>}  SWI_number
SWI執行后的寄存器變化:
lr_svc = SWI指令后面的指令地址
spsr_svc = cpsr
pc = vectors + 0x08
cpsr模式 = SVC
cpsr I = 1(屏蔽IRQ中斷)
 
   處理器執行SWI指令時,設置程序計數器pc為向量表的0x08偏移處,同事強制切換處理器模式到SVC模式,以便操作系統例程可以在特權模式下被調用。
   每個SWI指令有一個關聯的SWI號(number),用于表示一個特定的功能調用或特性。
【例子】 一個ARM工具箱中用于調試SWI的例子,是一個SWI號為0x123456的SWI調用。通常SWI指令是在用戶模式下執行的。
SWI執行前:
    cpsr = nzcVqift_USER
    pc = 0x00008000
    lr = 0x003fffff   ;lr = 4
    r0 = 0x12
 
執行指令:
    0x00008000   SWI    0x123456
 
SWI執行后:
    cpsr = nzcVqIft_SVC
    spsr = nzcVqift_USER
    pc = 0x00000008
    lr = 0x00008004
    r0 = 0x12
   SWI用于調用操作系統的例程,通常需要傳遞一些參數,這可以通過寄存器來完成。在上面的例子中,r0
用于傳遞參數0x12,返回值也通過寄存器來傳遞。
   處理軟件中斷調用的代碼段稱為中斷處理程序(SWI Handler)。中斷處理程序通過執行指令的地址獲取軟件中斷號,指令地址是從lr計算出來的。
   SWI號由下式決定:
   SWI_number = <SWI instruction> AND NOT<0xff000000>
   其中SWI instruction就是實際處理器執行的32位SWI指令
 
   SWI指令編碼為:
   31 - 28  27 - 24  23 - 0
     cond   1 1 1 1  immed24
   指令的二進制代碼的bit23-bit0是24bit的立即數,即SWI指令的中斷號,通過屏蔽高8bit即可獲得中斷號。lr寄存器保存的是中斷返回指令的地址,所以 [lr - 4] 就是執行SWI的執行代碼。通過load指令拷貝整個SWI指令到寄存器,使用BIC屏蔽指令的高8位,獲取SWI中斷號。
  
    ;read the SWI instruction
    LDR  r10, [lr, #-4]
    BIC  r10, r10, #0xff000000
 
2. 周立功移植uC/OS-II到s3c2410的軟中斷服務級的任務切換
uC/OS-II的任務調度函數
   uC/OS-II的任務級的調度是由函數OS_Sched( )完成的。
 
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
    OS_CPU_SR cpu_sr;
#endif
    INT8U y;

    OS_ENTER_CRITICAL();
    if ((OSIntNesting == 0) && (OSLockNesting == 0)) { /* Sched. only if all ISRs done & not locked */
        y = OSUnMapTbl[OSRdyGrp]; /* Get pointer to HPT ready to run */
        OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
        if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
            OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
            OSCtxSwCtr++; /* Increment context switch counter */
            OS_TASK_SW(); /* Perform a context switch */
        }
    }
    OS_EXIT_CRITICAL();
}
 

   詳細解釋可以參考《嵌入式實時操作系統 uC/OS-II》,os_sched函數在確定所有就緒任務的最高優先級高于當前任務優先級時進行任務切換,通過OS_TASK_SW( )宏來調用。
   OS_TASK_SW( )宏實際上定義的是SWI軟中斷指令。見OS_CPU.H文件的代碼:

__swi(0x00) void OS_TASK_SW(void); /* 任務級任務切換函數 */
__swi(0x01) void _OSStartHighRdy(void); /* 運行優先級最高的任務 */
__swi(0x02) void OS_ENTER_CRITICAL(void); /* 關中斷 */
__swi(0x03) void OS_EXIT_CRITICAL(void); /* 開中斷 */

__swi(0x40) void *GetOSFunctionAddr(int Index); /* 獲取系統服務函數入口 */
__swi(0x41) void *GetUsrFunctionAddr(int Index);/* 獲取自定義服務函數入口 */
__swi(0x42) void OSISRBegin(void); /* 中斷開始處理 */
__swi(0x43) int OSISRNeedSwap(void); /* 判斷中斷是否需要切換 */

__swi(0x80) void ChangeToSYSMode(void); /* 任務切換到系統模式 */
__swi(0x81) void ChangeToUSRMode(void); /* 任務切換到用戶模式 */
__swi(0x82) void TaskIsARM(INT8U prio); /* 任務代碼是ARM代碼 */
__swi(0x83) void TaskIsTHUMB(INT8U prio); /* 任務代碼是THUMB */
 

__swi(0x00) void OS_TASK_SW(void); 是與ADS相關的代碼,通過反匯編可以看到,調用OS_TASK_SW實際上被替換成swi 0x00 軟中斷指令。執行此執行,pc會跳轉到向量表的0x08偏移處。

中斷向量表:(見Startup.s文件)

CODE32
        AREA vectors,CODE,READONLY
; 異常向量表
Reset
        LDR PC, ResetAddr
        LDR PC, UndefinedAddr
        LDR PC, SWI_Addr
        LDR PC, PrefetchAddr
        LDR PC, DataAbortAddr
        DCD IRQ_Addr
        LDR PC, IRQ_Addr
        LDR PC, FIQ_Addr

ResetAddr     DCD ResetInit
UndefinedAddr DCD Undefined
SWI_Addr      DCD SoftwareInterrupt
PrefetchAddr  DCD PrefetchAbort
DataAbortAddr DCD DataAbort
Nouse         DCD 0
IRQ_Addr      DCD IRQ_Handler
FIQ_Addr      DCD FIQ_Handler
 

執行SWI 0x00指令后,pc會跳轉到SoftwareInterrupt代碼處開始執行:

見Os_cpu_a.s文件的SoftwareInterrupt函數:

 

SoftwareInterrupt
        LDR SP, StackSvc ; 重新設置堆棧指針
        STMFD {R0-R3, R12, LR}
        MOV R1, SP ; R1指向參數存儲位置

        MRS R3, SPSR
        TST R3, #T_bit ; 中斷前是否是Thumb狀態
        LDRNEH R0, [LR,#-2] ; 是: 取得Thumb狀態SWI指令
        BICNE R0, R0, #0xff00
        LDREQ R0, [LR,#-4] ; 否: 取得arm狀態SWI指令
        BICEQ R0, R0, #0xFF000000    ; 如上面所述,此處通過屏蔽SWI指令的高8位來獲取SWI號,r0 = SWI號,R1指向參數存儲位置
        CMP R0, #1
        LDRLO PC, =OSIntCtxSw  ;為0時跳轉到OSIntCtxSwdi地址處
        LDREQ PC, =__OSStartHighRdy ; 為1時,跳轉到__OSStartHighRdy地址處。SWI 0x01為第一次任務切換

        BL SWI_Exception  ;進入中斷號散轉函數
       
        LDMFD {R0-R3, R12, PC}^
       
StackSvc DCD (SvcStackSpace + SVC_STACK_LEGTH * 4 - 4)

 

以上就是任務切換軟中斷級服務的實現。
 
 淺析arm匯編中指令使用學習
本帖被 smd801124 設置為精華(2008-05-08)
macro restore_user_regs
  ldr r1,[sp, #S_PSR]
  ldr lr,[sp, #S_PC]!  @ !用來控制基址變址尋址的最終新地址是否進行回寫操作,
                      @ 執行ldr之后sp被回寫成sp+#S_PC基址變址尋址的新地址
  msr spsr,r1          @ 把cpsr的值保存到spsr中
  ldmdb sp,{r0 - lr}^  @ lr=[sp-1*4],r13=[sp-2*4],r12=[sp-3*4],......,r0=[sp-15*4]
                      @ 因為沒對pc賦值,所以^的表示將數據恢復到User模式的[r0-lr]寄存器組中[gliethttp]
  mov r0,r0
  add sp,sp,#S_FRAME_SIZE - S_PC
  movs pc,lr
.endm

其他指令正在學習中[隨時補充gliethttp]
-----------------------------
1.ldr ip,[sp],#4 將sp中內容存入ip,之后sp=sp+4;
  ldr ip,[sp,#4] 將sp+4這個新地址下內容存入ip,之后sp值保持不變
  ldr ip,[sp,#4]!將sp+4這個新地址下內容存入ip,之后sp=sp+4將新地址值賦給sp
  str ip,[sp],#4 將ip存入sp地址處,之后sp=sp+4;
  str ip,[sp,#4] 將ip存入sp+4這個新地址,之后sp值保持不變
  str ip,[sp,#4]!將ip存入sp+4這個新地址,之后sp=sp+4將新地址值賦給sp
-----------------------------
2.movs r1,#3 ;movs將導致ALU被更改,因為r1賦值非0,即操作結果r0非0,所以ALU的Z標志清0
  bne 1f    ;因為Z=0,說明不等,所以向前跳到標號1:所在處繼續執行其他語句
-----------------------------
3.LDM表示裝載,STM表示存儲.
  LDMED LDMIB 預先增加裝載
  LDMFD LDMIA 過后增加裝載
  LDMEA LDMDB 預先減少裝載
  LDMFA LDMDA 過后減少裝載

  STMFA STMIB 預先增加存儲
  STMEA STMIA 過后增加存儲
  STMFD STMDB 預先減少存儲
  STMED STMDA 過后減少存儲

注意ED不同于IB;只對于預先減少裝是相同的.在存儲的時候,ED是過后減少的.
FD、ED、FA、和 EA 指定是滿棧還是空棧,是升序棧還是降序棧.
對于存儲STM而言
先加后存 FA 姑且這么來記,先加(first add),存數據
后加先存 EA 姑且這么來記,存數據,后加end add
先減后存 FD 姑且這么來記,先減first dec,存數據
后減先存 ED 姑且這么來記,存數據,后減end dec
然后記憶LDM,LDM是STM的反相彈出動作,所以
因為是先加后存,所以后減先取 FA 就成了與STM對應的取數據,后減
因為是后加先存,所以先減后取 EA 就成了與STM對應的先減,取數據
因為是先減后存,所以后加先取 FD 就成了與STM對應的取數據,后加
因為是后減先存,所以先加后取 ED 就成了與STM對應的先加,取數據
我想通過上面的變態方式可以比較容易的記住這套指令[gliethttp]
一個滿棧的棧指針指向上次寫的最后一個數據單元,而空棧的棧指針指向第一個空閑單元.
一個降序棧是在內存中反向增長(就是說,從應用程序空間結束處開始反向增長)而升序棧在內存中正向增長.
其他形式簡單的描述指令的行為,意思分別是
IA過后增加(Increment After)、
IB預先增加(Increment Before)、
DA過后減少(Decrement After)、
DB預先減少(Decrement Before).

RISC OS使用傳統的滿降序棧.在使用符合APCS規定的編譯器的時候,它通常把你的棧指針設置在應用程序空間的
結束處并接著使用一個FD(滿降序-Full Descending)棧.如果你與一個高級語言(BASIC或C)一起工作,你將別無選擇.
棧指針(傳統上是R13)指向一個滿降序棧.你必須繼續這個格式,或則建立并管理你自己的棧.

4.
teq r1,#0    //r1-0,將結果送入狀態標志,如果r1和0相減的結果為0,那么ALU的Z置位,否則Z清0
bne reschedule//ne表示Z非0,即:不等,那么執行reschedule函數
-----------------------------
5.使用tst來檢查是否設置了特定的位
tst r1,#0x80 //按位and操作,檢測r1的0x1<<7,即第7位是否置1,按位與之后結果為0,那么ALU的Z置位
beq reset    //如果Z置位,即:以上按位與操作結果是0,那么跳轉到reset標號執行
-----------------------------
6.'^'的理解
'^'是一個后綴標志,不能在User模式和Sys系統模式下使用該標志.該標志有兩個存在目的:
6.1.對于LDM操作,同時恢復的寄存器中含有pc(r15)寄存器,那么指令執行的同時cpu自動將spsr拷貝到cpsr中
如:在IRQ中斷返回代碼中[如下為ads環境下的代碼gliethttp]
ldmfd {r4}          //讀取sp中保存的的spsr值到r4中
msr spsr_cxsf,r4    //對spsr的所有控制為進行寫操作,將r4的值全部注入spsr
ldmfd {r0-r12,lr,pc}^//當指令執行完畢,pc跳轉之前,將spsr的值自動拷貝到cpsr中[gliethttp]
6.2.數據的送入、送出發生在User用戶模式下的寄存器,而非當前模式寄存器
如:ldmdb sp,{r0 - lr}^;表示sp棧中的數據回復到User分組寄存器r0-lr中,而不是恢復到當前模式寄存器r0-lr  當然對于User,System,IRQ,SVC,Abort,Undefined這6種模式來說[gliethttp]r0-r12是共用的,只是r13和r14
  為分別獨有,對于FIQ模式,僅僅r0-r7是和前6中模式的r0-r7共用,r8-r14都是FIQ模式下專有.
7.spsr_cxsf,cpsr_cxsf的理解
c - control field mask byte(PSR[7:0])
x - extension field mask byte(PSR[15:8])
s - status field mask byte(PSR[23:16)
f - flags field mask byte(PSR[31:24]).
老式聲明方式:cpsr_flg,cpsr_all在ADS中已經不在支持
cpsr_flg對應cpsr_f
cpsr_all對應cpsr_cxsf

需要使用專用指令對cpsr和spsr操作:mrs,msr
mrs tmp,cpsr      //讀取CPSR的值
bic tmp,tmp,#0x80 //如果第7位為1,將其清0
msr cpsr_c,tmp    //對控制位區psr[7:0]進行寫操作
-----------------------------
8.cpsr的理解
CPSR = Current Program Status Register
SPSR = Saved Program Status Registers
CPSR寄存器(和保存它的SPSR寄存器)
本文來自: 電子論壇[url]http://www.eehome.cn[/url]電子工程師之家!

 

 

 

 

 

 

 

 

 

ARM的堆棧學習筆記
來源:http://www.hzlitai.com.cn/   作者:藍石頭
字體大小:[大][中][小]

  以下是我在學習ARM指令中記錄的關于堆棧方面的知識:

  1、寄存器 R13 在 ARM 指令中常用作堆棧指針

  2、對于 R13 寄存器來說,它對應6個不同的物理寄存器,其中的一個是用戶模式與系統模式共用,另外5個物理寄存器對應于其他5種不同的運行模式。采用以下的記號來區分不同的物理寄存器: R13_<mode> 其中,mode為以下幾種模式之一:usr、fiq、irq、svc、abt、und。

  3、寄存器R13在ARM指令中常用作堆棧指針,但這只是一種習慣用法,用戶也可使用其他的寄存器作為堆棧指針。而在Thumb指令集中,某些指令強制性的要求使用R13作為堆棧指針。由于處理器的每種運行模式均有自己獨立的物理寄存器R13,在用戶應用程序的初始化部分,一般都要初始化每種模式下的R13,使其指向該運行模式的棧空間,這樣,當程序的運行進入異常模式時,可以將需要保護的寄存器放入R13所指向的堆棧,而當程序從異常模式返回時,則從對應的堆棧中恢復,采用這種方式可以保證異常發生后程序的正常執行。

  4、有四種類型的堆棧:

  堆棧是一種數據結構,按先進后出(First In Last Out,FILO)的方式工作,使用一個稱作堆棧指針的專用寄存器指示當前的操作位置,堆棧指針總是指向棧頂。

  當堆棧指針指向最后壓入堆棧的數據時,稱為滿堆棧(Full Stack),而當堆棧指針指向下一個將要放入數據的空位置時,稱為空堆棧(Empty Stack)。

  同時,根據堆棧的生成方式,又可以分為遞增堆棧(Ascending Stack)和遞減堆棧(DecendingStack),當堆棧由低地址向高地址生成時,稱為遞增堆棧,當堆棧由高地址向低地址生成時,稱為遞減堆棧。這樣就有四種類型的堆棧工作方式,ARM 微處理器支持這四種類型的堆棧工作方式,即: ◎ Full descending 滿遞減堆棧堆棧首部是高地址,堆棧向低地址增長。棧指針總是指向堆棧最后一個元素(最后一個元素是最后壓入的數據)。 ARM-Thumb過程調用標準和ARM、Thumb C/C++ 編譯器總是使用Full descending 類型堆棧。

  ◎ Full ascending 滿遞增堆棧堆棧首部是低地址,堆棧向高地址增長。棧指針總是指向堆棧最后一個元素(最后一個元素是最后壓入的數據)。

  ◎ Empty descending 空遞減堆棧堆棧首部是低地址,堆棧向高地址增長。棧指針總是指向下一個將要放入數據的空位置。

  ◎ Empty ascending 空遞增堆棧堆棧首部是高地址,堆棧向低地址增長。棧指針總是指向下一個將要放入數據的空位置。

  5、操作堆棧的匯編指令堆棧類型 入棧指令 出棧指令 Full descending STMFD (STMDB) LDMFD (LDMIA) Full ascending STMFA (STMIB) LDMFA (LDMDA) Empty descending STMED (STMDA) LDMED (LDMIB) Empty ascending STMEA (STMIA) LDMEA (LDMDB)

  例子: STMFD r13!, {r0-r5} ; Push onto a Full Descending Stack LDMFD r13!, {r0-r5} ; Pop from a Full Descending Stack.
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/IT_yulei_3g/archive/2008/06/08/2524842.aspx

七星彩走势图2元网官网