常用ARM指令及汇编【二】

来源:互联网 发布:淘宝 摄影布光基本知识 编辑:程序博客网 时间:2024/05/24 06:21

ARM汇编程序设计及一些格式要求说明

一般地,ARM源程序文件名的后缀名如下:
汇编文件: *.S
引入文件: *.INC
C程序 :     *.c
头文件 :    *.h

汇编语句格式:
[ 标号 ]  <指令 | 条件 | S >   <操作数>   [ ;注释]
1、所有标号必须在一行的顶格书写,其后面不要加:
2、所有指令均不能顶格书写
3、ARM汇编器对标识符大小写敏感,书写标号及指令时字母大小要一致,在ARM汇编程序中,一个ARM指令,伪指令,寄存器名可以全部为大写字母,也可以全部为小写字母,但不要大小写混合使用
4、注释使用 ;或者 @,@表示开始到此行结束,注释可以在一行顶格书写(对ADS汇编格式,只支持 ; )
5、源程序中允许空行
6、如果单行太长,可以使用字符 / 将其分行, / 后不能有任何字符,包括空格
7、对于变量的设置,常量的定义,其标识符必须在一行的顶格书写

标号:在ARM汇编中,标号代表一个地址,根据标号生成方式,可以分为以下3种
1、基于PC的标号,例如: BL  LEDTEST
2、基于寄存器的标号,例如: MAP  0x00,R9
3、绝对地址,例如: LDR R0,=WTCON

局部标号:主要用于局部范围代码中,对宏定义也非常有用,格式如下:
N { routname }
N是局部标号,为 0 ~ 99
routname是局部标号作用范围的名称
局部标号引用格式: % {F | B }  {A | T}  N {routname }
其中:
% 表示局部标号引用操作
F 指示编译器只向前搜索
B 指示编译器只向后搜索
A 指示编译器搜索宏的所有嵌套层次
T 指示编译器搜索宏的当前层
应用举例如下:
       mov   r1, #16
0     subs  r1,r1, #1
       bne    %B0


宏定义及其作用:使用宏定义可以提高程序的可读性,简化程序代码和同步修改,宏首先要定义,然后再使用,当源程序被汇编时,汇编器将展开每一个宏调用,用宏定义体代表程序中的宏调用,并使用实际的参数值代替宏定义时的形式参数
宏定义应用举例如下:
.....
MACRO        ;宏定义
CALL  $FUNCTION , $DAT1 , $DAT2   ;宏名称为CALL,带有3个参数
IMPORT  $FUNCTION       ; 声明外部子程序
MOV   R0, $DAT1    ; 设置子程序参数,R0 = $DAT1
MOV   R1, $DAT2    ;
BL       FUNCTION  ;  调用子程序
MEND                       ; 宏定义结束
......

汇编预处理后,宏调用将被展开,程序清单如下:
......
IMPORT  FADD1
MOV    R0, #3
MOV    R1, #3
BL       FADD1


子程序的调用:使用BL指令进行调用,该指令会把返回的PC值保存在LR
示例如下:
        ......
        BL  DELAY
        ......
DELAY   ....
         MOV PC,LR

当子程序指令完毕后,使用 MOV, B/BX , STMFD 等指令返回,STMFD 要与 LDMFD配套使用
STMFD  SP! , {R0-R7, LR}
......
LDMFD  SP! , {R0-R7,PC }

ARM7TDMI (-S) 是没有BLX指令的,但可以通过以下几条程序实现其功能
ADR  R1 ,  DELAY+1
MOV  LR ,  PC  ; 保存返回地址到LR
BX      R1           ;  跳转并切换指令集
......
该程序要注意的是 3级流水线,PC执行到哪里是关键


特殊寄存器定义及应用:基于ARM核的芯片一般有片内外设,它们通过其特殊寄存器访问
示例如下:
WDTC   EQU  0xE000000    ;寄存器定义
              ....
                LDR  R0, =WDTC   ; 加载立即数要加 =
                MOV  R1,  #0x12
                STR  R1,  [R0]          ; WDTC = 0x12


散转功能是汇编程序常用的一种算法,其示例如下:
       CMP  R0, #MAXINDEX                  ; 判断索引号是否超过最大索引值
       ADDLO  PC , PC , R0 , LSL #2    ; 若没有超出,则跳转到相应位置
       B      ERROR                                   ; 若已经超出,则进行出错处理
       ; 散转表,对应索引号为 0 ~ N
       B     FUN1
       B     FUN2
       B     FUN3


查表操作是汇编程序常用的一种操作,其示例如下:
         LDR  R3,  =DISP_TAB           ;取得表头
         LDR  R2 , [R3, R5, LSL #2]   ;根据R5的值查表,取出相应的值
        ......
        ;下表为 0 - F 的自模
DISR_TAB     DCD  0xC0, 0xF9 , 0xA4 , 0x99 , 0x92 , 0xA1, 0xC6,  0x83
                        DCD   0x82. 0xF8 , 0x80 , 0x90 , 0x88 , 0x86, 0x8E , 0xFF


长跳转功能的实现:ARM的B 和 BL 指令不能全空间跳转,但通过对PC进行复制,实现32位地址的跳转
ADD  LR , PC , #4   ;保存返回地址,即RET_FUN
LDR  PC , [PC , # -4] ; 跳转到 LADR_FUN
DCD  LADR_FUN
RET_FUN ....

也可以使用伪指令 LDR  PC, =LADR_FUN 实现长跳转


汇编程序一个完整的例子:
ABC   EQU  0x12
            AREA Example , CODE , READONLY     ;声明一个代码段 Example
            ENTRY              ; 入口处
            CODE32
            ADR R0, THUMB_START + 1    ; 装载地址,并设置第0位为1
            BX   R0                                           ; 切换到THUMB状态
            CODE 16                                       ; 声明 16位代码
THUMB_START
             MOV  R1, #ABC
             ADD  R1, R1 , #0x10
             B    THUMB_START
             END


外围部件控制:在ARM核芯片中,其外围部件的控制寄存器,一般会设置“置位/复位”寄存器,这样可以方便的实现位操作,而不影响到其它位,操作示例如下:
LDR  R0 , =GPIO_BASE
MOV  R1 , #0x00
STR  R1 , [R0 , #0x04 ]
MOV  R1 ,  #0x10
STR  R1 ,  [R0 , #0x0C]
 

==================================================================================================

C与汇编混合编程

在需要C与汇编混合编程时,可使用直接内存汇编的方法混合编程,或者将汇编文件以文件的形式加入项目中

内嵌汇编的语法:
__asm
{
      指令 [;指令]   /*注释*/
      ......
      [指令]
}

应用举例:
__inline  void  enable_IRQ(void)
{
      int  temp
      __asm           //嵌入汇编代码
      {
           MRS  tmp , CPSR             //读取CPSR的值
           BIC    tmp , tmp, #0x80    //IRQ中断禁止位I清零,即允许中断
           MSR  CPSR , tmp             //设置CPSR的值
      }
}


ARM编译器特定的关键词
__asm:告诉编译器下面的代码是用汇编写的
__inline:声明该函数在其被调用的地方展开
__irq:声明该函数可以被用做irq或者fiq异常的中断处理程序
__pure:声明一个函数,其结果仅仅依赖于其输入参数,而且它没有负效应
__int64:是long long 的同义词
__volatile:告诉编译器该对象可能在程序之外被修改
__weak:用于限定一个对象,该对象如果连接时不存在,不会报错


内嵌汇编的指令用法:
1、不能直接向PC寄存器赋值,程序跳转只能使用 B 或 BL指令实现,但是只有B可以使用C程序中的标号,BL不能
2、在内嵌汇编指令中,常量前面的 # 可以省略
3、所有的内存分配均由C编译器完成,内嵌汇编器不支持内嵌汇编程序用于内存分配的伪指令
以上的常见的注意事项

内嵌汇编器与 armasm 汇编器的差异
1、内嵌汇编器不支持通过 " . " 指示符 或 PC 获取 当前指令地址, 不支持 LDR Rn , =expr伪指令
2、使用 Mov  Rn , expr 指令向寄存器赋值,不支持标号表达式,不支持 ADR和ADRL,不支持BX,不能向PC赋值
3、N、Z、C 和V标志中的C不具有真实意义

内存汇编还有一些注意事项:
1、不要使用寄存器代替变量,尽管有时候寄存器明显对应某个变量
int  had_f (int x)
{
     __asm
       {
           ADD R0,R0, #1    //发生寄存器冲突,实际上x的值没有变化
       }
    return(x) ;
}


使用 IMPORT 伪指令来引入全局变量,并利用LDR 和 STR 指令根据全局变量的地址访问它们
下面例子是一个汇编代码的函数,它读取全局变量 glovbvar,将其加1后返回

         AREA  globats , CODE , READONLY
         EXPORT  asmsubroutime
         IMPORT   glovbvar
asmsubroutime
         LDR R1, =glovbvar
         LDR R0, [R1]
         ADD R0 , R0 , #1
         STR R0 , [R1]
         MOV PC , LR
         END



C与汇编相互调用

寄存器的使用规则为:子程序间通过寄存器R0 - R3 来传递参数,使用 R4 - R11来保存局部变量, R12用作过程调用中间临时寄存器,记作 IP, R13用作堆栈指针,R14作为链接寄存器,记作 LR,R15是程序计数器

子程序参数传递规则:当寄存器不超过4个时,使用 R0 - R3来传递参数,当超过4个时,可以使用堆栈来传递参数,入栈的顺序与参数顺序相反,即最后一个字数据先入栈

子程序结果返回规则:结果为一个32位的整数时,可以通过寄存器R0返回,如果是64位,通过R0和R1返回,对于位数更多的结果,需要通过内存来传递
 

=================================================================================================

C语言调用汇编程序:在汇编程序中使用 EXPORT 伪指令声明本子程序,使其它程序可以调用子程序,在C语言程序中使用extern关键字声明外部函数(声明要调用的汇编子程序),即可调用此汇编子程序


C程序调用汇编程序例子如下:

被调用的汇编子程序代码:
        AREA  SCopy , CODE , READONLY
        EXPORT  strcopy   ; 声明strcopy ,以便外部程序引用
strcopy
        ;R0  为目标字符串的地址
        ; R1 为源字符串的地址
        LDRB R2,[R1] , #1  ; 读取字节数据,源地址加1
        STRB  R2,[R0], #1  ; 保存读取的1字节数据,目标地址加1
        CMP  R2, #0            ; 判断字符串是否复制完毕
        BNE   strcopy          ; 没有复制完毕,继续循环
        MOV   PC,LR           ; 返回
        END


调用汇编的C函数:

Code:
  1. #include <stdio.h>   
  2. extern void strcopy(char *d,const char *s)    //声明外部函数,即汇编子程序  
  3. int main(void)   
  4. {   
  5.     const char *strcstr = "First String-source" ;    //定义字符串常量  
  6.     char dststr[] = "Second string-destination";   //定义字符串变量  
  7.     printf("Before copying:  /n");   
  8.     printf("%s/n %s/n",srcstr,dststr);    //显示源字符串和目标字符串的内容  
  9.     strcopy(dststr,srcstr);   
  10.     printf("After copying: /n");   
  11.     printf("%s/n %s/n",srcstr,dststr);   //显示复制后的结果  
  12.     return 0;   
  13. }  

=================================================================================================

汇编程序调用C程序:
在汇编程序中使用IMPORT伪指令声明将要调用的C程序函数,在调用C程序时,要正确设置入口参数,然后使用BL调用

C程序函数代码:

Code:
  1. int sum5(int a,int b,int c,int d,int e)   
  2. {   
  3.     return a+b+c+d+e;    //返回5个变量的和  
  4. }  

汇编调用C程序:
        EXPORT  CALLSUM5
        AREA  Example , CODE , READONLY
        IMPORT sum5      ;  声明外部标号 sum5,即C函数 sum5()
CALLSUM5
        STMFD  SP!,{LR}  ;   LR寄存器入栈
        ADD R1,R0,R0     ;   设置sum5函数入口参数,R0为参数a
        ADD R3,R1,R2
        STR  R3,[SP #4]!  ;   参数e要通过堆栈传递
        ADD R3,R1,R1     ;   R3为参数d
        BL sum5               ;   调用sum5(),结果保存在R0中
        ADD SP,SP, #4    ;   修正SP指针
        LDMFD  SP!, {PC}  ; 子程序返回
        END


到此为止,自己将 ARM常用及汇编的内容给整理了一遍,也深入了对它们的了解,希望自己日后也能坚持下去,读好自己的专业!!

原创粉丝点击