伪指令和指令的区别:只存在于汇编语言中,而不存在于机器语言中。帮助编译器将汇编语言(或者其它高级语言)转换成机器语言(伪指令被编译时被等效的指令替换)。
ARM架构的操作状态有Thumb状态和ARM状态之分(当然还有调试状态):
Thumb状态 ARM状态 指令集 Thumb指令集 ARM指令集 指令长度 16位(半字指令) 32位 指令执行条件 大多数指令无条件执行 大多数指令有条件执行 优点 低功耗,存储空间要求低 代码需要的指令数少,性能高 Cortex-M系列不支持ARM状态,因此本文内容基于Thumb状态进行撰写。
Thumb指令集的操作数和指令地址仍为32位
程序要执行的指令都保存在存储器中(指令转化为机器码,也称操作码)。当计算机需要执行一条指令时,首先产生这条指令的地址,并根据地址号打开相应的存储单元并取出指令代码,最后CPU根据指令代码的要求以及指令中的操作数去执行相应的操作。
后缀 | 描述 |
---|---|
S | 更新APSR(应用程序状态寄存器,如进位、溢出、零和负标志),例如:ADDS R0,R1;该ADD操作会更新APSR |
EQ, NE, CS, CC, MI,PL,VS,VC,HI,LS,GE, LT, GT, LE | 条件执行后缀,若满足相应条件则执行后面的语句,例如:BEQ label;若之前的操作得到相等的状态(状态寄存器Z置位),则跳转至 label ,各个条件码的介绍如条件码介绍表所示 |
.N,.W | 指定使用的是16位指令( narrow)或32位指令(wide) |
.32,.F32 | 指定32位单精度运算,对于多数工具链,32后缀是可选的 |
.64,.F64 | 指定64位单精度运算,对于多数工具链,64后缀是可选的 |
可以通过S后缀的指令影响状态寄存器的标志位,再通过各类条件码后缀执行相应判断
条件码助记符 | 条件码 | 标志 | 含义 |
---|---|---|---|
EQ | 0000 | Z=1 | 相等 |
NE | 0001 | Z=0 | 不相等 |
CS/HS | 0010 | C=1 | 无符号数大于或等于 |
CC/LO | 0011 | C=0 | 无符号数小于 |
MI | 0100 | N=1 | 负数 |
PL | 0101 | N=0 | 正数 |
VS | 0110 | V=1 | 溢出 |
VC | 0111 | V=0 | 没有溢出 |
HI | 1000 | C=1,Z=0 | 无符号数大于 |
LS | 1001 | C=0或Z=1 | 无符号数小于或等于 |
GE | 1010 | N=V | 带符号数大于或等于 |
LT | 1011 | N!=V | 带符号数小于 |
GT | 1100 | Z=0,N=V | 带符号数大于 |
LE | 1101 | Z=1或N!=V | 带符号数小于或等于 |
条件码应用举例:
1、比较两个值大小,C语言代码如下:
if(a > b) a++; else b++;
对应的ARM指令代码如下:(设R0为a,R1为b)
CMP R0, R1 ;R0与R1比较
ADDHI R0,R0,#1 ;若R0 > R1,则R0 = R0 + 1
ADDLS R1,R1,#1 ;若R0 <= R1,则R1 = R1 + 1
2、若两个条件均成立,则将这两个数值相加,C语言代码如下:
if((a != 10)&&(b != 20)) a = a + b;
对应的ARM指令代码为:
CMP R0,#10 ;比较R0是否为10
CMPNE R1,#20 ;若R0不为10,则比较R1是否为20
ADDNE R0,R0,R1 ;若R0不为10且R1不为20,则执行 R0 = R0+R1
3、若两个条件有一个成立,则将这两个数值相加,C语言代码如下:
if((a!=10)||(b!=20)) a=a+b;
对应的ARM指令代码为:
CMP R0,#10
CMPEQ R1,#20
ADDNE R0,R0,R1
Rx、Ry、Rz为寄存器,#num32为小于0xFFFF FFFF(32位)的立即数(即未溢出),ADDR为[地址表达式],Fun_lab表示函数标号,num_lab表示变量或常量标号,cond表示条件码
指令名称 | 语法 | 指令作用 | 注意 |
---|---|---|---|
MOV | MOV Rx,Ry/#num32 |
将源操作数的值赋给目的操作数 | |
MRS | MRS Rx,Rs |
同MOV | 源操作数应为特殊寄存器 |
MSR | MSR Rs,Rx |
同MOV | 目的操作数应为特殊寄存器 |
MOVW | MOVW Rx,#num16 |
将源操作数赋给目的操作数的低16位 | 高位清零 |
MOVT | MOVT Rx,#num16 |
将源操作数赋给目的操作数的高16位 | 低位不变 |
MOV指令传递的立即数应在0~0xFFFF范围内或可以通过8bit连续有效位通过移位或复制全部奇数或偶数字节能得到
数据类型 | 读存储器指令 | 写存储器指令 | 语法 |
---|---|---|---|
32位 | LDR | STR | LDR Rx,ADDR;将地址ADDR上的值赋给Rx STR Rx,ADDR;将Rx的值赋给地址为ADDR的存储空间 |
16位有符号 | LDRSH | 无 | |
16位无符号 | LDRH | STRH | |
8位有符号 | LDRSB | 无 | |
8位无符号 | LDRB | STRB | |
多个32位 | LDM | STM | LDM、STM |
双字(64位) | LDRD | STRD | LDRD/STRD R1,R2,ADDR;从地址ADDR上读出两个字并分别赋给两个寄存器 |
栈操作(32位) | POP | PUSH | PUSH、POP |
LDR R0, =X ;将变量X的地址赋给R0
MOV R1,#0xFFFFBFFF ;R1=0xFFFFBFFF
STRH R1,[R0] ;将R1的低16位值赋给X,X=0xBFFF
LDR R2,[R0] ;R2=0xBFFF
LDRSH R3,[R0] ;R3=0xFFFFBFFF
LDRH R4,[R0] ;R4=0xBFFF
STR R1,[R0] ;将R1的值赋给X(相当于C语言通过指针为变量赋值),X=0xFFFFBFFF
LDR R2,[R0] ;R2=0xFFFFBFFF
LDRSH R3,[R0] ;R3=0xFFFFBFFF
LDRH R4,[R0] ;R4=0xBFFF
有符号读和无符号读的区别在于,同样的数据0x83,通过LDRB读取是0x0000 0083,通过LDRSB指令读取是0xFFFF FF83,即无符号读将对应8位数据进行符号位扩展
LDR等指令的操作数为立即数时,范围为±4095
LDR不一定是指令,当使用格式为
LDR 寄存器,=立即数
时为伪操作,作用是将该立即数赋给寄存器,与MOV指令的区别在于,MOV指令赋的立即数有限制
数据传输使用的存储器地址为:寄存器中的数值+立即数常量(偏移地址)
LDRB R0,[R1,#0x3];从地址R1+0x3中读取一个字节并将其存入R0
加入感叹号(!)可更新存放地址的寄存器的值(写回):
LDRB R0,[R1,#0x3]!;从地址R1+0x3中读取一个字节并将其存入R0后令R1=R1+0x3
可以使用寄存器R15(PC)作为寄存器,PC寄存器的值为当前指令地址
类似立即数偏移,但这里的寄存器可以通过移位指令进行移位:
LDR R3,[R0, R2, LSL #2];将存储器[R0+(R2<<2)]读入R3
LDR R3,[R0, R2];将存储器[R0+R2]读入R3
注意:这里进行的是前序偏移,也就是以地址偏移后的值为地址进行取值,下面介绍一下后序寻址:
后序寻址是取地址上的值,后进行地址偏移:
LDR R0, [R1], #offset;读取存储器[R1],然后R1被赋值为R1+偏移
后序寻址不能使用R14(SP)或R15(PC)。
介绍:连续取出一个地址上连续的多个数据
LDM和STM可添加相应后缀以控制地址增长方向及地址变化方式,可添加后缀如下表所示。
可添加后缀 | 作用 | 等效后缀 |
---|---|---|
IB | 地址增加后完成操作 | FA |
IA | 完成操作后地址增加 | EA |
DB | 地址减少后完成操作 | FD |
DA | 完成操作后地址减少 | ED |
注意,在Cortex-M4上似乎只能使用IA和DB后缀,另外两个可能需要ARM状态。
指令 | 作用 | 注意事项 |
---|---|---|
LDMIA(/LDMEA) | 以目的操作数的值为地址,读取多个值(数量和源操作数的数量一致) | “IA/EA”后缀表示操作后地址增加 |
LDMDB | 同“LMDIA” | “DB/FD”后缀代表操作后地址减小 |
STMIA | 以目的操作数的值为地址,写入多个值(数量和源操作数的数量一致) | “IA”后缀表示操作后地址增加 |
STMDB | 同“STMIA” | “DB”后缀代表操作后地址减小 |
MOV R1,#0xFFFFAFFF
STR R1,[R0]
MOV R1,#0xFFFFBFFF
STR R1,[R0,#4]
MOV R1,#0xFFFFCFFF
STR R1,[R0,#8]
MOV R1,#0xFFFFDFFF
STR R1,[R0,#12]
LDMIA R0,{R5-R8} ;R5~R8分别是0xFFFFAFFF、B、C、D
ADD R0,#16
LDMDB R0,{R1-R4} ;R1~R4分别是0xFFFFAFFF、B、C、D
注意:地址增加/减少最终并不会影响目的操作数的值,即不会“写回”。
源操作数应为多个寄存器,格式如下:
1.以“{”为开始,“}”为结束。
2.可以使用“-”表示范围:R2-R5表示R2、R3、R4、R5四个寄存器。
3.通过“,”将各个寄存器隔开。
注意:{R1-R4}、{R1,R2,R3,R4}、{R4,R3,R2,R1}三者等效,顺序都是R1 ~ R4
加入感叹号(!)可更新存放地址的寄存器的值(写回):
LDMIA R0!,[R1,R3-R5];从地址R0中读取4个字并将其分别赋给R1、R3、R4、R5四个寄存器后令R0=R0+4
压栈指令PUSH和出栈指令POP的操作数可以为多个寄存器,格式如:多寄存器格式
PUSH {R4-R6, LR} ;在子程序开始处将R4-R6和LR(链接寄存器)中的值入栈
POP {R4-R6, PC} ;从栈中恢复R4-R6和返回地址,返回地址存入PC以返回子程序
指令名称 | 语法 | 指令作用 | 注意 |
---|---|---|---|
ADD | ADD Rx,Ry/#num32 ADD Rx,Ry,Rz/#num32 |
Rx=Rx+Ry Rx=Ry+Rz 加法运算 |
源操作数可以为立即数,立即数限制与MOV同:限制 |
ADDW | ADD Rx,#num12 ADD Rx,Ry,#num12 |
同ADD | 源操作数至少有一个是立即数,且不超过12位。 |
ADC | 同ADD | Rx=Rx+Ry+CF Rx=Ry+Rz+CF ,带进位的加法运算 |
在ADD的基础上加上进位标志位的值(0/1) |
SUB | 同ADD | 减法运算 | 可添加W后缀(SUBW) |
SBC | 同ADD | 带借位的减法运算 | 在SUB的基础上减去借位标志位的值(0/1) |
RSB | 同ADD | Rx=Ry-Rx Rx=Rz-Ry 减法反转,结果为-SUB |
|
MUL | 同ADD | 32位乘法 | |
UDIV | 同ADD | 无符号除法 | |
SDIV | 同ADD | 有符号除法 |
注意:许多数据处理指令会有多种形式,形式差别包括操作数数量(2 ~ 3)以及操作数种类(寄存器、立即数、存储器),以ADD为例:
操作数数目为2:ADD Rx,Ry;Rx=Rx+Ry
操作数数目为3:ADD Rx,Ry,Rz;Rx=Ry+Rz
如果具有两个源操作数,这不可都为立即数,且立即数不可为第一源操作数。
指令名称 | 语法 | 指令作用 |
---|---|---|
AND | AND Rx,Ry/#num AND Rx,Ry,Rz/#num |
按位进行与运算 |
ORR | 同AND | 按位进行或运算 |
BIC | 同AND | Rx=Rx&(~Ry) Rx=Ry&(~Rz) 进行非与操作 |
ORN | 同AND | 进行或非操作 |
EOR | 同AND | 进行异或操作 |
MVN | AND Rx,Ry/#num |
进行取反操作(位非) |
逻辑运算的立即数应是可以通过8bit连续有效位通过移位或复制全部奇数或偶数字节能得到的立即数。
指令名称 | 语法 | 指令作用 | 注意 |
---|---|---|---|
ASR | ASR Rx,Ry/#num ASR Rx,Ry,Rz/#num |
Rx=Rx< |
右移指定位数,并往左侧补原符号位 |
LSL | 同ASR | 逻辑左移 | 左移指定位数,并往右侧补0 |
LSR | 同ASR | 逻辑右移 | 右移指定位数,并往左侧补0 |
ROR | 同ASR | 循环右移 | 右移x位=左移32-x位 |
RRX | RRX Rx,Ry |
将进位标志并在最右侧后进行循环移动一位 |
移位指令使用的立即数范围为1 ~ 32
对于上述移位指令,更加常用的用法是,在进行数据操作时进行移位:
指令名 Rx,Ry,移位指令名 #num/Rz
,如
MOV R0,Ry,LSL #2
注意:RRX作为移位指令名时,不需要立即数或寄存器作为移位位数
图解:
指令名称 | 语法 | 指令作用 |
---|---|---|
SXTB | SXTB Rx,Ry |
将源操作数[7,0]有符号地展开 |
SXTH | 同SXTB | 将源操作数[15,0]有符号地展开 |
UXTB | 同SXTB | 将源操作数[7,0]无符号地展开 |
UXTH | 同SXTB | 将源操作数[15,0]无符号地展开 |
举例:
MOV R0,#0xBB ;R0=0x0000 00BB
SXTB R1,R0 ;R1=0xFFFF FFBB
SXTH R1,R0 ;R1=0x0000 00BB
UXTB R1,R0 ;R1=0x0000 00BB
UXTH R1,R0 ;R1=0x0000 00BB
可在源操作数后加入{,ROR #n}进行先移位后展开,n的取值为0、8、16、24,且仅能ROR
指令名称 | 语法 | 指令作用 |
---|---|---|
REV | REV Rx,Ry |
反转字中的字节,如图解所示 |
REV16 | 同REV | 反转每个半字中的字节 |
REVSH | 同REV | 反转低半字中的字节并将结果有符号展开 |
举例:
MOV R1,#0x01
MOV R0,R1,LSL #24
ADD R1,#1
ADD R0,R1,LSL #16
ADD R1,#1
ADD R0,R1,LSL #8
ADD R1,#1
ADD R0,R1 ;R0=0x0102 0304
REV R2,R0 ;R2=0x0403 0201
REV16 R3,R0 ;R3=0x0201 0403
REVSH R4,R0 ;R4=0x0000 0403
指令名称 | 语法 | 指令作用 |
---|---|---|
BFC | BFC Rx,#num_1,#num_2 |
将寄存器Rx中num_1到num_1+num_2-1对应的位清零,即将寄存器对应位清零,num_1为清零的最低位,num_2为清零宽度 |
BFI | BFI Rx,Ry,#num_1,#num_2 |
寄存器Ry中的0到num_2-1位赋给R1的num_1到num_1+num_2-1对应的位 |
CLZ | CLZ Rx,Ry |
将源操作数中从31位开始连续为0的个数赋给目的操作数,即计算前导零的个数 |
RBIT | RBIT Rx,Ry |
将源操作数按位反转,即0和31位互换,1和30位互换… |
SBFX | SBFX Rx,Ry,#num_1,#num_2 |
将寄存器Ry中的num_1到num_1+num_2-1对应的位有符号的展开并赋给Rx:举例 |
UBFX | 同SBFX | 同SBFX,但将对应的位无符号展开 |
LDR R0, =0x5678ABCD
UBFX R1,R0,#4,#8;将R0的4~11位无符号展开后赋给R1,即R1=0x0000 00BC
SBFX R1,R0,#4,#8;将R0的4~11位有符号展开后赋给R1,即R1=0xFFFF FFBC
注意,这里的LDR是LDR伪指令,与LDR指令不同,=表示将后面的值赋给寄存器R0
这里LDR伪指令和MOV指令的区别是,MOV指令并不能传递所有的32位立即数,具体限制:MOV立即数限制。
指令名称 | 语法 | 指令作用 |
---|---|---|
CMP | CMP Rx,Ry/#num |
计算Rx-Ry/#num,并更新APSR寄存器 |
CMN | 同CMP | 计算Rx+Ry/#num,并更新APSR寄存器 |
TST | 同CMP | 计算Rx&Ry/#num(按位与),并更新APSR寄存器的N和Z位 |
TEQ | 同CMP | 计算Rx⊕Ry/#num(按位异或),并更新APSR寄存器的N和Z位 |
这4个指令使用的立即数同样有限制,同MOV:具体限制:MOV立即数限制。
上述计算的结果不会保存,如CMP指令,不会把Rx-Ry的差值赋给Rx
引起跳转操作的指令:
- 跳转指令
- 更新R15(PC)的数据处理指令(MOV、ADD等)
- 写入PC的读存储器指令(LDR、LDM、POP等)
指令名称 | 语法 | 指令作用 |
---|---|---|
B | B label |
跳转到标号对应的地址,属于相对跳转(会计算标号和当前PC的差),跳转范围为±2KB(可添加.W后缀使用32位版本的指令) |
BX | BX Rx |
跳转到存放于寄存器Rx中的地址值,并基于Rx第0位设置处理器执行状态(Cortex-M只支持Thumb状态,因此第0位必须为1) |
指令名称 | 语法 | 指令作用 |
---|---|---|
BL | BL label |
跳转到标号位置并将返回地址保存到链接寄存器R14(LR)中 |
BLX | BLX Rx |
跳转到存放于寄存器Rx中的地址值并将返回地址保存到LR中,以及更新EPSR中的T位为Rx的最低位 |
程序计数器R15(PC)为跳转目标地址(即将标号/地址赋给PC)
返回地址即BL/BLX指令后的指令的地址
由于Cortex-M只支持Thumb状态,因此使用BLX指令时,Rx的第0位必须为1
函数调用和标号跳转的区别在于,函数调用需要将返回地址保存,这也是BL和BLX与B和BX的区别
通过指令改变标志寄存器后根据条件码运行程序。
指令格式:B(cond) label
/B(cond).W label
B(cond)表示指令B和条件码cond相连,中间无空格
各个条件码如条件码助记符所示。
举例:
CMP R0,#2 ;比较R0和1的值,并根据结果修改应用程序状态寄存器APSR
BEQ LOOP ;注意,BEQ是B和EQ结合,但APSR的Z位置位,即符合条件码EQ,则跳转到LOOP对应的位置
可用于if语句之类的分支选项
指令名称 | 语法 | 指令作用 |
---|---|---|
CBZ | CBZ Rx label |
当Rx为0时跳转到标号对应的位置 |
CBNZ | 同CBZ | 当Rx不为0时跳转到标号对应的位置 |
标号的相对偏移量应该在0x00 ~ 0x7E内
可用于循环中的条件判断
指令不影响APSR的值
指定下面语句执行的条件
语法:
IT(T/E)(T/E)(T/E) cond;(T/E)数量为0~3,决定下面指令的数量
ins_1(cond) ;若数量为0,则下面不需要写,ins_1表示指令,这里的cond和上一行的cond相同
ins_2(cond/~cond) ;若数量为1,则需要写到这一句(当然下面的不用写),当第一个(T/E)为T时,这里的cond和第一行的cond相同,为E时相反
ins_3(cond/~cond) ;
ins_4(cond/~cond) ;
举例:
ITETT NE
ADDNE R0, R0, R1
ADDEQ R0, R0, R3
ADDNE R2, R4, #1
MOVNE R5, R3
指令名称 | 语法 | 指令作用 |
---|---|---|
TBB | TBB语法及示例 | 进行字节跳转(偏移),偏移范围为0 ~ 512(2*2^8)字节 |
TBH | TBH语法及示例 | 进行字节跳转(偏移),偏移范围为0 ~ 128k(2*2^16)字节 |
LDR R0, =1 ;偏移量
TBB [PC, R0] ;TBB语法,其中PC可以替换为其它寄存器,一般用PC实现跳转,R0为偏移量,该语句执行后,(由于处理器特性),PC=PC+4,即指向下面的语句(这里是标号Table_start)
Table_start ;跳转表起始,指向该标号后,PC=PC+R0(偏移量),即跳转到下面的DCB语句进行赋值
DCB ((Dest0-Table_start)/2);数据为8位因此使用DCB,作用:为PC赋值,进而跳转到对应的标号。
DCB ((Dest1-Table_start)/2);由于R0=1,PC会执行到此语句,之后PC=Table_start+2*((Dest1-Table_start)/2)=Dest1,进而跳转到对应的编号
DCB ((Dest2-Table_start)/2)
DCB ((Dest3-Table_start)/2)
Dest0
LDR R1, =0
B Table_end
Dest1 ;即跳转到这里执行
LDR R1, =1
B Table_end ;执行后跳出
Dest2
LDR R1, =2
B Table_end
Dest3
LDR R1, =3
Table_end
LDR R0, =3 ;偏移量
TBH [PC, R0, LSL #1] ;同TBB,“LSL #1”不可更换
Table_start
DCI ((Dest0-Table_start)/2);数据为16位因此使用DCI
DCI ((Dest1-Table_start)/2)
DCI ((Dest2-Table_start)/2)
DCI ((Dest3-Table_start)/2)
Dest0
LDR R1, =0;
B Table_end
Dest1
LDR R1, =1
B Table_end
Dest2
LDR R1, =2
B Table_end
Dest3
LDR R1, =3
Table_end
上述的TBB和TBH实际上实现的是表格跳转,使用PC时可以实现分支选择,即c语言中的switch语句
指令名称 | 语法 | 指令作用 |
---|---|---|
SSAT | SSAT Rx,#num,Ry |
将Ry局限到-2 ^(num-1)-1 ~ 2 ^(num-1)并将该值赋给Rx |
USAT | USAT Rx,#num,Ry |
将Ry局限到0 ~ 2 ^(num-1)并将该值赋给Rx |
注意,num为位数限制,因此1 <= num <= 32
Ry后可加位移运算,但仅局限于ASR与LSL
示例:
LDR R0,=0x00020000
SSAT R1,#16,R0 ;R1=0x00007FFF
SSAT R1,#17,R0 ;R1=0x00020000
USAT R1,#16,R0 ;R1=0x00008000
USAT R1,#17,R0 ;R1=0x00020000
LDR R0,=0xFFF20000
SSAT R1,#16,R0 ;R1=0xFFFF8000
USAT R1,#16,R0 ;R1=0x00000000
指令名称 | 语法 | 指令作用 |
---|---|---|
SVC | SVC num |
触发SVC中断,可用于执行状态的切换。num为优先级,范围为0x00 ~ 0xFF,并且该立即数前面可不加"#" |
CPS | CPSIE/CPSID I/F |
"IE"后缀用于使能中断,"ID"后缀用于禁止中断 |
指令名称 | 语法 | 指令作用 |
---|---|---|
WFI(Wait for interrupt) | WFI |
进入休眠(中断可唤醒) |
WFE(Wait for event) | WFE |
进入休眠,等待事件(中断、复位、外部输入等)发生后唤醒 |
SEV | SEV |
发送事件 |
指令名称 | 语法 | 指令作用 |
---|---|---|
DMB | DMB |
数据存储器屏障。确保在执行新的存储器访问前所有的存储器访问都已经完成 |
DSB | DSB |
数据同步屏障。确保在下一条指令执行前所有的存储器访问都已经完成 |
ISB | ISB |
指令同步屏障。清空流水线,确保在执行新的指令前,之前所有的指令都已完成 |
应用场景:
指令名称 | 语法 | 指令作用 |
---|---|---|
NOP | NOP |
空语句,无任何功能,可用于指令对齐或延时 |
BKPT | BKPT num |
num立即数,且此时其前面可不加"#",大小为0x00 ~ 0xFF。该指令的作用是实现软件断点,特殊的num值会使处理器执行某些动作 |
本文基于keil5,芯片STM32F429IGx撰写,参考书籍:《ARM Cortex-M3与Cortex-M4权威指南》