语句
GNU 汇编语法适用于所有的架构,并不是 ARM 独享的,GNU 汇编由一系列的语句组成,每行一条语句,每条语句有三个可选部分:label: instruction @ comment
label:标号,表示地址位置instruction:指令,汇编指令或伪指令@ comment:注释
ARM中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混用。
伪操作(伪指令)
用户可通过.section伪操作来定义一个段,汇编系统预定义了一些段名,包括:
.text:代码段.data:初始化的数据段.bss:未初始化的数据段.rodata:只读数据段
.section也可用于自定义段,每个段以段名开始,以下一段名或文件结尾结束。如.section .testsection @顶一个testsection段
汇编程序的默认入口标号是_start,不过我们也可以在链接脚本中使用ENTRY来指明其它的入口点,下面的代码就是使用_start作为入口标号:
“
.global _start
_start:
ldr r0, =0x12 @r0=0x12
“
代码中.global是伪操作,表示_start是一个全局标号(性质类似于C语言中的全局变量)。
常见的伪指令有:
.global:声明全局符号,如.global main @声明main为全局符号.section:定义段.word:定义字数据.asciz:定义以null结尾的字符串,如.asciz "Hello World".byte:定义单字节数据,比如.byte 0x12.short:定义双字节数据,比如.short 0x1234.long:定义一个4字节数据,比如.long 0x12345678.equ:赋值,如.equ num, 0x12表示num=0x12.align:数据字节对齐,.align 4表示4字节对齐.end:表示源文件结束
处理器内部数据传输指令
最常见的一类指令,包括:
- 将数据从一个寄存器传递到另外一个寄存器
- 将数据从一个寄存器传递到特殊寄存器,如 CPSR 和 SPSR 寄存器
- 将立即数传递到寄存器
MOV
将数据从一个寄存器拷贝到另一个寄存器,或将一个立即数传递到寄存器中。
MOV R0, R1 @寄存器R1中的数据传递给R0MOV R0, #0x12 @立即数0x12传递给R0MRS
将特殊寄存器(如CPSR和SPSR)中的数据传递给通用寄存器。注意:读取特殊寄存器数据时只能使用MRS指令。
MRS R0, CPSRMSR
将普通寄存器的数据传递至特殊寄存器,即写入特殊寄存器。
MSR CPSR, R0存储器访问指令
ARM无法直接访问存储器(如RAM)。以I.MX6UL为例,其寄存器为RAM类型,汇编配置寄存器时需借助存储器访问指令,将待配置的值写入到Rx(x=0~12)寄存器中,然后借助存储器访问指令将Rx中的数据写入之MX6UL的寄存器中,读取亦同。
LDR
用于从存储器加载数据至寄存器Rx中,也可以将一个立即数加载到寄存器Rx中。加载立即数时,使用=而不是#,如=0xFFFFFFFF。
在嵌入式开发中,LDR最常用的就是读取CPU的寄存器值:
LDR R0, =0x0209C004 @将寄存器地址0x0209C004加载到R0中,准备寻址LDR R1, [R0] @读取地址0x0209C004中的值到R1中STR
与LDR相反,STR用于将数据写入到存储器中。
LDR R0, =0x0209C004 @寄存器地址0x0209C004写入R0LDR R1, =0x20000002 @待写入值0x20000002写入R1STR R1, [R0] @R1中的值写入R0中所保存的地址中跳转指令
B指令
B指令将PC寄存器的值设置为目标跳转地址,一旦执行B指令,ARM处理器就会立即跳转至指定的目标地址。如果待调用函数不会再返回到原来的执行处,就可以使用B指令。
_start: ldr sp, =0x80200000 @设置栈指针 b main @跳转到main函数BL指令
相比B指令,BL指令在跳转之前会在寄存器 LR(R14) 中保存当前 PC 寄存器值,所以可以通过将 LR 寄存器中的值重新加载到 PC 中来继续从跳转之前的代码处运行,这是子程序调用一个基本但常用的手段,常见于各种中断服务程序。
Cortex-A 处理器的 irq 中断服务函数都是汇编写的,主要用汇编来实现现场的保护和恢复、获取中断号等。但是具体的中断处理过程都是 C 函数,所以就会存在汇编中调用 C 函数的问题。而且当 C 语言版本的中断处理函数执行完成以后是需要返回到 irq 汇编中断服务函数,因为还要处理其他的工作,一般是恢复现场。这个时候就不能直接使用 B 指令了,因为 B 指令一旦跳转就再也不会回来了,这个时候要使用 BL 指令,示例代码如下:
push {r0, r1} @入栈,保存R0, R1cps #0x13 @进入SVC模式,CPS指令用于直接修改CPSR寄存器的M[4:0],让CPU进入不同的模式
bl system_irqhandler @加载C语言中断处理函数至R2寄存器
cps #0x12 @进入IRQ模式pop {r0, r1} @恢复现场str r0, {r1, 0#10} @中断执行完成,写EOIRARM中常见的寄存器
几个ARM中常用的寄存器:
- IP寄存器:内部程序调用暂存寄存器,子程序的连接text段中常使用该规则
- SP寄存器:栈指针寄存器,用于存储当前栈顶地址。程序执行过程中,栈是用来存储临时变量、函数调用返回地址等数据的重要数据结构,SP寄存器的值会随着栈的变化而变化
- LR寄存器:连接寄存器,程序跳转(子程序调用,中断跳转)后,arm自动在该寄存器中存入原程序(未跳转)的下一条指令的地址,也叫函数调用返回地址。当一个函数被调用时,LR寄存器会存储调用该函数的下一条指令的地址,当函数执行完毕后,程序会跳转到LR寄存器中存储的地址继续执行。
- PC寄存器:程序计数器,保存的是当前正在取指的指令的地址(arm采用2级流水线,因此是当前正在执行指令的地址+8)。PC寄存器是ARM中的程序计数器,用于存储下一条将要执行的指令的地址。
此外,还有两个比较重要的状态寄存器:
- CPSR:程序状态寄存器,在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。CPSR在用户级编程时用于存储条件码。
- SPSR:程序状态保存寄存器,每一种处理器模式下都有一个状态寄存器SPSR,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。由于用户模式和系统模式不是异常中断模式,所以他没有SPSR。当用户在用户模式或系统模式访问SPSR,将产生不可预知的后果。
二者常用于MRS或MSR指令,用于spsr中的值转移到寄存器或把寄存器的内容加载到spsr中,如:
mrs r0, spsr /* 读取spsr寄存器 */msr spsr_cxsf, r0 /* 恢复spsr */ARM进入异常模式后,SPSR自动保存进入异常前的CPSR的值,以便异常返回后恢复异常发生时的工作状态。
LED点灯示例
.global _start /* 全局标号 */
/* * 描述: _start函数,程序从此函数开始执行此函数完成时钟使能、 * GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。 */_start: /* 例程代码 */ /* 1、使能所有时钟 */ ldr r0, =0X020C4068 /* CCGR0 */ ldr r1, =0XFFFFFFFF str r1, [r0]
ldr r0, =0X020C406C /* CCGR1 */ str r1, [r0]
ldr r0, =0X020C4070 /* CCGR2 */ str r1, [r0]
ldr r0, =0X020C4074 /* CCGR3 */ str r1, [r0]
ldr r0, =0X020C4078 /* CCGR4 */ str r1, [r0]
ldr r0, =0X020C407C /* CCGR5 */ str r1, [r0]
ldr r0, =0X020C4080 /* CCGR6 */ str r1, [r0]
/* 2、设置GPIO1_IO03复用为GPIO1_IO03 */ ldr r0, =0X020E0068 /* 将寄存器SW_MUX_GPIO1_IO03_BASE加载到r0中 */ ldr r1, =0X5 /* 设置寄存器SW_MUX_GPIO1_IO03_BASE的MUX_MODE为5 */ str r1,[r0]
/* 3、配置GPIO1_IO03的IO属性 *bit 16:0 HYS关闭 *bit [15:14]: 00 默认下拉 *bit [13]: 0 kepper功能 *bit [12]: 1 pull/keeper使能 *bit [11]: 0 关闭开路输出 *bit [7:6]: 10 速度100Mhz *bit [5:3]: 110 R0/6驱动能力 *bit [0]: 0 低转换率 */ ldr r0, =0X020E02F4 /*寄存器SW_PAD_GPIO1_IO03_BASE */ ldr r1, =0X10B0 str r1,[r0]
/* 4、设置GPIO1_IO03为输出 */ ldr r0, =0X0209C004 /*寄存器GPIO1_GDIR */ ldr r1, =0X0000008 str r1,[r0]
/* 5、打开LED0 * 设置GPIO1_IO03输出低电平 */ ldr r0, =0X0209C000 /*寄存器GPIO1_DR */ ldr r1, =0 str r1,[r0]
/* * 描述: loop死循环 */loop: b loop