ARM汇编基础

概要:

        我们在进行嵌入式 Linux 开发的时候是绝对要掌握基本的 ARM 汇编,因为 Cortex-A 芯片一上电 SP 指针还没初始化,C 环境还没准备好,所以肯定不能运行 C 代码,必须先用汇编语言设置好 C 环境,比如初始化 DDR、设置 SP指针等等,当汇编把 C 环境设置好了以后才可以运行 C 代码。所以 Cortex-A 一开始肯定是汇编代码。汇编的知识很庞大,本章我们只讲解最常用的一些指令,满足我们后续学习即可。

        I.MX6U-ALPHA 使用的是 NXP 的 I.MX6UL 芯片,这是一款 Cortex-A7 内核的芯片,所以
我们主要讲的是 Cortex-A 的汇编指令。为此我们需要参考两份跟 Cortex-A 内核有关的文档:
《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》《ARM Cortex-A(armV7)编程手册 V4.0.pdf》,第一份文档主要讲解 ARMv7-A 和 ARMv7-R 指令集的开发,Cortex-A7 使用的是 ARMv7-A 指令集,第二份文档主要讲解 Cortex-A(armV7)编程的,这两份文档是学习 Cortex-A 不可或缺的文档。在《ARM ArchitectureReference Manual ARMv7-A and 
ARMv7-R edition.pdf》的 A4 章详细的讲解了 Cortex-A 的汇编指令,要想系统的学习 Cortex-A
的指令就要认真的阅读 A4 章节

一 处理器内部数据传输指令

使用处理器做的最多事情就是在处理器内部来回的传递数据,常见的操作有:
①、将数据从一个寄存器传递到另外一个寄存器。
②、将数据从一个寄存器传递到特殊寄存器,如 CPSR 和 SPSR 寄存器。
③、将立即数传递到寄存器。

记忆方法:

MRS  =》 MOVE   [Register]    [Status Register]

MSR  =》 MOVE   [Status Register]  [Register]

二 存储器访问指令

ARM 不能直接访问存储器,比如 RAM 中的数据,I.MX6UL 中的寄存器就是 RAM 类型的,我们用汇编来配置 I.MX6UL 寄存器的时候需要借助存储器访问指令,一般先将要配置的值写入到Rx(x=0~12)寄存器中,然后借助存储器访问指令将 Rx 中的数据写入到 I.MX6UL 寄存器中。读取 I.MX6UL 寄存器也是一样的,只是过程相反。

 记忆方法:

LDR:load  register

STR:store register

1、LDR 指令

LDR 主要用于从存储加载数据到寄存器 Rx 中,LDR 也可以将一个立即数加载到寄存器 Rx
中,LDR 加载立即数的时候要使用“=”,而不是“#”。在嵌入式开发中,LDR 最常用的就是读
取 CPU 的寄存器值。

2、STR 指令

LDR 是从存储器读取数据,STR 就是将数据写入到存储器中

LDR 和 STR 都是按照字进行读取和写入的,也就是操作的 32 位数据,如果要按照字节、
半字进行操作的话可以在指令“LDR”后面加上 B 或 H,比如按字节操作的指令就是 LDRB 和
STRB,按半字操作的指令就是 LDRH 和 STRH。

三 压栈和出栈指令

     我们通常会在A函数种调用B函数,当B函数执行完以后再回到A函数继续执行。要想再跳回 A 函数以后代码能够接着正常运行,那就必须在跳到 B 函数之前将当前处理器状态保存起来(就是保存 R0~R15 这些寄存器值),当 B 函数执行完成以后再用前面保存的寄存器值恢复R0~R15 即可。保存 R0~R15 寄存器的操作就叫做现场保护,恢复 R0~R15 寄存器的操作就叫做恢复现场。在进行现场保护的时候需要进行压栈(入栈)操作,恢复现场就要进行出栈操作。压栈的指令为 PUSH,出栈的指令为 POP,PUSH 和 POP 是一种多存储和多加载指令,即可以一次操作多个寄存器数据,他们利用当前的栈指针 SP 来生成地址。

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

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

压栈完成以后的堆栈如图 

上图是对R0~R3,R12进行压栈以后的堆栈示意图,此时的SP指向了0X7FFFFFEC,
假如我们现在要再将 LR 进行压栈,汇编代码如下:

      PUSH {LR} @将 LR 进行压栈

是分两步对 R0~R3,R12 和 LR 进行压栈以后的堆栈模型,如果我们要出栈的话
就是使用如下代码:
  POP {LR} @先恢复 LR
  POP {R0~R3,R12} @在恢复 R0~R3,R12

出栈的就是从栈顶,也就是 SP 当前执行的位置开始,地址依次减小来提取堆栈中的数据
到要恢复的寄存器列表中。

PUSH 和 POP 的另外一种写法是“STMFD SP!”和“LDMFD SP!”,因此上面的汇编代码可以改为:

STMFD 可以分为两部分:STM 和 FD,同理,LDMFD 也可以分为 LDM 和 FD。看到 STM
和 LDM 有没有觉得似曾相识(不是 STM32 啊啊啊啊),前面我们讲了 LDR 和 STR,这两个是
数据加载和存储指令,但是每次只能读写存储器中的一个数据。STM 和 LDM 就是多存储和多
加载,可以连续的读写存储器中的多个连续数据。(M代表Multiple的意思)
FD 是 Full Descending 的缩写,即满递减的意思。根据 ATPCS 规则,ARM 使用的 FD 类型
的堆栈,SP 指向最后一个入栈的数值,堆栈是由高地址向下增长的,也就是前面说的向下增长
的堆栈,因此最常用的指令就是 STMFD 和 LDMFD。STM 和 LDM 的指令寄存器列表中编号
小的对应低地址,编号高的对应高地址。

四 跳转指令

有多种跳转操作,比如:
①、直接使用跳转指令 B、BL、BX 等。
②、直接向 PC 寄存器里面写入数据。
上述两种方法都可以完成跳转操作,但是一般常用的还是 B、BL 或 BX

我们重点来看一下 B 和 BL 指令,因为这两个是我们用的最多的:

1. B指令

这是最简单的跳转指令,B 指令会将 PC 寄存器的值设置为跳转目标地址, 一旦执行 B 指
令,ARM 处理器就会立即跳转到指定的目标地址,并且不会再返回。

上述代码就是典型的在汇编中初始化 C 运行环境,然后跳转到 C 文件的 main 函数中运行,跳转到 C 文件以后再也不会回到汇编了。

2. BL指令

     BL 指令相比 B 指令,在跳转之前会在寄存器 LR(R14)中保存当前 PC 寄存器值,所以可以
通过将 LR 寄存器中的值重新加载到 PC 中来继续从跳转之前的代码处运行,这是子程序调用
一个基本但常用的手段。比如 Cortex-A 处理器的 irq 中断服务函数都是汇编写的,主要用汇编
来实现现场的保护和恢复、获取中断号等。但是具体的中断处理过程都是 C 函数,所以就会存
在汇编中调用 C 函数的问题。而且当 C 语言版本的中断处理函数执行完成以后是需要返回到
irq 汇编中断服务函数,因为还要处理其他的工作,一般是恢复现场。这个时候就不能直接使用
B 指令了,因为 B 指令一旦跳转就再也不会回来了,这个时候要使用 BL 指令。

上述代码中第 5 行就是执行 C 语言版的中断处理函数,当处理完成以后是需要返回来继续
执行下面的程序,所以使用了 BL 指令。

3. 算术运算指令

4. 逻辑运算指令

未完待续。。。

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值