一、立即数
1. 立即数:直接写在指令里的常数,不是寄存器的值,也不是内存里的数据。
2. 指令格式:#数字,#是立即数标记。
立即数支持 8 位、10 位、11 位、12 位等不同位宽,以下为常见立即数的二进制表示示例。
3. 12位立即数判断条件:
1)如果某个数的数值范围是0~0xFF之间,那么这个数一定是立即数
2)把某个数展开成2进制,这个数的最高位1至最低位1之间的二进制数序列的位数不能超过8位(包含这两位)
3)这个数的二进制序列凑够8位之后的的右边必须为偶数个连续的0
示例:
(1)0x234 = 0000 0000 0000 0000 0000 0010 0011 0100
最高位1至最低位1之间的二进制数序列:1000 1101没有超过8位
末尾1的右边有2个0,所以0x234是立即数
(2)0x3f4 = 0000 0000 0000 0000 0000 0011 1111 0100
最高位1至最低位1之间的二进制数序列:1111 1101 从第一个1开始到最后一个1之间没有超过8位
末尾1的右边有2个0,所以0x3f4是立即数
(3)旋转位 0xFA00
0x0000FA00
循环右移位数:24位 / 2 = 12
0xC 1100
注:常量既可以当作无符号数也可以当作有符号数,由编译器决定
二、指令
1. mov
加载12位立即数到寄存器或转移一个寄存器的值到另外一个寄存器
MOV{S}<c> <Rd>, #<const>
mov r0, #2 ;加载立即数2到寄存器r0
MOV{S}<c> <Rd>, <Rm>
mov r1, r0 ;将r0寄存器的值加载到r1
注:大小写均可,但必须保持统一
大多数指令的格式为opcode rd, rn ,rm,其中,rd是目标寄存器,rn是第一操作数寄存器
示例:
mov r0, #1 ;r0 = 1
mov r1, #2 ;r1 = 2
mov r2, r1 ;r2 = r1
mov r3, r1, lsl #2 ;r3 = r1 << 2
mov r4, r0, lsl r1 ;r4 = r0 << r1
2. ldr
(1)将非立即数加载到通用寄存器
例:ldr r0, =1314
(2)将RAM中的数据加载到通用寄存器
ldr r0, =0x40000000 ;i
ldr r1, [r0] ;r1 = *r0
ldr r0, =0x40000004 ;j
ldr r2, [r0] ;r2 = *r0
add r3, r1, r2
ldr r0, =0x40000008 ;sum
str r3, [r0] ;*r0 = r3
ldr r0, =0x40000000
ldr r1, [r0] ;r1 = *r0
ldr r1, =0x1A2B3C4D
str r1, [r0] ;*r0 = r1
str r1, [r0, #4] ;*(r0 + 1) = r1
str r1, [r0], #4 ;*r0++ = r1
str r1, [r0, #4]! ;*++r0 = r1
ADD 指令用于实现加法运算,支持立即数、寄存器以及带移位操作的寄存器三种形式。
3. str
将通用寄存器中的值写入到RAM中
str r1, [r0, #4] ;*(r0 + 1) = r1
str r1, [r0], #4 ;*r0++ = r1
str r1, [r0, #4]! ;*++r0 = r1
内存地址映射示例
| nor flash | 0x00000000 – 0x1FFFFF | 2MB |
| RAM | 0x200000 – 0xFFFFFFFE | 4KB |
| pc | 0x40000000 – 0x40000FFF | 程序计数器 |
4. add
寄存器加法:add Rd, Rm, Rn → Rd = Rm + Rn
可选加后缀 adds:加法并且更新 CPSR 状态标志位(不带 s 不更新标志)
mov r0, #2
mov r4, #1
add r0, r1, #3 ;r0 = r1 + 3
add r2, r0, r1 ;r2 = r0 + r1
add r3, r2, r1, lsl r4 ;r3 = r2 + (r1 << r4)
5. sub
寄存器减法:sub Rd, Rm, Rn → Rd = Rm – Rn
减法同时更新 CPSR 标志(cmp本质就是 subs R0, R1,结果不保存,只改标志)
add r0, r1, r2 ; r0 = r1 + r2
add r0, r1, #5 ; r0 = r1 + 5
6.cmp
做减法 Rm − Rn,不保存计算结果,只修改 CPSR 程序状态寄存器的标志位 (N,Z,C,V)
寄存器 Rm、Rn 本身的值不会被改变
求三个数中的最大值
mov r0, #3
mov r1, #5
mov r2, #7
cmp r0, r1
movlt r3, r1
movgt r3, r0
cmp r3, r2
movlt r3, r2
常用后缀:lt小于、gt大于、le小于等于、ge大于等于
7. bic
bit clear,将某个通用寄存器清0(操作数中为bit1的位清0),清0结果写回到通用寄存器
ldr r0, =0xFFFFFFF
bic r0, r0, #(1 << 7)
汇编指令的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
8. orr
OR Register,将某个通用寄存器置1(操作数中为bit1的位置1),置1结果写回到通用寄存器
mov r0, #0
orr r0, r0, #(1 << 7)
9. 跳转指令
(1)b
实现无条件跳转,可用于条件分支、循环和函数调用
1)条件分支
mov r0, #5
mov r1, #3
mov r2, #7
cmp r0, r1
bgt great
blt less
great
mov r3, r0
b label
less
mov r3, r1
b label
label
cmp r3, r2
bgt finished
movlt r3, r2
finished
b finished
2)循环
(1)do while循环
mov r0, #1 ;i
mov r1, #0 ;sum
loop
add r1, r0, r1
add r0, r0, #1
cmp r0, #100
ble loop
finished
b finished
(2)while循环
mov r0, #1
mov r1, #0
loop
cmp r0, #100
bgt finished
add r1, r0, r1
add r0, r0, #1
b loop
finished
b finished
3)函数调用
(2)bl
函数跳转前,会将函数返回地址保存到 lr 寄存器
(3)bx
bx lr 等价于 mov pc, lr,bx 指令后需要加一个地址
三、栈的分类
ARM 栈分为四种类型:满减栈、满增栈、空增栈、空减栈。
- 增栈:入栈时,sp 指针往上走
- 减栈:入栈时,sp 指针往下走
- 空栈:先写数据,再挪指针
- 满栈:先挪指针,再写数据
ARM 默认使用满减栈(*–sp)
1. 栈空间分配
| sp_svc | 1K 栈(1024) |
| sp_user | 2048 |
| sp_irq | — |
2. 入栈与出栈指令
STMFD:入栈保护
STMFD<c> <Rn>{!}, <registers>
示例:stmfd sp!, <r0,r1,lr>
LDMFD:出栈恢复
LDMFD<c> <Rn>{!}, <registers>
示例:ldmfd sp!, <r0,r1,lr>
示例:
ldr pc, =_main
_asm_max
mov r0, #20
mov r1, #10
;入栈保护
stmfd sp!,{r0,r1,lr}
bl _asm_min
;出栈恢复
ldmfd sp!,{r0,r1,lr}
cmp r0, r1
movgt r2, r0
movlt r2, r1
bx lr ;mov pc, lr
_asm_min
mov r0, #4
mov r1, #3
cmp r0, r1
movlt r2, r0
movgt r2, r1
bx lr
_main
ldr sp, =0x40001000
stmfd sp!,{r0-r12,lr}
bl _asm_max
ldmfd sp!,{r0-r12,lr}
mov r2, #10
mov r3, #11
finished
b finished
四、汇编与 C 语言混合编程
1. 汇编调用 C 函数
汇编如何向 C 语言传参:
- 当参数小于等于 4 个时,通过通用寄存器进行传参(R0 – R3)
- 当参数超过 4 个时,必须通过栈进行传参(入栈保护、出栈恢复)
C 语言将函数运行结果返回给 R0 寄存器。
2. C 调用汇编函数
- C 语言向汇编函数传参,参数会被保存到通用寄存器。
- 汇编函数的结果通过 R0 寄存器返回给 C 语言。
示例:调用C语言函数找最大值
main.c代码
int c_max(int a, int b)
{
return a > b ? a : b;
}
int main(void)
{
return 0;
}
汇编:
area reset, code, readonly
code32
preserve8
entry
ldr pc, =_main
_asm_max
mov r0, #20
mov r1, #10
;入栈保护
stmfd sp!,{r0,r1,lr}
bl _asm_min
;出栈恢复
ldmfd sp!,{r0,r1,lr}
cmp r0, r1
movgt r2, r0
movlt r2, r1
bx lr ;mov pc, lr
_asm_min
mov r0, #4
mov r1, #3
cmp r0, r1
movlt r2, r0
movgt r2, r1
bx lr
_main
ldr sp, =0x40001000
;import c_add
import c_max
mov r0, #2
mov r1, #3
;bl c_add
stmfd sp!,{r0,r1,lr}
bl c_max
ldmfd sp!,{r0,r1,lr}
下面是调用汇编
;stmfd sp!,{r0-r12,lr}
;bl _asm_max
;ldmfd sp!,{r0-r12,lr}
;mov r2, #10
;mov r3, #11
finished
b finished
end
五、处理器工作模式切换(USER 模式)
切换处理器工作模式为 USER 模式的步骤:
- 将 cpsr 寄存器中的值读到 R0 寄存器。
- 将 R0 寄存器中的值修改为 10000。
- 将 R0 寄存器中的值写回到 cpsr 寄存器。
MRS 与 MSR 指令
MRS 指令:读取某个特殊寄存器(cpsr)的值到通用寄存器
MRS<c> <Rd>, <spec_reg>
MSR 指令:将通用寄存器中的值写入到特殊寄存器(cpsr)
MSR<c> <spec_reg>, <Rn>
示例:
;SVC stack
ldr sp, =0x40001000
;切换工作模式:USER模式
mrs r0, cpsr
bic r0, r0, #0x1F
orr r0, r0, #0x10
msr cpsr_c, r0
;USER stack
ldr sp, =0x40001000
sub sp, sp, #1024
import main
b main




