Skip to content

ARM处理器汇编语言

· 20 min · 嵌入式开发 / 汇编语言 / ARM / 学习笔记

Thumb状态切换#

Thumb是16位指令的状态,可以使用16位指令,在进行切换时不会改变通用寄存器。

;现在是ARM指令集
MOV R0,#1 ;将1放进r0寄存器
BX R0 ;跳转的最低位是1,ARM指令集下地址最低位都是0,也就是从ARM指令集跳到Thumb指令集
;现在是Thumb指令集
MOV R0,#0 ;将0放进r0寄存器
BX R0 ;跳转的最低位是0,Thumb指令集下地址最低位都是1,也就是从Thumb指令集跳到ARM指令集

16位Thumb地址一般位于奇数地址,所以跳转到1就进入了Thumb状态。

ARM工作模式切换#

接下来的都是特权模式,可以对CPSR寄存器所有的位直接进行读写,而用户模式只能进行间接访问,如使用MRS指令将CPSR寄存器读入另一个寄存器。

除了系统模式之外的5种特权模式,都是异常模式,异常模式只有在对应的异常发生时才会进入对应的模式。

在发生异常或者中断时,会有一个异常向量表或者中断向量表

+++

ARM寄存器#

ARM一共有37个用户可见寄存器。

通用寄存器#
状态寄存器#

最后五位是处理器工作模式。可以通过修改这五位进行工作模式的改变。

**N(Negative)**是否为负数,N=1就代表结果为负数。

Z(Zero) Z=1表示运算的结果为零。

C(Carry) 加法指令中结果产生了进位,则C=1,反之则为0;减法指令中发生了借位则C=0,没有借位则为1;移位操作中,C是最后被移出的位。

V(Overflow) 在有符号数的加减法指令中,发生了符号位溢出,则V=1

在通用寄存器中R0-R12是完全的通用寄存器,不会被用来做特殊用途,所有的针对普通寄存器的指令都可以用于他们。

特殊寄存器的操作#

往后看,有堆栈寻址的详细解释。堆栈往往用来实现函数跳转等操作。

执行BL、BLX指令时,会将PC-4(也就是要执行的下一条指令)的值自动记录到LR中,以便函数返回时将LR的值送回PC。但是在其他跳转时并不会自动记录,而是需要手动记录PC-4并存储。

一般可以这样用,也就是进行多重嵌套时,将LR压栈才能进入下一层嵌套,回到上一层嵌套就将寄存器出栈。

STMFD SP!, {<registers>, LR}
LDMFD SP!, {<registers>, PC}

在处理异常时会将异常处理的返回地址放在对应模式下的LR寄存器中,但是这个返回地址应该是发生异常代码的下一条代码也就是PC+4,而在返回时应该去执行发生异常时正在执行的那条代码,因为那条代码并没有执行完,也就需要LR减去一个偏移量。在发生异常时,所有的跳转地址都是自动配置好的,不需要我们程序员来关心。

​ arm采用了三级流水线的结构,每一条指令的执行都会经过取指、译码、执行三个阶段,所以正在执行的代码往后两条指令是正在取指的代码,也就是说PC的值是当前正在执行的代码地址+8。

+++

ARM存储器体系#

ARM的存储器支持大端模式和小端模式,默认是小端模式,也就是数据的低位存储在低地址内存单元中。

ARM处理器的ROM一般是FLASH,所有程序都存储在FLASH中,分为片内和片外两种。

ARM外设有统一的存储器映射编址,也就是访问对应的内存单元就可以访问特定外设的寄存器。这些内存地址不会被缓存。

STM32存储架构详解

+++

指令#

ARM汇编指令遵循着相对固定的格式,如**< opcode > {< cond >} {S}< Rd >,< Rn > {,< OP2 >}**。

在其中

条件执行的条件码和对应的条件

寻址#
MOV r1, r2 ;将r2的值传到r1
ADD r1, r2, r3 ;将r2的值加上r3的值并赋给r1
MOV r2, r1, LSL #3 ;将r1<<3位并赋给r1
ADD r3, r2, r1, LSL #3 ;将r1<<3 + r2赋给r3
ADD r3, r2, r1, LSL r0 ;将r1<<r0 + r2赋给r3
LDR r1, [r2] ;以r2中的内存地址加载数据进r1
STR r1, [r2] ;将r1中的值写入r2寄存器中的内存地址
LDR R1, [R0, #2] ;(前变址,不修改基址)将R0中的值加2作为新地址
LDR R1, [R0, #2]! ;(自变址,修改基址)从r0 + 2的位置加载完数据后,R0寄存器的值会增加2。
LDR R1, [R0, R2] ;(前变址,不修改基址)将R0+R2作为新地址
LDR R1, [R0, R2, LSL #2] ;(前变址,不修改基址)将R0+R2<<2作为新地址
LDR R1, [R0], #2 ;(后变址,修改基址)从r0的位置加载完数据后,R0寄存器的值会增加2。与上述第二条不同。
STR R1, [R0, #-2] ;(前变址,不修改基址)将R0中的值减2作为新地址。
STR R1, [R0], #-2 ;(后变址,修改基址)将R0中的值作为地址,将R1中的值写入该地址,然后R0寄存器值减2
常用指令#

基础指令

位操作指令

操作内存指令

当偏移地址为非字对齐时,会自动舍弃掉不满一个字的部分。半字同理

LDMIA R0, {R1-R5} ;以ro为基址读取五个数据单元加载到r1-r5
STMIA R6, {R1-R5} ;以r6为基址,将r1-r5的数据写入

多寄存器寻址和堆栈寻址时,需要加上不同的后缀,向不同的寻址。

IA 操作后递增
IB 操作前递增
DA 操作后递减
DB 操作前递减

堆栈寻址时,堆栈指针指向的是栈顶元素就是满栈,堆栈指针指向栈顶元素的下一个元素就是空栈。

每入栈一个元素,栈指针SP都会往栈增长的方向移动一个存储单元。如果栈指针SP从高地址往低地址移动,那么这个栈就是递减栈;如果栈指针SP从低地址往高地址移动,那么这个栈就是递增栈,ARM处理器使用的一般都是满递减堆栈。

SP!代表着在操作之后递增SP指针。

FA 满递增堆栈
FD 满递减堆栈
EA 空递增堆栈
ED 空递减堆栈
STMFD SP! {R0,R1,R2} ;会自动将高位寄存器先入栈
LDMFD SP! {R0,R1,R2} ;按照相对应的顺序出栈

伪指令#

声明全局变量

GBLA amount ;定义一个全局数字变量,默认值为0
GBLL logic ;定义一个全局逻辑变量,默认值为0
GBLS string ;定义一个全局字符串变量,默认值为null

声明局部变量

LCLA amount ;定义一个局部数字变量
LCLL logic ;定义一个局部逻辑变量
LCLS string ;定义一个局部字符串变量

变量赋值

amount SETA 10 ;给amount赋值10
logic SETL 1 ;给logic赋值1
string SETS "你好" ;给string赋值"你好"

定义寄存器列表

RegList RLIST {R0-R4} ;将R0到R4定义为RegList
STMFD sp!, RegList ;将R0到R4寄存器入栈

数据定义

LTORG ;在代码段最后或者无条件跳转后面或者子程序返回指令,定义数据缓冲池,存储常量信息
MAP 0x40000000 ;一个段中只能有一个map,定义map的基地址
A FIELD #4 ;定义A占4个字节,A的起始位置就是MAP的地址
B FIELD #4 ;定义B占4个字节,B的起始位置就是MAP加4
string DCB "hello" ;分配一些字节单元给标号string
DCD ;分配字单元,字对齐
DCDU ;分配子单元,非字对齐

程序控制

IF 逻辑表达式
程序段1
ELSE
程序段2
ENDIF
WHILE 逻辑表达式
程序段
WEND

其他伪指令

ALIGN 4,3 ;4个字节对齐并且在上一个位置之后偏移3个字节
AREA 段名,CODE,READWRITE ;段定义
CODE16 ;16位指令
CODE32 ;32位指令
ENTRY ;程序入口
END ;
EXPLORT ;和GLOBAL一样,向外部声明
IMPORT ;引入其他源文件中的符号并加入符号表
EXTERN ;引入其他源文件中的符号但是如果没有调用,就不会加入符号表
EQU ;将常量和标号等效另一个名字
GET ;和INCLUDE一样,包含源文件
ADR ;取一个标号的地址,在非字对齐255字节之内,字对齐1020之内
ADRL ;和ADR一样但是范围不同,在非字对齐64k之内,字对齐256k之内
LDR ;取地址,但是后面要加一个=,全局范围读取

宏

MACRO
$MyMacro MacroName $value1,$value2
LDR R1,#3
LDR R0,#0
$MyMacro.LOOP
SUB R1,#1
CMP R1,R0
BGT LOOP
MEND

运算符

x + y ;相加
x - y ;相减
x * y ;相乘
x / y ;整除
x :MOD: y ;取余
x :ROL: y ;循环左移
x :ROR: y ;循环右移
x :SHL: y ;左移
x :SHR: y ;右移
x :AND: y ;按位与
x :OR: y ;按位或
:NOT: y ;按位取反
x :EOR: y ;按位异或
;逻辑表达式
x = y
x > y
x < y
x >= y
x <= y
x /= y ;不等于
x <> y ;不等于
x :LAND: y ;逻辑与
x :LOR: y ;逻辑或
x :LEOR: y ;逻辑异或
:LNOT: y ;逻辑取反
:LEN: x ;取长度(字符长度)
:CHR: x ;将ASCII码转成字符