嵌入式分享——汇编学习基础总结
本文最后更新于 113 天前,其中的信息可能已经有所发展或是发生改变。

为什么要学汇编?

汇编语言是一个面对机器的程序设计语言,这么一个古老的语言,没什么我们还要去学?在接触裸机开发之后我知道了。

我之前经常使用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是跳过去执行完函数还能调回来

算术运算指令

我们常用加减,乘除基本用不到。

逻辑运算指令

如果您觉得这篇文章不错,且手里较为宽裕,可以支持一下博主,一分也是缘分😊
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇