Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1640062
  • 博文数量: 311
  • 博客积分: 7778
  • 博客等级: 少将
  • 技术积分: 4186
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-09 19:59
个人简介

蓝点工坊(http://www.bluedrum.cn) 创始人,App和嵌入式产品开发。同时也做相应培训和外包工作。 详细介绍 http://pan.baidu.com/s/1y2g88

文章存档

2012年(3)

2011年(115)

2010年(170)

2009年(23)

分类: 嵌入式

2010-11-02 14:44:38

Andrew Haung 转载注明作者及联络方式
 

Andrew Huang

 

内容:

l         ARM指令集介绍

l         ARM指令教程

l         ARM常用指令,伪指令

l         ARM C与汇编混和编程

l         ARM汇编样例: 使用汇编程序来控制LED

 

  编写一些基本汇编语言程序,用汇编实验一个LED 灯的亮和熄灭. 掌握ARM 汇编语言编程,掌握ARM 汇编语言和C混和编程

 

ARM指令集介绍

 

ARM CPURISC体系结构,相对于X86CISC体系,指令集大大简化.因此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

 

有比较明显的特征,# /* */作为注释,伪指令一用小写,并以.打头.

一类是ADSARM 编译器采用的另外一种风格.伪指令用大写,不用. 注释采用@;打头,寄存器名字前面也不用%打头

 

行号

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程序分区映象

l         GCC编译程序分区

        Gcc 的编译程序文件成功后,最后都会生一个.outELF格式的可执行文件,这个文件通常都包含三个段.text,.data.bss,

        运行时,会在进程空间会生成.text,.data.bssstack,heap五个区.

l         ADS,可执行文件有两种,一种是.axf文件,带有调试信息的ELF可执行文件,可供AXD调试工具使用.另一种是.bin

l         GCC程序对应,ADS编写的程序也有两种状态,一个保存状态.对于ELF可执行文件,一种是运行态,对应进程空间分区.

        在保存状态时,分别是代码段和数据段。代码段又分为可执行代码段(.text)和只读数据段(.rodata), 数据段又分为初始化数据段(.data)和未初始化数据段(.bss)

        可执行文件通过装载过程, 搬入到RAM中运行, 这时候可执行文件就变成运行态。这时各区的名字变成了RO,RWZI.

l         只读的代码段和常量被称作RO(ReadOnly); 对应.text.rodata

l         可读写的全局变量和静态变量被称作RW(ReadWrite) ,对应.data.bss

l         RW段中要被初始化为零的变量被称为ZI(ZeroInit) ,对应.bss

 

l         对于嵌入式系统而言,程序映象都是存储在Flash存储器等一些非易失性器件中的,而在运行时,程序中的RW段必须重新装载到可读写的RAM中简单来说,程序的加载时域就是指程序烧入Flash中的状态,运行时域是指程序执行时的状态。

        对于比较简单的情况,可以在ADS集成开发环境的ARM LINKER选项中指定RO BASERW BASE,告知连接器RORW的连接基地址。

l         在前一例即是采用这一方法

l         因为RO段包含数据和代码,所以RO BASE不一定等于可执行程序的入口.所以在ADS里通常还要手工指定 Image Entry Point,它即可以等于或大于RO BASE的地址.

        对于复杂情况,如RO段被分成几部分并映射到存储空间的多个地方时,需要创建一个称为分布装载描述文件的文本文件,通知连接器把程序的某一部分连接在存储器的某个地址空间。

 

ADS成功编译后结果

ADSC和汇编里,可以用如下保留变量来取出各个值

l                Image$$RO$$Base  RO基地址

l                Image$$RO$$Limit RO最大地址

l                Image$$RW$$Base RW段的基地址

l                Image$$RW$$Limit RW段的最大地址

l                Image$$ZI$$Base ZI段的基地址

l                Image$$ZI$$Limit ZI段的最大地址

 

在编译后,ARM编译器会把上述保留字由真实的地址代替,以下是一个实例。可以看出ZIRW一部分

 

 

 

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)        R12R13 可能被编译器用来存放中间编译结果,计算表达式值时可能将R0 R3R12R14用于子程序调用,因此要避免直接使用这些物理寄存器

4)        一般不要直接指定物理寄存器,而让编译器进行分配

内嵌汇编使用的标记是 _asm或者asm关键字,,GNU ,采用 __asm(“ ”);格式

ADS C程序内嵌汇编

#include
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
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
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 和汇编之间的参数传递是通过ATPCSARM 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

 

 
 
阅读(2217) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~