前期准备——2.基本汇编语法


在做裸机开发前,我们要掌握一些基础的ARM汇编语法,因为即使后面我们用C去写驱动,也要用汇编去执行配置指针、中断、清除session等操作。我们使用的芯片是I.MX6UL,这是款Cortex-A7的内核芯片,所以使用的就是Cortex-A的汇编指令,这里有两份资料可以参考点击下载(提取码:l1rg)。还好我们主要目的就是进行系统的初始化,用到的都是比较简单的指令,也不会涉及到复杂的代码结构。

GNU汇编语法

语法结构

GNU汇编语法是适用于所有的架构,并不是ARM独享的,GNU汇编由一系列的语句组成,每条语句结构如下:

label: instruction @ comment

label:标号,用来表示地址位置,有些指令前面可能会有标号,这样可以通过标号来找到指令的地址注意标号后面有个冒号,在汇报中,任何以冒号结尾的表示符都会被识别为标号

instruction:指令,包括汇编指令或伪指令

@:注释符,也可以用/**/来包裹注释内容

comment:注释内容

比如我们在第一个用汇编点亮LED的试验中的一段汇编代码

 第二行的_start就是就是标号,第6行的

ldr r0,=0x020c4068

就是指令。注意指令中的指令、伪指令、寄存器、寄存器名可以全部使用大写,也可以全部使用小写,但是切记不能大小写混用

后面的@CCGR0就是表明注释内容。

常用的伪操作

在定义标号时,要知道有些标号是常用的伪操作

  • .byte  定义单字节数据
  • .short   定义双字节数据
  • .long  定义4字节数据
  • .equ   赋值语句  比如.equ num,0x12,表示num=0x12
  • .align  字节对齐,比如.align 4 表示4字节对齐,这个在后面定义session会经常用到
  • .end  表示源文件结束
  • .global 定义全局符号,比如上面的.global _start。

并且在在使用.session伪操作定义段的时候,汇编已经预定义了一些段名:

  • .text——代码段
  • .data——初始化的数据段
  • .bss——未初始化的数据段
  • .rodata——只读数据段

函数

GNU也是支持函数的,函数的格式如下:

函数名:
    函数体
    返回语句

可以发现,函数声明的方法和前面的基础语法一样,就是把label换成了函数名。返回值在函数里不是必要的。比如下面这段代码就是我们在讲中断时定义的函数

/*复位中断服务函数 */
Reset_Handler:
    ldr r0,=Reset_Handler
    bx r0

/*未定义指令中断服务函数 */
Undefined_Handler:
    ldr r0,=Undefined_Handler
    bx r0

/*SVC中断服务函数 */
SVC_Handler:
    ldr r0,=SVC_Handler
    bx r0

常用汇编语句

下面看看我们常用的汇编语句

处理器内部数据传输指令

处理器做的最多的事情就是在处理器内部来回传递数据,这些数据操作基本为

  • 通用寄存器之间数据相互传递
  • 通用寄存器与特殊寄存器(如CPSR、SPSR等)之间数据相互传递
  • 寄存器存入立即数

数据传输常用的指令有三个:MOV、MRS和MSR,这三个指令的用法如下:

指令 目的 数据源 说明
MOV R0 R1 将寄存器R1内数据复制到寄存器R0中
MRS R0 CPSR 将特殊寄存器CPSR内的数据复制到R0中
MSR CPSR R0 将R0内数据复制到特殊寄存器CPSR内

下面分别介绍下3个指令的具体用法

MOV指令

MOV指令用来将一个寄存器里的数据拷贝到另一个寄存器内,也可以将一个立即数拷贝到寄存器内,用法如下

MOV R0,R1       @将R1内的数据传递给R0
MOV R0,#0xFF    @将立即数0xFFC传递给R0

MRS指令

MRS是将数据从特殊寄存器传递个通用寄存器,也就是用于读取特殊寄存器的值,用法如下:

MRS R0,CPSR     @将CPSR的数据传递给R0

MSR指令

MSR指令是将数据从通用寄存器传递给特殊寄存器,也就是写通用寄存器

MSR CPSR,R0    @将R0的数据传递给CPSR

MSR和MRS两个指令非常容易混淆,有个简单的方法记忆:因为指令后面两个寄存器方向是固定的,前面的是目标,后面的是源,R可以记成通用寄存器,S刚好和特殊的头文字相符,就记成特殊寄存器,MRS就是从特殊到通用,MSR就是从通用到特殊。

存储器访问指令

ARM处理器是不能直接访问存储器的,这里的存储器不是SD卡或者NANDFlash,而是类似RAM中的数据,它必须借助内核寄存器组,也就是R0~R15来实现功能,我们需要用汇编来配置I.MX6UL寄存器的时候需要借助存储器访问指令,一般就是将要配置的参数写入到通用寄存器(R0~R12)中,然后借助存储器访问指令将R存储器中的数据写入到I.MX6UL寄存器中,读取寄存器也是一样的,只是过程相反。常用的访问存储器的指令有两种:LDR和STR:用法如下表

指令 说明
LDR Rd,[Rn,#offset]

从存储器Rn+offset的位置读取数据放到Rd中

STR Rd,[Rn,#offset] 将Rd里的数据写入到Rn+offset的位置

下面来看看这两条指令是怎么用的:

LDR指令

LDR用于从RAM里读取寄存器的值,比如我们要读取地址为0x0209C004这个寄存器(GPIO1_GDIR,控制GPIO1输入输出功能的寄存器,点灯时候会用到)的值,就要这么做

LDR R1,=0x0209C004
LDR R0,[R1]

就是现在R1中保存要读取寄存器的地址,再把R1地址中的数据传给R0。注意这里传递立即数的时候和MOV指令有区别,一个用的是#,一个用的是=

STR指令

STR指令和LDR相反,用来设置寄存器的值,比如我们要讲GPIO1_GDIR的值设置为0x00000001,代码如下:

LDR R1,=0x0209C004
LDR R0,=0x00000001
STR R0,[R1]

LDR和STR是按照字节进行读取和写入到,也就是操作得都是32位的数据,如果要按照字节或板子姐进行操作得话可以来指令后加上B(Byte)或者H(Half)。同样我们找个方便记忆的方法:LDR可以记忆成LosdR,也就是读取寄存器,STR是setR,设置,也就是写入寄存器。

压栈和出栈

我们通常在中断等操作中需要在A函数中调用B函数,当B函数执行完成后继续执行A函数,要想在跳回A函数时代码能够正常运行,就需要在跳转B函数前讲当前处理器状态保存(也就是保存R0~R15的值),在B函数执行完毕后将保存的值恢复值R0~R15即可。所以保存R0~R15的过程就叫现场保护,恢复的过程就叫恢复现场。在现场保护的时候要进行压栈操作,恢复现场就是进行出栈操作。我们有两个指令来进行该擦操作:PUSH和POP

指令 说明
PUSH

将寄存器列表存入栈中

POP

从栈中恢复到寄存器列表

假如我们现在要把R0~R3和R12这5个寄存器压栈,当前的SP指针指向0x80000000,处理器的堆栈为向下增长,使用的代码就是这样的

PUSH {R0~R3, R12} @将 R0~R3 和 R12 压栈

一定要注意堆栈指针的增长方向,入栈以后的堆栈如下图所示

现在的指针就指向0x7FFFFFEC,假如此刻我们需要对LR寄存器入栈,那么执行完下面代码

PUSH {LR}    @将LR压栈

堆栈模型如下:

 在这种情况下我们如果要出栈,就要执行下面的代码

POP {LR}      @先恢复 LR
POP {R0~R3,R12}  @在恢复 R0~R3,R12

出入栈的另一种方法

出栈就是从栈盯,也就是SP当前执行的开始位置,地址依次减小来提取堆栈中的数据到要恢复的寄存器列表中,PUSH和POP的另一种方法就是 STMFD SP!和LDMFD SP!所以上面的代码也可以写成这样:

STMFD SP!,{R0~R3, R12}     @R0~R3,R12 入栈
STMFD SP!,{LR}                    @LR 入栈
LDMFD SP!, {LR}                   @先恢复 LR
LDMFD SP!, {R0~R3, R12}    @再恢复 R0~R3, R12

STMFD可以被分解为两部分:STM和FD,同样,LDMFD也可以被分解成LDM和FD这个STM和LDM就跟前面我们讲过LDR和STR,但是这两个指令只能读取存储器中一个数据,而这两个STM和LDM可以多存储和多加载,可以操作存储器内多个连续数据。FD是Full Descending的缩写,即满递减的意思,根据ATPCS规则,ARM使用的FD类型的堆栈,SP指向最后一个入栈的数值,堆栈是由高地址向下增长的,所以用的是STMFD的指令。STM和LDM的指令寄存器列表中标号小的对应低地址,编号高度对应小地址。

跳转指令

在代码中我们经常需要程序跳转至别的代码段,这就是跳转操作。有两种方法我们比较常用:

  1. 使用跳转指令:B、BL、BX
  2. 直接向PC寄存器写入数据

第一种方法我们是最常用的,指令用法如下:

指令 说明
B

跳转到label处,如果跳转范围超过了±2KB,可以指定B.W来使用32位版本的跳转指令。

BX

间接跳转,Rm内存放数据为跳转到目的地址,并且切换指令集

BL

跳转到Label处,并将返回地址保存在LR(Link Register)里,可以回到跳转前的位置

BLX

结合了BX和BL的特点,间接跳转至Rm保存的地址,并保存返回地址再LR内,切换指令集。

B指令

最简单的跳转指令,跳转后不考虑返回,一旦执行B指令,ARM处理器直接跳转到指定目标地址

_start:

    ldr sp,=0x80200000  @设置堆栈指针
    b main              @跳转到main函数

上面的代码就是用来初始化C语言的运行环境,然后跳转到C底main函数处。这里跳转到main函数后不会再回到汇编,所以使用了B指令。

BL指令

BL指令和B指令相比,就是在LR(R14)中保存PC(R15)的值,看下图,处理器在各种模式下R0~R15各个内核寄存器作用,后面会大致提到。主要是这个R15,也就是PC,保存了当前指令地址加8个字节。也就是说R15记录了当前程序的位置,我们需要回到跳转前位置只需要读取LR到值就可以了。这个后面有机会可以详细讲讲。

上面就是我们常用的指令,除此之外还有算数运算指令、逻辑运算指令等, 需要用到话我们再来补充。

相关