目录
[一. 学习汇编的目的](#一. 学习汇编的目的)
[二. ARM 指令集](#二. ARM 指令集)
[2.1 ARM 指令集体系结构](#2.1 ARM 指令集体系结构)
[三. 常用汇编指令](#三. 常用汇编指令)
[3.1 MOV](#3.1 MOV)
[3.2 LSR ASR ROR](#3.2 LSR ASR ROR)
[3.3 ADD](#3.3 ADD)
[3.4 SUB](#3.4 SUB)
[3.5 有关立即数的判断](#3.5 有关立即数的判断)
[3.5.1 判断示例](#3.5.1 判断示例)
[3.5.2 原因](#3.5.2 原因)
[3.6 LDR](#3.6 LDR)
[3.7 STR](#3.7 STR)
[3.8 清零与置位指令](#3.8 清零与置位指令)
[3.9 跳转指令](#3.9 跳转指令)
[3.10 两个对'栈'进行操作的指令](#3.10 两个对‘栈’进行操作的指令)
[3.11 对特殊功能寄存器进行操作的指令](#3.11 对特殊功能寄存器进行操作的指令)
[四 . C程序与汇编程序的相互调用](#四 . C程序与汇编程序的相互调用)
[4.1 栈](#4.1 栈)
[4.2 在汇编中调用C函数](#4.2 在汇编中调用C函数)
[4.2.1 步骤](#4.2.1 步骤)
[4.3 在C程序中调用汇编函数](#4.3 在C程序中调用汇编函数)
[4.3.1 步骤](#4.3.1 步骤)
[五. 处理器工作模式的改变](#五. 处理器工作模式的改变)
一. 学习汇编的目的
学习ARM汇编的主要目的是为了编写ARM启动代码,启动代码启动以后,引导程序到c语言环境下运行。启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。因此启动代码的主要任务有:
1、初始化异常向量表
2、初始化各工作模式的栈指针寄存器
3、开启ARM内核中断允许
4、将工作模式设置为User模式 ---系统初始为SVC模式
5、完成上述工作后,引导程序进入c语言主函数执行
二. ARM 指令集
2.1 ARM 指令集体系结构
ARM公司定的主要指令集体系结构版本:
ARMv1:
该版本的原型机是ARM1,没有用于商业产品。
ARMv2:
对V1版进行了扩展,包含了对32位结果的乘法指令和协处理器指令的支持。
ARMv3:
ARM公司第一个微处理器ARM6核心是版本3的,它作为IP核、独立的处理器、具有片
上高速缓存、MMU和写缓冲的集成CPU。
ARMv4:
当前应用最广泛的ARM指令集版本。
ARM7TDMI、ARM720T、ARM9TDMI、ARM940T、ARM920T、Intel的StrongARM等
是基于ARMv4T版本。
ARMv5:
ARM9E-S、ARM966E-S、ARM1020E、ARM 1022E以及XScale是ARMv5TE的。
ARM9EJ-S、ARM926EJ-S、ARM7EJ-S、ARM1026EJ-S是基于ARMv5EJ的。
ARM10也采用。
其中后缀意义如下:
**E:**增强型DSP指令集。包括全部算法和16位乘法操作。
**J:**支持新的Java。
ARMv6:
采用ARMv6核的处理器是ARM11系列。
ARM1136J(F)-S基于ARMv6主要特性有SIMD、Thumb、Jazelle、DBX、(VFP)、MMU。
ARM1156T2(F)-S基于ARMv6T2 主要特性有SIMD、Thumb-2、(VFP)、MPU。
ARM1176JZ(F)-S基于ARMv6KZ 在 ARM1136EJ(F)-S 基础上增加MMU、TrustZone。
ARM11 MPCore基于ARMv6K 在ARM1136EJ(F)-S基础上可以包括1-4 核SMP、MMU。
ARMv7-A:
ARM处理器核:
ARM公司开发了很多ARM处理器核,最新版位ARM11。 Cortex A7等。
三. 常用汇编指令
本节介绍几种 ARMv4/v7 指令集中常用的汇编指令
首先先看看指令的格式,以 SUB指令为例:

{S}: 指令后面加s,指令执行结果会影响CPSR寄存器中的N Z C V位
汇编指令的s后缀,几乎所有的汇编指令都可以在指令后面加上s后缀,s后缀的含义是在指令执行过程中会更新CPSR寄存器的N,V,C,Z位
|-------|-------------------------------------------------------------------------------------------------------|
| N | 在结果是有符号的二进制补码情况下,如果结果为负数,则N=1; 如果结果为非负数,则N=0 |
| Z | 如果结果为0,则Z=1;如果结果为非零,否则Z=0 |
| C | 是针对无符号数最高有效位向更高位进位时C=1 减法中运算结果的最高有效位从更高位借位时C=0 |
| V | 该位是针对有符号数的操作,会在下面两种情形变为1: 两个最高有效位均为0的数相加,得到的结果最高有效位为1; 两个最高有效位均为1的数相加,得到的结果最高有效位为0; 除了这两种情况以外V位为0 |
示例:
cpp
mov r0, #0xFFFFFFFF
adds r1, r0, #1
上面的操作会导致Z,C置位,这是因为结果为0,并且从无符号数角度来看,已经从最高位向更高位进位了
cpp
mov r0. #0x7FFFFFFF
adds r1, r0, #1
会造成N位和C位置位,这是因为计算结果0x80000000最为位为1,代表负数,并且 从有符号角度来看,把一个整数加成了负数。
**<c>:**相当于该条指令执行的条件
几乎所有的arm指令都可以在指令之后可选地增加执行条件

示例:
找三个数中的最大值
CMP比较指令用于比较两个寄存器的值或者比较一个寄存器和立即数的值,其原理是对待比较的两个数求差,看结果是否为0,这个指令会无条件修改N,V,C,Z位
cpp
MOV r0, #1
MOV r1, #2
MOV r2, #3
CMP r0, r1
MOVLT r3, r1 /如果比较结果是小于,则将r1中的值存入r3
MOVGT r3, r0 /如果比较结果是大于,则将r0中的值存入r3
CMP r2, r3
MOVGT r3, r2 /如果比较结果是大于,则将r2中的值存入r3
3.1 MOV
**作用:**加载12位立即数到寄存器或转移一个寄存器的值到另外一个寄存器
用法:
cpp
area reset, code, readonly
code32
entry
mov r0, #1 ;move 1 to r0
mov r1, #0xFF ;move 0xFF to r1
mov r2, #2
end
3.2 LSR ASR ROR
**LSR:**逻辑右移,针对无符号数,最高位补0即可
**ASR:**算术右移,最高位补符号位
**ROR:**循环右移,循环将每一次移出的最低为补到最高位
对于左移来说,只有逻辑左移和循环左移
常见的使用方法:
cpp
mov r3, r0, lsl #2 /将r0中的数逻辑左移2位后存到r3中
mov r4, r0, lsl r3 /将r0中的数逻辑左移r3中的位后存到r4中
ADD r5, r0, r2, lsl r0 /先将r2中的数逻辑左移r0中的数后再和r0相加,存到r5中
3.3 ADD
**作用:**加法操作
**一般用法:**ADD 目标寄存器 第一操作数寄存器 立即数
3.4 SUB
**作用:**减法操作
一般用法:SUB 目标寄存器 第一操作数寄存器 立即数
3.5 有关立即数的判断
很多指令都有立即数作为第二操作数的情况,那么是什么立即数呢?
立即数判断条件:
1、如果某个数的数值范围是 0~0xFF 之间,那么这个数一定是立即数
2、把某个数展开成2进制,这个数的最高位1至最低位1之间的二进制数序列的位数不能
超过8位
3、这个数的二进制序列凑够8位之后 的的右边必须为偶数个连续的 0
3.5.1 判断示例
1、0x3f4 = 0000 0000 0000 0000 0000 0011 1111 0100
最高位1至最低位1之间的二进制数序列:1111 1101 从第一个1开始到最后一个1之间没有超过8位末尾1的右边有2个0,所以0x3f4是立即数
2、0x132 = 0000 0000 0000 0000 0000 0001 0011 0010
最高位1至最低位1之间的二进制数序列:1001 1001 从第一个1开始到最后一个1之间没有超过8位末尾1的右边有1个0,不满足第二条,所以0x132不是立即数
3、0x7f8 = 0000 0000 0000 0000 0000 0111 1111 1000
最高位1至最低位1之间的二进制数序列:1111 1111从第一个1开始到最后一个1之间没有超过8位 末尾1的右边有3个0,不满足第二条,所以0x7f8不是立即数
4、0xfab4 = 0000 0000 0000 0000 1111 1010 1011 0100
最高位1至最低位1之间的二进制数序列:0011 1110 1010 1101 从第一个1开始到最后一个1之间超过8位,不满足条件1,所以这个数不是立即数
3.5.2 原因
为什么要这样进行立即数的存储:是为了增大立即数的表示范围
在ARMv汇编指令集中,指令的格式:

可以看到指令格式中,用12位去存一个立即数:
ARM中将这 12bits 分为 8bit 常数(0~255) 和 4bit 循环右移位值(0~15)
8bit 常数范围(0~255),位移的步进值是以2为单位(即实际位移 2 * rotate 位),可以表示循环有以(0~30)偶数位: 0、2、4、6、8、10、12、14、16、18、20、22、24、26、28、30。在实际存储这个数值的时候,要想办法把这个数压缩到这12位中去。压缩的方法就是找一个数,这个数必须是一个8bit数,之后循环右移2 * rotate位。如果能找打这个数,那么待保存的数就是立即数,否则就不是。
3.6 LDR
**作用:**向寄存器中加载一个非立即数;将RAM中的数据加载到通用寄存器
从内存读取数据到 CPU 寄存器(内存 → 寄存器,读)
用法:
cpp
LDR r0, =0x40000000
LDR r1, [r0] /r0中的数据为地址,将此地址中的值装载到r1中
/一次性读四字节
LDR r0, =0x40000000
LDR r1, [r0]
LDR r0, =0x40000004
LDR r2, [r0]
3.7 STR
**作用:**将通用寄存器中的值写入到RAM中
把 CPU 寄存器数据写入内存(寄存器 → 内存,写)
用法:
cpp
STR r1, [r0]
STR r1, [R0, #4]
/将r1中的值存到(r0+4)的地址处 将r0中的地址先向后偏移4个字节,r0不变
STR r1, [r0], #4
/将r1中的值存到(r0+4)的地址处,但是r0要改变,写完后r0再偏移,r0要改变
STR r1,[r0, #4]!
/先偏移4字节,但是r0要改变!!!
3.8 清零与置位指令
清零:BIC
用法:BIC+目的寄存器+第一操作数寄存器+立即数
将操作数中为bit1的位清0,结果写回目的寄存器
BIC r0, r0, #(1 << 7) //将D7位清0
置位:ORR
将操作数中bit1的位置一
eg:orr r0, r0, #3 //将低两位置1
示例代码:
cpp
LDR r0, =0xFFFFFFFF
BIC r0, r0, #3 /将低两位一起清零
LDR r0, =0x0
ORR r0 , r0, #0xF /将低四位置1
ORR r0, r0, #(1 << 3) | (1 << 2) /将D2位和D3位置1
3.9 跳转指令
b指令类似c语言的goto语句,能够实现无条件跳转。跳转时需要一个lable,表示要跳转到什么地方去
常见的三种跳转指令:
|--------|-----------------------------------------------------------|
| B | 无条件跳转,不装返回地址 |
| BL | 在函数跳转前可以将返回地址装入到 LR 寄存器 |
| BX | 后面加寄存器,可以将寄存器中的地址恢复给pc 经常在某一函数返回时使用 eg:BX lr = MOV pc, lr |
示例代码:
找三个数的最大值:
cpp
MOV r0, #1
MOV r1, #3
MOV r2, #5
CMP r0, r1
BGT great /如果大于则跳转到great标签处
BLT less /如果小于则跳转到less标签处
great
MOV r3, r0 /保存最大值到r3寄存器中
B lable /跳转到lable标签处
less
MOV r3, r1 /保存最大值到r3寄存器中
lable
CMP r3, r2 /比较
MOVLT r3, r2 /如果r3 < r2则将r2中的值装载到r3中
finish
B finish /这两句是为了防止程序跑飞
求1 - 100的和,使用两种方法,while循环和do-while循环:
cpp
/while循环求1-100和
MOV r0, #0
MOV r1, #0
lable
CMP r0, #100
BGT finish /若大于100,则跳转结束
ADD r1, r1, r0
ADD r0, r0, #1
B lable
----------------------------
/do while循环求1-100和
MOV r0, #0
MOV r1, #0
lable
ADD r1, r1, r0
ADD r0, r0, #1
CMP r0, #100
BLE lable /若小于等于100则继续
finish
B finish
3.10 两个对'栈'进行操作的指令
STMFD 入栈
STMFD<c> <Rn>{!}, <registers>
LDMFD 出栈
LDMFD<c> <Rn>{!}, <registers>
示例:
cpp
STMFD sp!, {r0, r1, lr} /将r0,r1,lr寄存器的值入栈
/这里进行函数跳转/
LDMFD sp!, {r0, r1, lr} //将r0,r1,lr寄存器的值出栈
/!表示入栈之后sp自动自减
------------------------
/也可以这样
STMFD sp!, {r0-r12, lr}
/这里进行函数跳转/
LDMFD sp!, {r0-r12, lr}
3.11 对特殊功能寄存器进行操作的指令
MRS 指令
读取某个特殊寄存器到通用寄存器
eg:MRS r0, cpsr
MSR 指令
将通用寄存器的值写到某个特殊寄存器
eg:MSR cpsr, r0
四 . C程序与汇编程序的相互调用
此小节将说明在c程序中如何调用汇编代码的函数,以及在汇编程序中如何调用c程序写好的函数,众所周知,完成函数调用就要保护现场与恢复现场,这就要使用到 '栈' ,所以先对 '栈' 进行介绍
4.1 栈
栈的分类:
空减栈,空增栈,满减栈,满赠栈
**空增:**先写入数据,再让栈指针自增;
**空减:**先写入数据,再让栈指针自减;
**满增:**先让栈指针自增,再写入数据;
**满减:**先让栈指针自减,再写入数据。


ARM 体系 采用的方案是满减栈,但是在进行操作之前,必须设置栈底的位置,这里把栈底设置为0x40001000 ,从地址0x40000000开始的0x1000这段内存空间对应的是 2440芯片 内部的一段RAM ,总共4k 。实际能够使用的内存空间为**0x40000000\~0x40000FFF**
设置栈底指针寄存器指令: LDR sp =0x40001000
4.2 在汇编中调用C函数
4.2.1 步骤
1、首先在汇编程序中使用import 函数名 导入外部函数,eg:import c_add
2、汇编程序向C程序函数传参:
通过通用寄存器传参
注意: 参数个数<=4,可以通过通用寄存器R0 - R3 传参
超过4个时,必须通过栈 传参
3、C函数将返回值保存到通用寄存器 R0
其他注意事项:
在keil中,要加上 PRESERVE8才不会报错----->强制堆栈按照8字节对齐
示例代码:
C函数:
cpp
/C函数
int c_add(int a, int b, int c, int d, int e)
{
int sum;
sum = a + b + c + d + e;
return sum;
}
汇编程序:
cpp
LDR pc, =asm_main
asm_main
LDR sp, =0x40001000 /初始化栈底指针
import c_add /在汇编中使用c函数,需要导入函数名
MOV r0, #5
MOV r1, #6
MOV r2, #1
MOV r3, #1
MOV r4, #1
STMFD sp!, {r0-r12, lr} /保护现场
STMFD sp!, {r4} /传参超过四个时使用栈传递
BL c_add
LDMFD sp!, {r4}
LDMFD sp!, {r0-r12, lr} /恢复现场
finish
B finish
4.3 在C程序中调用汇编函数
4.3.1 步骤
1、首先要声明函数
eg:extern int asm_min(int a, int b);
2、C调用汇编传参
将c传递的参数保存到通用寄存器 中
汇编函数执行的结果会装入到R0 寄存器,通过R0 返回
3、在汇编中需要导出函数:export 函数名 eg:export asm_min
示例代码:
c代码:
cpp
extern int asm_min(int a, int b);
int main()
{
int ret = 0;
ret = asm_min(30, 20);
while(1)
{
}
return 0;
}
汇编代码:
cpp
PRESERVE8
area reset, code, readonly
code32
entry
LDR pc, =asm_main
asm_min
CMP r0, r1
MOVGT r2, r1
MOVLT r2, r0
MOV r0, r2
BX lr
asm_main
LDR sp, =0x40001000
import c_add;在汇编中使用c函数,需要导入函数名
import main
export asm_min;在c代码中使用汇编函数,需要导出函数
STMFD sp!, {r0-r12, lr}
BL main
LDMFD sp!, {r0-r12, lr}
finish
B finish
end
五. 处理器工作模式的改变
切换工作方式的思路很简单,由于内核的工作模式是由CPSR寄存器的低5位 来设置的,那么就可以先把CPSR 读出来,更改低5位之后再设置进去。这里读取CPSR 使用MRS指令,写CPSR 寄存器用MSR指令,需要注意的是在keil环境下写CPSR 需要写成: MSR cpsr_c r0 ;将r0的值写入到cpsr寄存器
改变处理器工作模式,实际就是改变CPSR寄存器中的低五位


示例代码:
**基本流程:**读取CPSR---修改低五位---写回CPSR---分配栈区空间
cpp
PRESERVE8
area reset, code, readonly
code32
entry
;工作模式的切换
MRS r0, cpsr
BIC r0, r0, #0x1F
ORR r0, r0, #0x10
MSR cpsr_c, r0
LDR sp, =0x40001000 ;不同工作模式使用不同的SP栈指针,故需要单独设置
SUB sp, sp, #1024 ;为User工作模式分配1K大小的栈区
finish
B finish
end
设置栈区图示:
对同一块RAM进行分配,每种工作模式都为其分配栈区
对于其他工作模式,可以使用相同的方法来进行设置
