蓝点工坊(http://www.bluedrum.cn) 创始人,App和嵌入式产品开发。同时也做相应培训和外包工作。 详细介绍 http://pan.baidu.com/s/1y2g88
全部博文(311)
分类: 嵌入式
2010-11-02 14:44:38
Andrew Huang
内容:
l ARM指令集介绍
l ARM指令教程
l ARM常用指令,伪指令
l ARM C与汇编混和编程
l ARM汇编样例: 使用汇编程序来控制LED
编写一些基本汇编语言程序,用汇编实验一个LED 灯的亮和熄灭. 掌握ARM 汇编语言编程,掌握ARM 汇编语言和C混和编程
ARM CPU是RISC体系结构,相对于X86的CISC体系,指令集大大简化.因此ARM汇编一般也比X86的汇编比较易学.
ARM的伪指令
ARM的伪指令是类似于C语言的宏指令,本身不是CPU指令集的一部分,只是为了简化程序编写而设计的,在编译前也必须将其预处理掉.因为不是CPU标准指令集.所以不同编译器可能采用不同伪指令集.ARM汇编编程目前主要有两个类伪指令集.一种是GNU GAS采用的AT&T格式,象大部分实用的BOOTLOADER(vivi,uboot)和Linux内核的汇编代码均采用这种格式.
清单 1. 一个使用退出码 2 退出的程序 | ||||
行号 |
GAS |
| ||
|
| |||
有比较明显的特征,#和 /* */作为注释,伪指令一用小写,并以.打头.
一类是ADS的ARM 编译器采用的另外一种风格.伪指令用大写,不用. 注释采用@和;打头,寄存器名字前面也不用%打头
行号 |
ARM 汇编 | ||
|
|
在ADS下,一般汇编程序后缀名被约定为 .s(汇编源代码)或.inc(汇编包含文件)
ARM程序的执行
1. 在模拟器执行汇编
如果用开发一般在汇编,或C语言程序,只要不牵涉到外部设备使用.可以用AXD模拟器来调试.而无需真实的硬件
源代码
Hello.s的项目配置
l 如果在真实的硬件平台测试,需要调整image RO Base 0x40000000(代码装入区,) RW BASE 0x40003000(可写区首址)
l 主要缺省的0x8000并不是一个S
为此调整到0x4000000,这一段合法的RAM地址
配置Image的执行代码入口=0x40000000
ARM ADS程序分区映象
l GCC编译程序分区
– 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和汇编里,可以用如下保留变量来取出各个值
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编译器会把上述保留字由真实的地址代替,以下是一个实例。可以看出ZI是RW一部分
在嵌入式系统开发中,目前使用的主要编程语言是C和汇编,C++已经有相应的编译器,但是现在使用还是比较少的。在稍大规模的嵌入式软件中,例如含有OS,大部分的代码都是用C 编写的,主要是因为C 语言的结构比较好,便于人的理解,而且有大量的支持库。尽管如此,很多地方还是要用到汇编语言,例如bootloader和操作系统的硬件系统的初始化,包括CPU 状态的设定,中断的使能,主频的设定,以及RAM的控制参数及初始化,一些中断处理方面也可能涉及汇编。另外一个使用汇编的地方就是一些对性能非常敏感的代码块,这是不能依靠C编译器的生成代码,而要手工编写汇编,达到优化的目的。而且,汇编语言是和CPU 的指令集紧密相连的,作为涉及底层的嵌入式系统开发,熟练对应.
汇编语言与C/C++的混合编程通常有以下几种方式:
- 在C/C++代码中嵌入汇编指令
- 在汇编程序和C/C++的程序之间进行变量的互访
- 汇编程序、C/C++程序间的相互调用
1) 不能直接向PC寄存器赋值,程序跳转要使用B或者BL指令
2) 在使用物理寄存器时,不要使用过于复杂的C 表达式,避免物理寄存器冲突
3) R12和R13 可能被编译器用来存放中间编译结果,计算表达式值时可能将R0 到R3、R12及R14用于子程序调用,因此要避免直接使用这些物理寄存器
4) 一般不要直接指定物理寄存器,而让编译器进行分配
内嵌汇编使用的标记是 _asm或者asm关键字,,但GNU 中,采用 __asm(“ ”);格式
ADS 中 C程序内嵌汇编 | |
|
在上例中, 在这里C 和汇编之间的值传递是用C 的指针来实现的,因为指针对应的是地址,所以汇编中也可以访问。
内嵌汇编不用单独编辑汇编语言文件,比较简洁,但是有诸多限制,当汇编的代码较多时一般放在单独的汇编文件中。这时就需要在汇编和C 之间进行一些数据的传递,最简便的办法就是使用全局变量。
ADS 中 带有全局变量的C程序 | |
|
ADS 中 使用全局变量的汇编程序 | |
|
在C 中调用汇编文件中的函数,要做的主要工作有两个,一是在C 中声明函数原型,并加extern关键字;二是在汇编中用EXPORT 导出函数名,并用该函数名作为汇编代码段的标识,最后用mov pc, lr返回。然后,就可以在C 中使用该函数了。从C的角度,并不知道该函数的实现是用C还是汇编。更深的原因是因为C 的函数名起到表明函数代码起始地址的左右,这个和汇编的label是一致的。
ADS 中 C中调用汇编函数 | |
| |
ADS 中实现函数的汇编程序 | |
|
在这里,C 和汇编之间的参数传递是通过ATPCS(ARM Thumb Procedure Call Standard)的规定来进行的。简单的说就是如果函数有不多于四个参数,对应的用R0-R3来进行传递,多于4个时借助栈,函数的返回值通过R0来返回。
在汇编中调用C的函数,需要在汇编中IMPORT 对应的C函数名,然后将C 的代码放在一个独立的C 文件中进行编译,剩下的工作由连接器来处理。
ADS 中 实现函数的C程序 | |
|
在汇编中调用C 的函数,参数的传递也是通过ATPCS来实现的。需要指出的是当函数的参数个数大于4时,要借助stack,具体见ATPCS规范。
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 ; 声明32位ARM指令 ResetEntry ;下面这三条语句, 主要是用来设置I/O 口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 |