超薄灯箱制作材料:ARM汇编学习笔记

来源:百度文库 编辑:九乡新闻网 时间:2024/04/29 15:40:28

ARM汇编学习笔记     2008-06-8 07:01:05

大 中 小
标签:IT/科技
    这两天参加了一个编写操作系统的项目,因为要做很多底层的东西,而且这个操作系统是嵌入式的,所以开始学习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   {}  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 = 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   {}  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 = 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为以下几种模式之一: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.