第三章-Cortex-A8处理器编程(下)

ARM 188浏览

六.指令系统

Cortex-A8采用ARMv7架构,包含:

1)      32位的ARM指令集

2)      16位和32位混合的Thumb-2指令集

3)      ThumbEE指令集

主要内容包括:

(一).指令格式

ARM指令的基本格式如下:<opcode> {<cond>} {S}    <Rd> ,<Rn>{,<operand2>}

其中<>号内的项是必须的,{}号内的项是可选的。各项的说明如下:

Ø  opcode:指令助记符;

Ø  cond:执行条件;

Ø  S:是否影响CPSR寄存器的值;

Ø  Rd:目标寄存器;                 

Ø  Rn:第1个操作数的寄存器;

Ø  operand2:第2个操作数;能够提高代码效率。它有如下的形式:

#immed_8r——常数表达式;该常数必须对应8位位图,即一个8位的常数通过循环右移偶数位得到。

MOV         R0,#1

AND R1,R2,#0x0F

Rm——寄存器方式;在寄存器方式下,操作数即为寄存器的数值。

SUB  R1, R1, R2

MOV         PC, R0

Rm,shift——寄存器移位方式;将寄存器的移位结果作为操作数,但Rm值保持不变,移位方法如下:

ADD R1,R1,R1,LSL #3        ;R1=R1+R1*8=9R1

SUB R1,R1,R2,LSR R3       ;R1=R1-(R2/2R3)                 

(二).ARM指令集

32位ARM指令集由13种基本指令类型组成,分成四大类:

1)        3种类型的存储器访问指令。用于控制存储器和寄存器之间的数据传送。第一种类型用于优化的灵活寻址及交换数据;第二种类型用于快速上下文切换;第三种类型用于交换数据。

2)        3种类型的数据处理指令,使用片内的累加器(ALU)、桶形移位器和乘法器,对寄存器完成高速数据处理操作。

3)        4种类型的分支指令,用于控制程序执行流程、指令优先级、ARM代码和Thumb代码的切换

4)        3种类型的协处理器指令,专用于控制外部协处理器。

1.      条件码

<opcode>{<cond>} {S}    <Rd>,<Rn>{,<operand2>}

使用条件码“cond”可以实现高效的逻辑操作,提高代码效率。

所有的ARM指令都可以条件执行,而Thumb指令只有B(跳转)指令具有条件执行功能。如果指令不标明条件代码,将默认为无条件(AL)执行。

指令条件吗:

实例:

2.      存储器访问指令

(1)、基本指令:LDR|STR|LDM|STM {<cond>} {B} {T}<Rd>, <addressing_mode>

存储器访问指令分为单寄存器操作指令多寄存器操作指令

(2)、ARM存储器访问指令——单寄存器加载

所有单寄存器加载/存储指令可分为“字和无符号字节加载存储指令”“半字和有符号字节加载存储指令”。

LDR/STR指令用于对内存变量的访问、内存缓冲区数据的访问、查表、外围部件的控制操作等。若使用LDR指令加载数据到PC寄存器,则实现程序跳转功能,就实现了程序散转。LDR/STR指令是寄存器加载/存储指令后缀B表示字节操作,后缀H表示半字操作

LDR和STR指令应用示例:(P55)

1)        加载/存储字和无符号字节指令

LDR   R2,[R5]         ;将R5指向地址的字数据存入R2

STR    R1,[R0,#0x04]   ;将R1的数据存储到R0+0x04地址,若有!,则R0就要更新

LDRB R3,[R2],#1  ;将R2指向地址的字节数据存入R3,R2=R2+1

STRB R6,[R7]       ;将R7指向地址的字节数据存入R6

2)        加载/存储半字和有符号字节指令

LDRSB R1,[R0,R3]     ;将R0+R3地址上的字节数据存入R1,

                    ;高24位用符号扩展

LDRH R6,[R2],#2  ;将R2指向地址的半字数据存入R6,高16位用0扩展

                    ;读出后,R2=R2+2

STRH R1,[R0,#2]! ;将R1的半字数据保存到R0+2地址,

                    ;只修改低2字节数据,R0=R0+2

(3)、ARM存储器访问指令——多寄存器存取

LDM为加载多个寄存器;STM为存储多个寄存器。允许一条指令传送16个寄存器的任何子集或所有寄存器。它们主要用于现场保护、数据复制、常数传递等。

(4)、多寄存器加载/存储指令格式如下:

LDM{cond}<模式> Rn{!},reglist{^}

STM{cond}<模式> Rn{!},reglist{^}

模式:控制地址的增长方式,一共有8种模式;

!:表示在操作结束后,将最后的地址写回Rn中;

reglist :表示寄存器列表,可以包含多个寄存器,它们使用“,”隔开,如{R1,R2,R6-R9},寄存器由小到大排列;

^:加入该后缀后,进行数据传送且寄存器列表不包含PC时,加载/存储的寄存器是用户模式下的,而不是当前模式的寄存器。若在LDM指令且寄存器列表中包含有PC时使用,那么除了正常的多寄存器传送外,还将SPSR也拷贝到CPSR中,这可用于异常处理返回。注意:该后缀不允许在用户模式或系统模式下使用。

右边四种为堆栈操作、左边四种为数据传送操作。

(Iàincrease,Aàafter,Bàbefore)

进行数据复制时,先设置好源数据指针和目标指针,然后使用块拷贝寻址指令LDMIA/STMIA、LDMIB/STMIB、LDMDA/STMDA、LDMDB/STMDB进行读取和存储 。

进行堆栈操作时,要先设置堆栈指针(SP),然后使用堆栈寻址指令STMFD/LDMFD 、STMED/LDMED、STMFA/LDMFA和STMEA/LDMEA实现堆栈操作。

举例(P56)

3.      数据处理指令

数据处理指令只能对寄存器的内容进行操作,而不能对内存中的数据进行操作。所有ARM数据处理指令均可选择使用S后缀,并影响状态标志。

ARM数据处理指令包括以下几种:

1)        数据传送指令(P56-59)

a)        MOV指令

    MOV{cond}{S}    Rd,operand2

例:

MOV R1,#0x10          ;R1=0x10

 MOV        R0,R1                          ;R0=R1

 MOVS      R3,R1,LSL #2    ;R3=R1<<2,并影响标志位

 MOV        PC,LR                          ;PC=LR,子程序返回

 

b)        MVN指令

MVN{cond}{S}    Rd,operand2

例:

    MVN R1,#0xFF           ;R1=0xFFFFFF00

    MVN R1,R2                          ;将R2取反,结果存到R1

2)        算术逻辑运算指令

a)        ADD指令

ADD{cond}{S}    Rd,Rn,operand2

例:

ADDS   R1,R1,#1           ;R1=R1+1,并影响标志位

ADD    R1,R1,R2          ;R1=R1+R2    

b)        ADC指令

   ADC{cond}{S}    Rd,Rn,operand2

例:

ADDS   R0,R0,R2         ;R0等于低32位相加,并影响标志位

ADC    R1,R1,R3 ;R1等于高32位相加,并加上低位进位

c)        SUB指令

SUB{cond}{S}    Rd,Rn,operand2

例:

SUBS R0,R0,#1           ;R0=R0-1 ,并影响标志位

SUBS R2,R1,R2          ;R2=R1-R2 ,并影响标志位

d)        SBC指令

SBC{cond}{S}    Rd,Rn,operand2

例:

SUBS   R0,R0,R2          ; 低32位相减,并影响标志位(有借为0,无借为1)

SBC    R1,R1,R3  ;高32位相减,并减去低位借位

e)        RSB指令

RSB{cond}{S}    Rd,Rn,operand2

例:

    RSB    R3,R1,#0xFF00 ;R3=0xFF00-R1

     RSBS  R1,R2,R2,LSL #2        ;R1=(R2<<2)-R2=R2×3

f)         RSC指令

RSC{cond}{S}    Rd,Rn,operand2

例:

RSBS   R2,R0,#0

RSC    R3,R1,#0

g)        AND指令

AND{cond}{S}    Rd,Rn,operand2

例:

ANDS R0,R0,#0x01 ;R0=R0&0x01,取出最低位数据

AND  R2,R1,R3       ;R2=R1&R3

h)        ORR指令

ORR{cond}{S}    Rd,Rn, operand2

例:

MOV R1,R2,LSR#24         ;使用ORR指令将R2的高8位

ORR  R3,R1,R3,LSL#8       ;数据移入到R3低8位中

ORR  R0,R0,#0x0F           ;将R0的低4位置1

i)          EOR指令

EOR{cond}{S}    Rd,Rn, operand2

例:

EOR   R1,R1,#0x0F              ;将R1的低4位取反

EOR R2,R1,R0            ;R2=R1^R0

EORS R0,R5,#0x01  ; 将R5和0x01进行逻辑异或;结果保存到R0,并影响标志位

j)          RIC指令

BIC{cond}{S}    Rd,Rn, operand2

例:

BIC R1,R1,#0x0F         ;将R1的低4位清零,其它位不变

BIC    R1,R2,R3          ;将R3的反码和R2相逻辑“与”;结果保存到R1中

3)        比较指令(P59-60)

比较指令不保存运算结果,只更新CPSR中相应的条件标志位。

a)        CMP指令

CMP{cond}         Rn, operand2

例:

CMP  R1,#10   ; R1与10比较,设置相关标志位

CMP  R1,R2                 ; R1与R2比较,设置相关标志位 

CMP指令与SUBS指令的区别在于CMP指令不保存运算结果。在进行两个数据的大小判断时,常用CMP指令及相应的条件码来操作。

b)        CMN指令

CMN{cond}        Rn, operand2

例:

    CMN R0,#1   ;R0+1,判断R0是否为1的补码 ; R0是否-1如果是,则设置Z标志位

CMN指令与ADDS指令的区别在于CMN指令不保存运算结果。CMN指令可用于负数比较,比如CMN R0,#1指令则表示R0与-1比较,若R0为-1(即1的补码),则Z置位;否则Z复位。

c)        TST指令

TST{cond}    Rn, operand2

例:

TST    R0,#0x01         ;判断R0的最低位是否为0

TST    R1,#0x0F           ; 判断R1的低4位是否为0

TST指令与ANDS指令的区别在于TST指令不保存运算结果。TST指令通常与EQ、NE条件码配合使用,当所有测试位均为0时,EQ有效,而只要有一个测试位不为0,则NE有效。

d)        TEQ指令

TEQ{cond}    Rn, operand2

例:

TEQ R0,R1         ;比较R0与R1是否相等 (不影响V位和C位)

TEQ指令与EORS指令的区别在于TEQ指令不保存运算结果。使用TEQ进行相等测试时,常与EQ、NE条件码配合使用。当两个数据相等时,EQ有效;否则NE有效。

4)        乘法指令

a)        MUL指令

MUL{cond}{S}    Rd,Rm,Rs

例:

MUL  R1,R2,R3     ;R1=R2×R3

MULS R0,R3,R7  ;R0=R3×R7,同时影响CPSR中的N位和Z位

b)        MLA指令

MLA{cond}{S}    Rd,Rm,Rs,Rn

例:

MLA     R1,R2,R3,R0             ; R1=R2×R3+R0

c)        SMULL指令

 SMULL{cond}{S}    RdLo,RdHi,Rm,Rs

例:

SMULL       R2,R3,R7,R6    ; (R3、R2)=R7×R6

d)        SMLAL指令

SMLAL{cond}{S}    RdLo,RdHi,Rm,Rs

例:

SMLAL R2,R3,R7,R6   ; (R3、R2)=R7×R6+(R3、R2)

e)        UMULL指令

 UMULL{cond}{S}    RdLo,RdHi,Rm,Rs

例:

UMULL   R0,R1,R5,R8 ; (R1、R0)=R5×R8

f)         UMALA指令

UMLAL{cond}{S}    RdLo,RdHi,Rm,Rs

例:

UMLAL  R0,R1,R5,R8      ;(R1、R0)=R5×R8+(R1、R0)

4.      跳转指令

a)        B跳转指令

B{cond}    Label

例;

B   WAITA                         ; 跳转到WAITA标号处

B        0x1234              ; 跳转到绝对地址0x1234处

b)        BL跳转指令

BL指令适用于子程序调用,使用该指令后,下一条指令的地址被拷贝到R14(即LR) 连接寄存器中,然后跳转到指定地址运行程序。

BL{cond}    Label

例:

BL           DELAY                          ;调用子程序DELAY

c)        BLX跳转指令

BLX <target _add>

d)        BX跳转指令

BX{cond}    Rm

例:

ADRL R0,ThumbFun+1  ;将Thumb程序入口地址加1;存入R0

BX   R0      ; 跳转到R0指定的地址;并根据R0的最低位来切换处理器状态

5.      程序状态寄存器存取指令

a)        MRS指令——状态寄存器读指令

MRS{cond}    Rd,psr

例:

MRS R1,CPSR  ; 将CPSR状态寄存器读取,保存到R1中

MRS R2,SPSR  ; 将SPSR状态寄存器读取,保存到R2中

b)        MSR指令——状态寄存器写指令

MSR{cond}      psr_fields, #immed_8r

MSR{cond}      psr_fields, Rm

例:

七.ARM汇编程序设计

ARM伪指令有四条,分别为ADR伪指令、ADRL伪指令、LDR伪指令、NOP伪指令

1.        ADR伪指令——小范围的地址读取

将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中。

ADR{cond}  register,  expr

地址表达式expr的取指范围:

当地址值不是字对齐时,其取值范围为-255~255;

当地址值是字对齐时,其取值范围为-1020~1020;

当地址值是16字节对齐时,其取值范围将更大。

2.        ADRL伪指令——中等范围的地址读取

将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中,比ADR伪指令可以读取更大范围的地址 。

ADRL{cond}  register,expr

地址表达式expr的取指范围:

当地址值不是字对齐时,其取址范围为-64K~64K;

当地址值是字对齐时,其取址范围为-256K~256K;

当地址值是16字节对齐时,其取址范围将更大。

 

3.        LDR伪指令——大范围的地址读取

用于加载32位的立即数或一个地址值到指定寄存器。

LDR{cond}  register,=expr

注:1.从指令位置到文字池的偏移量必须小于4KB;

2.与ARM指令的LDR相比,伪指令的LDR的参数有“=”号。

 

4.        NOP伪指令——空操作伪指令

在汇编时将会被代替成ARM中的空操作,比如可能是“MOV R0,R0”指令等。NOP可用于延时操作。

NOP

八.ARM汇编程序规范

1.ARM汇编程序中每一行的通用格式为:

{标号} {指令|指示符|伪指令} {;注解}

    在ARM汇编语言源程序中,除了标号和注释外,指令、伪指令和指示符都必须有前导空格,而不能顶格书写。如果每一行的代码太长,可以使用字符“”将其分行书写,并允许有空行。指令助记符、指示符和寄存器名既可以用大写字母,也可以用小写字母,但不能混用。注释从“;”开始,到该行结束为止。

2.标号

标号代表一个地址,段内标号的地址值在汇编时确定,段外标号的地址值在链接时确定。在此要区别程序相对寻址和寄存器相对寻址。在程序段中,标号代表其所在位置与段首地址的偏移量,根据程序计数器PC和偏移量计算地址称为程序相对寻址。在映像中定义的标号代表标号到映像首地址的偏移量,映像的首地址通常被赋予一个寄存器,根据该寄存器值与偏移量计算地址称为寄存器相对寻址。

3.指示符

4.与定义变量

预定义的寄存器如下:

·R0~R15或r0~r15;

·a1~a4(参数、结果或临时寄存器,与r0~r3同义);

·v1~v8(变量寄存器,与r4~r11同义);

·sb和SB(静态基址寄存器,与r9同义);

·sl和SL(堆栈限制寄存器,与r10同义);

·fp和FP(帧指针,与r11同义);

·ip和IP(过程调用中间临时寄存器,与r12同义);

·sp和SP(堆栈指针,与r13同义);

·lr和LR(链接寄存器,与r14同义);

·pc和PC(程序计数器,与r15同义);

·cpsr和CPSR(程序状态寄存器);

·spsr和SPSR(程序状态寄存器);

·f0~f7和F0~F7(FPA寄存器);

·s0~s31和S0~S31(VFP单精度寄存器);

·d0~d15和D0~D15(VFP双精度寄存器);

·p0~p15(协处理器0~15);

·c0~c15(协处理器寄存器0~15)。

5.内置变量

内置变量的设置不能用SETA、SETL或SETS等指示符来设置,只能用表达式或条件来设置。示例:

IF  {ARCHITECTURE} = “4T”

6.子程序调用规则

程序设计时,通常会把完成某个特定功能的一段程序代码编写成子程序,在需要的地方进行调用。ARM汇编程序中,使用下面语句调用子程序。

    BL next

    其中,next为子程序中的第一条指令代码的标号。

任何一个子程序进入前,处理器需要保存主程序中的现场,即需要保存当前工作寄存器(注意:当采用了子程序嵌套调用时,应该保存LR寄存器)。汇编指令BL的功能是将BL指令的下一条指令地址放到LR寄存器中,作为返回地址。并将子程序的第一条指令地址赋予PC寄存器,实现程序转移,即进入子程序执行。子程序执行完后,通过把LR寄存器值赋予PC寄存器,实现返回。

7.C、C++语言程序中内嵌汇编

C、C++语言编写的程序要比汇编语言编写的程序易读性、移植性好,因此,在嵌入式系统开发时,编写系统程序大多还是采用C、C++语言。但是在某些场合有时需要采用汇编指令编写程序,以实现一些高级语言没有的功能,并提高执行效率。ARM汇编工具支持在C、C++语言程序中嵌入汇编编写的程序段,其语法格式如下:

    __asm{“指令[;指令]”}

8.系统引导程序

大多数嵌入式系统加电后运行的第一段程序(在此称为系统引导程序,有时也称启动代码),往往是采用汇编语言编写的。

    系统引导程序是依赖于具体硬件环境的,除了依赖于CPU的体系结构外,还依赖于具体的板级硬件配置。