ADS下arm汇编

ARM 277浏览
内容:

ARM指令集介绍
ARM指令教程
ARM常用指令,伪指令
ARM C与汇编混和编程
ARM汇编样例: 使用汇编程序来控制LED

编写一些基本汇编语言程序,用汇编实验一个LED 灯的亮和熄灭. 掌握ARM 汇编语言编程,掌握ARM 汇编语言和C混和编程
ARM指令集介绍
 
ARM CPU是RISC体系结构,相对于X86的CISC体系,指令集大大简化.因此ARM汇编一般也比X86的汇编比较易学.
ARM的伪指令
ARM的伪指令是类似于C语言的宏指令,本身不是CPU指令集的一部分,只是为了简化程序编写而设计的,在编译前也必须将其预处理掉.因为不是CPU标准指令集.所以不同编译器可能采用不同伪指令集.ARM汇编编程目前主要有两个类伪指令集.一种是GNU GAS采用的AT&T格式,象大部分实用的BOOTLOADER(vivi,uboot)和Linux内核的汇编代码均采用这种格式.
清单1. 一个使用退出码 2退出的程序
行号 GAS
 
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
# Text segment begins
.section .text
 
   .globl _start
 
# Program entry point
   _start:
 
# Put the code number for system call
      movl $1, %eax
 
/* Return value */
      movl $2, %ebx
 
# Call the OS
      int   $0x80
 
       
有比较明显的特征,#和 /* */作为注释,伪指令一用小写,并以.打头.
一类是ADS的ARM 编译器采用的另外一种风格.伪指令用大写,不用. 注释采用@和;打头,寄存器名字前面也不用%打头
 
行号
ARM 汇编
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
 
COUNT              EQU          0x40003100     ;
定义一个变量,地址为0x40003100       
                           
                            AREA        Example2,CODE,READONLY   ;
声明代码段Example2
                            ENTRY                                 ;
标识程序入口
                            CODE32                              ;
声明32ARM指令
START                LDR          R1,=COUNT     ; R1 <= COUNT
                            MOV          R0,#0                 ; R0 <= 0
                            STR           R0,[R1]              ; [R1] <= R0,即设置COUNT0
                                                       
LOOP              LDR          R1,=COUNT    
                            LDR          R0,[R1]              ; R0 <= [R1]
                            ADD          R0,R0,#1 ; R0 <= R0 + 1
                            CMP          R0,#10               ; R010比较,影响条件码标志
                            MOVHS     R0,#0                 ;
R0大于等于10,则此指令执行,R0 <= 0
                            STR           R0,[R1]              ; [R1] <= R0,即保存COUNT
                           
                            B                LOOP
                           
                            END
 
 
在ADS下,一般汇编程序后缀名被约定为 .s(汇编源代码)或.inc(汇编包含文件)
ARM程序的执行
1.在模拟器执行汇编
如果用开发一般在汇编,或C语言程序,只要不牵涉到外部设备使用.可以用AXD模拟器来调试.而无需真实的硬件
源代码
Hello.s的项目配置
l如果在真实的硬件平台测试,需要调整image RO Base 0x40000000(代码装入区,) RW BASE 0x40003000(可写区首址)
l主要缺省的0x8000并不是一个S3C2410合法的地址,强行在这个地址运行会触发”Data Abort”异常.
为此调整到0x4000000,这一段合法的RAM地址
配置Image的执行代码入口=0x40000000

 
ARM ADS程序分区映象
lGCC编译程序分区
–Gcc 的编译程序文件成功后,最后都会生一个.out或ELF格式的可执行文件,这个文件通常都包含三个段.text,.data和.bss段,
–运行时,会在进程空间会生成.text,.data.bss和stack,heap五个区.
l在ADS下,可执行文件有两种,一种是.axf文件,带有调试信息的ELF可执行文件,可供AXD调试工具使用.另一种是.bin
l跟GCC程序对应,ADS编写的程序也有两种状态,一个保存状态.对于ELF可执行文件,一种是运行态,对应进程空间分区.
–在保存状态时,分别是代码段和数据段。代码段又分为可执行代码段(.text)和只读数据段(.rodata), 数据段又分为初始化数据段(.data)和未初始化数据段(.bss)。
–可执行文件通过装载过程, 搬入到RAM中运行, 这时候可执行文件就变成运行态。这时各区的名字变成了RO,RW和ZI段.
l 只读的代码段和常量被称作RO段(ReadOnly); 对应.text和.rodata
l 可读写的全局变量和静态变量被称作RW段(ReadWrite) ,对应.data和.bss
l RW段中要被初始化为零的变量被称为ZI段(ZeroInit)。 ,对应.bss段
l对于嵌入式系统而言,程序映象都是存储在Flash存储器等一些非易失性器件中的,而在运行时,程序中的RW段必须重新装载到可读写的RAM中简单来说,程序的加载时域就是指程序烧入Flash中的状态,运行时域是指程序执行时的状态。
–对于比较简单的情况,可以在ADS集成开发环境的ARM LINKER选项中指定RO BASE和RW BASE,告知连接器RO和RW的连接基地址。
l在前一例即是采用这一方法
l因为RO段包含数据和代码,所以RO BASE不一定等于可执行程序的入口.所以在ADS里通常还要手工指定 Image Entry Point,它即可以等于或大于RO BASE的地址.
–对于复杂情况,如RO段被分成几部分并映射到存储空间的多个地方时,需要创建一个称为“分布装载描述文件”的文本文件,通知连接器把程序的某一部分连接在存储器的某个地址空间。
ADS成功编译后结果
在ADS的C和汇编里,可以用如下保留变量来取出各个值
lImage$$RO$$Base RO基地址
lImage$$RO$$Limit RO最大地址
lImage$$RW$$Base RW段的基地址
lImage$$RW$$Limit RW段的最大地址
lImage$$ZI$$Base ZI段的基地址
lImage$$ZI$$Limit ZI段的最大地址
 
在编译后,ARM编译器会把上述保留字由真实的地址代替,以下是一个实例。可以看出ZI是RW一部分

ARM C与汇编混和编程
在嵌入式系统开发中,目前使用的主要编程语言是C和汇编,C++已经有相应的编译器,但是现在使用还是比较少的。在稍大规模的嵌入式软件中,示例含有OS,大部分的代码都是用C 编写的,主要是因为C 语言的结构比较好,便于人的理解,而且有大量的支持库。尽管如此,很多地方还是要用到汇编语言,示例bootloader和操作系统的硬件系统的初始化,包括CPU 状态的设定,中断的使能,主频的设定,以及RAM的控制参数及初始化,一些中断处理方面也可能涉及汇编。另外一个使用汇编的地方就是一些对性能非常敏感的代码块,这是不能依靠C编译器的生成代码,而要手工编写汇编,达到优化的目的。而且,汇编语言是和CPU
的指令集紧密相连的,作为涉及底层的嵌入式系统开发,熟练对应.
 
汇编语言与C/C++的混合编程通常有以下几种方式:
        - 在C/C++代码中嵌入汇编指令
        - 在汇编程序和C/C++的程序之间进行变量的互访
        - 汇编程序、C/C++程序间的相互调用
 
1.在C 语言中内嵌汇编
 在C 中内嵌的汇编指令包含大部分的ARM 和Thumb 指令,不过其使用与汇编文件中的指令有些不同,存在一些限制,主要有下面几个方面:
1)不能直接向PC寄存器赋值,程序跳转要使用B或者BL指令
2)在使用物理寄存器时,不要使用过于复杂的C 表达式,避免物理寄存器冲突
3)R12和R13 可能被编译器用来存放中间编译结果,计算表达式值时可能将R0 到R3、R12及R14用于子程序调用,因此要避免直接使用这些物理寄存器
4)一般不要直接指定物理寄存器,而让编译器进行分配
内嵌汇编使用的标记是 _asm或者asm关键字,,但GNU 中,采用 __asm(“ ”);格式
ADS
C程序内嵌汇编

#include <stdio.h>
void my_strcpy(const char *src, char *dest)
{
  char ch;
  _asm
  {
    loop:
    ldrb ch, [src], #1
    strb ch, [dest], #1
    cmp ch, #0
    bne loop
  }

}
int main()
{
  char *a = "forget it and move on!";
  char b[64];
  my_strcpy(a, b);
  printf("original: %s", a);
  printf("copyed: %s", b);
  return 0;
}

 
在上例中, 在这里C 和汇编之间的值传递是用C 的指针来实现的,因为指针对应的是地址,所以汇编中也可以访问。
2.在汇编,C程序之间的全局变量的传递
内嵌汇编不用单独编辑汇编语言文件,比较简洁,但是有诸多限制,当汇编的代码较多时一般放在单独的汇编文件中。这时就需要在汇编和C 之间进行一些数据的传递,最简便的办法就是使用全局变量。
ADS
带有全局变量的C程序

/* cfile.c
*
定义全局变量,并作为主调程序
*/
#include <stdio.h>
int gVar_1 = 12;
extern asmDouble(void);
int main()
{
  printf("original value of gVar_1 is: %d", gVar_1);
  asmDouble();//
使用汇编代码段
  printf(" modified value of gVar_1 is: %d", gVar_1);
  return 0;

 
ADS
使用全局变量的汇编程序

;called by main(in C),to double an integer, a global var defined in C is used.
AREA asmfile, CODE, READONLY
EXPORT asmDouble

IMPORT gVar_1
asmDouble //汇编子函数asmDouble
ldr r0, =gVar_1
ldr r1, [r0]
mov r2, #2
mul r3, r1, r2
str r3, [r0]
mov pc, lr
END

 
3.在C 中调用汇编的函数
在C 中调用汇编文件中的函数,要做的主要工作有两个,一是在C 中声明函数原型,并加extern关键字;二是在汇编中用EXPORT 导出函数名,并用该函数名作为汇编代码段的标识,最后用mov pc, lr返回。然后,就可以在C 中使用该函数了。从C的角度,并不知道该函数的实现是用C还是汇编。更深的原因是因为C 的函数名起到表明函数代码起始地址的左右,这个和汇编的label是一致的。
ADS
C中调用汇编函数

/* cfile.c
* in C,call an asm function, asm_strcpy
* Sep 9, 2004
*/
#include <stdio.h>
extern void asm_strcpy(const char *src, char *dest);
int main()
{
  const char *s = "seasons in the sun";
  char d[32];
  asm_strcpy(s, d);
  printf("source: %s", s);
  printf(" destination: %s",d);
  return 0;
}

 
ADS
中实现函数的汇编程序

;asm function implementation
AREA asmfile, CODE, READONLY
EXPORT asm_strcpy
asm_strcpy

loop
ldrb r4, [r0], #1 address increment after read
cmp r4, #0
beq over
strb r4, [r1], #1
b loop
over
mov pc, lr
END

 
在这里,C 和汇编之间的参数传递是通过ATPCS(ARM Thumb Procedure Call Standard)的规定来进行的。简单的说就是如果函数有不多于四个参数,对应的用R0-R3来进行传递,多于4个时借助栈,函数的返回值通过R0来返回。
4.在汇编中调用C的函数
在汇编中调用C的函数,需要在汇编中IMPORT 对应的C函数名,然后将C 的代码放在一个独立的C 文件中进行编译,剩下的工作由连接器来处理。
ADS
实现函数的C程序

;the details of parameters transfer comes from ATPCS
;if there are more than 4 args, stack will be used
EXPORT asmfile
AREA asmfile, CODE, READONLY
IMPORT cFun
ENTRY
mov r0, #11
mov r1, #22
mov r2, #33
BL cFun
END

/*C file, called by asmfile */
int cFun(int a, int b, int c)
{
return a + b + c;
}

 
在汇编中调用C 的函数,参数的传递也是通过ATPCS来实现的。需要指出的是当函数的参数个数大于4时,要借助stack,具体见ATPCS规范。
 
ARM汇编样例: 使用汇编程序来控制LED
ARM采用内存映射的模式来使用I/O端口,因此CPU设备寄存器被映射到一个物理地址空间上.但是汇编中,对数字的运算全部是在通用寄存器中完成,因此LED对应的GPIO端口的值必须要装入到通用寄存器中.
LDR指令用于从存储空间(这里就是GPIO寄存器映射空间)装入值到通用寄存器中,STR指令用于把计算的结果值存入到存储空间上.
 
;定义端口E
寄存器预定义
rGPFCON         
EQU          0x56000050     ;
rGPFDAT         
EQU         0x56000054
rGPFUP           
EQU         0x56000058    
 
;该伪指令定义了一个代码段,
段名为Init ,
属性只读                     
                           
AREA        Init,CODE,READONLY     
                           
ENTRY                                 ;
标识程序入口
                           
CODE32                             ;
声明32ARM指令
ResetEntry      
;下面这三条语句,
主要是用来设置IO
GPF7
为输出属性
                           
LDR R0 , =rGPFCON    ;将寄存器rPCONF
的地址存放到寄存器r0
                           
LDR R1 , =0x4000
                           
                           
STR R1 , [R0 ]            ;r1
中的数据存放到寄存器rPCONF
;下面这三条语句,
主要是禁止GPF
端口的上拉电阻
                           
LDR R0 , =rGPFUP
                           
LDR R1 , =0xffff
                           
STR R1 , [ R0 ]
                           
LDR R2 , =rGPFDAT    ;将数据端口F
的数据寄存器的地址附给寄存器r2
LEDLOOP
                           
LDR R1 , =0xff
                           
STR R1 , [R2]                     ;使GPE7
输出高电平, D14
灯会灭
 
                           
BL DELAY                                     ;调用延迟子程序
 
                           
LDR R1 , =0x0
                           
STR R1 , [R2 ]                     ;使GPE7
输出低电平, D14
灯亮
                           
BL DELAY                                     ;调用延迟
                           
B LEDLOOP                                ;不断的循环, D14
将不停的闪烁
;下面是延迟子程序
DELAY
                           
LDR R3 , =0xBFFFF                   ;设置延迟的时间
DELAY1
                           
SUB R3 , R3 ,#1                          ; r3 =r3 -1
                           
CMP R3 , #0x0                             ;r3
的值与。相比较
                           
BNE DELAY1             ;比较的结果不为0 ( r3
不为0 ) ,
继续调用delayl ,
否则执行下一条语句
MOV PC ,LR ;返回                                
                           
                           
END