为什么要学汇编?
汇编语言是一个面对机器的程序设计语言,这么一个古老的语言,没什么我们还要去学?在接触裸机开发之后我知道了。
我之前经常使用Keil开发STM32F1、F4系列单片机,我只需要编写C语言部分的代码就可以,为什么我在Keil中不需要写汇编呢?因为ST公司已经帮我们写好了。
在市面上大部分芯片上电后是没有准备好C语言的环境的。所以第一行程序肯定是汇编的,至于要写多少汇编程序,那就看你能在哪一步把 C 语言环境准备好。所谓的 C语言环境就是保证 C 语言能够正常运行。C 语言中的函数调用涉及到出栈入栈,出栈入栈就要对堆栈进行操作,所谓的堆栈其实就是一段内存,这段内存比较特殊,由 SP 指针访问,SP 指针指向栈顶。芯片一上电 SP 指针还没有初始化,所以 C 语言没法运行,对于有些芯片还需要初始化 DDR,因为芯片本身没有 RAM,或者内部 RAM 不开放给用户使用,用户代码需要在DDR 中运行,因此一开始要用汇编来初始化 DDR 控制器。
GUN语法
不同的编译器其实它们之间的汇编语法也是不同的。我们要编写的是 ARM汇编,编译使用的 GCC 交叉编译器,所以我们的汇编代码要符合 GNU 语法。
GNU 汇编语法适用于所有的架构,并不是 ARM 独享的,GNU 汇编由一系列的语句组成,每行一条语句,每条语句有三个可选部分,如下:
label:instruction @ comment
- label:标号,即地址位置。有些命令是有标号的这样就可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。
- instruction 指令
- @ comment 注释
例如:
add:
MOVS R0, #0X11 @设置 R0=0X11
注意千万不要大小写混用,要么全部用大写,要么全部用小写。
GNU 汇编同样也支持函数,函数格式如下:
函数名:
函数体
返回语句
常用汇编指令
处理器内部的数据传输
如果我们想把a的值赋值给b,在C语言中只需b=a就可以;在汇编中就比较复杂了。
常见的三种传输情况
- 将数据从一个寄存器传递到另一个寄存器
- 将数据从一个寄存器传递到特殊寄存器,如CPSR
- 将立即数传递给寄存器
数据传输常用的指令有3个:MOV、MRS、MSR
MOV:将一个寄存器数或一个立即数拷贝到另一个寄存器
MOV R0, R1 @将R1寄存器的值拷贝到R0
MOV R0, #0x11 @将一个立即数0x11拷贝到R0
MRS:将特殊功能寄存器中的值拷贝到另一个寄存器中。
MRS R0, CPSR @将CPSR中的值拷贝到R0中
MSR:与MRS相反,将一个寄存器中国的值拷贝到特殊功能寄存器中。
MRS CPSR, R0 @将R0中的值拷贝到CPSR
存储访问指令
ARM不能直接访问存储器,比如RAM中的数据;我们如果想配置芯片的寄存器时候,需要借助存储器访问指令,首先将数据放入Rx(0-12)寄存器中,再通过存储器访问指令将Rx寄存器中的数值写出芯片寄存器中;读取芯片寄存器数值也是类似的,只不过就是过程反过来。
常用的寄存器访问指令包括LDR、STR
LDR指令
LDR指令主要用于从存储加载数据到寄存器Rx中(从存储器中读出数据),LDR也可以将一个立即数加载到寄存器Rx中,LDR加载立即数的时候要用“=”而不是“#”。
在嵌入式中,LDR最常用的就是读取CPU的寄存器值。例如:
LDR R0, =0x02020202 @将寄存器地址0x02020202加载到R0中
LDR R1, [R0] @读取地址为0x02020202中的数据到R1寄存器中
STR指令
STR指令与LDR指令相反,STR就是将数据写入到存储器中。例如:
LDR R0, =0x02020202 @将寄存器地址加载到R0中
LDR R1, =0x01010102 @将寄存器地址0x01010102加载到R1
LDR R0, =0x20202020 @R1 保存要写入到寄存器的值,即 R1=0X20000002
STR R1, [R0] @将 R1 中的值写入到 R0 中所保存的地址中
压栈和出栈指令
我们通常会在 A 函数中调用 B 函数,当 B 函数执行完以后再回到 A 函数继续执行。要想再跳回 A 函数以后代码能够接着正常运行,那就必须在跳到 B 函数之前将当前处理器状态保存起来(就是保存 R0~R15 这些寄存器值),当 B 函数执行完成以后再用前面保存的寄存器值恢复R0~R15 即可。
我们称为保护现场。在进行保护现场的时候需要进行压栈(入栈)操作,恢复现场就要进行出栈操作。压栈的指令为PUSH,出栈的指令为POP
示例:
PUSH {R0~R3, R12} @将 R0~R3 和 R12 压栈
压栈完成后效果图
我们在将LR进行压栈操作。
PUSH {LR} @将 LR 进行压栈
LR压栈完成后效果图
出栈示例:
POP {LR} @先恢复 LR
POP {R0~R3,R12} @在恢复 R0~R3,R12
出栈的就是从栈顶,也就是SP当前执行的位置开始,地址依次减小来提取堆栈中的数据直到要恢复的寄存器列表。
跳转指令
多种跳转操作:
- 直接使用跳转指令B、BL、BX。
- 直接向PC寄存器中写入数据
我们常用的是B与BL:
B指令
B指令会将PC(程序计数器)寄存器的值设置为跳转的目标地址,一旦执行B指令,ARM处理器就会立即跳转到指定的目标地址。如果要调用的函数不会再返回原来的执行处,那么就可以用B指令。
_START:
LDR R0, =0x80200000 @liustu
B MAIN @跳转到main函数
上述是典型的示例,从汇编中初始化C运行环境,然后跳转到C文件的主函数中运行。
BL指令
BL指令相比B指令,在跳转之前会在寄存器LR中保存当前PC寄存器值,所以可以将LR寄存器的值重新加载到PC中来继续从跳转之前的代码处运行。
B是只能跳过去,BL是跳过去执行完函数还能调回来
算术运算指令
我们常用加减,乘除基本用不到。