公众号:嵌入式不难
本文仅供参考学习,如有错误之处,欢迎留言指正。
伪指令
DCB指令
- DCB 用于分配一片连续的字节存储单元并用指定的数据初始化。
- 语法格式:
- (标号) DCB 操作数1, 操作数2…
- 标号确定数据的地址(标号可有可无)
- DCB 也可用“=”代替。
- 操作数可为0x00~0xFF的数字,也可为字符串。
- 举例:
Str DCB “This is a test”
- 解析:分配一片连续的字节存储单元并初始化为字符串。
- 语法格式:
DCW(DCWU)指令
- DCW 用于分配一片连续的半字存储单元并用指定的数据初始化。
- 语法格式:
- (标号) DCW 操作数1, 操作数2…
- 标号确定数据的地址(标号可有可无)
- 操作数为0x0000~0xFFFF的数字
- 举例:
DataTest_1 DCW 1 ,2 ,3 ;分配三个连续的半字存储单元并初始化。
- 解析:其中DataTest_1 指向存储单元内容为1的地址,DataTest_1 后紧接的第一个半字存储单元存储的数据为2,DataTest_1 后紧接的第二个半字存储单元存储的数据为3。
- note:DCWU语法同上,与DCW的区别在于由此命令分配的内存单元可不连续。
- 语法格式:
DCD (DCDU)指令
- DCD 用于分配一片连续的字存储单元并用指定的数据初始化。
- 语法格式:
- (标号) DCD 操作数1, 操作数2…
- 标号确定数据的地址(标号可有可无)
- 操作数为0x0000 0000~0xFFFF FFFF的数字
- 举例:
DataTest_4 DCD 4,5 ,6 ;分配三个连续的字存储单元并初始化。
- 解析:其中DataTest_4 指向存储单元内容为4的地址,DataTest_4 后紧接的第一个字存储单元存储的数据为5,DataTest_4 后紧接的第二个字存储单元存储的数据为6。
- note:DCDU语法同上,与DCD 的区别在于由此命令分配的内存单元可不连续。
- 语法格式:
DCQ (DCQU)指令
- DCQ 用于分配一片连续的双字存储单元并用指定的数据初始化。
- 语法格式:
- (标号) DCQ 操作数1, 操作数2…
- 标号确定数据的地址(标号可有可无)
- 操作数为0x0000 0000 0000 0000~0xFFFF FFFF FFFF FFFF的数字
- 举例:
DataTest_100 DCQ 100 ;分配一个的字存储单元并初始化为100。
- 解析:其中DataTest_100 指向存储单元内容为100的地址。
- note:DCQU语法同上,与DCQ 的区别在于由此命令分配的内存单元可不连续。
- 语法格式:
DCFS(DCFSU)指令
- DCFS用于分配一片连续的单精度浮点存储单元并用指定的数据初始化。
- 语法格式:
- (标号) DCFS 操作数1, 操作数2…
- 标号确定数据的地址(标号可有可无)
- 操作数为单精度浮点数(精度与c中float操持一致)
- 举例:
DataTest_f DCFS 0.1 ;分配一个的字存储单元并初始化为0.1
- 解析:其中DataTest_f 指向存储单元内容为0.1的地址。
- note:DCFSU语法同上,与DCFS 的区别在于由此命令分配的内存单元可不连续。
- 语法格式:
DCFD(DCFDU)指令
- DCFD用于分配一片连续的双精度浮点存储单元并用指定的数据初始化。
- 语法格式:
- (标号) DCFD 操作数1, 操作数2…
- 标号确定数据的地址(标号可有可无)
- 操作数为双精度浮点数(精度与c中double操持一致)
- 举例:
DataTest_d DCFD 0.001 ;分配一个的字存储单元并初始化为0.001。
- 解析:其中DataTest_d指向存储单元内容为0.001的地址。
- note:DCFDU语法同上,与DCFD 的区别在于由此命令分配的内存单元可不连续。
- 语法格式:
DCI指令
- 在ARM 代码中,DCI 用于分配一段字节的内存单元,用指定的数据expr 初始化。指定内存单元存放的是代码,而不是数据。在Thumb 代码中,DCI 用于分配一段半字节的内存单元,用指定的数据expr 初始化。指定内存单元存放的是代码,而不是数据。
- 语法格式:
- (标号) DCI expr
- 标号为内存块起始地址标号
- expr 可为数字表达式。
- DCI 伪指令和DCD 伪指令非常类似,不同之处在于DCI 分配的内存中的数据被标识为指令。可用于通过宏指令业定义处理器不支持的指令。
- 举例:
MACRO ;宏定义(定义NEWCMN Rd,Rn 指令) NEWCMN $Rd,$Rm ;宏名为NEWCMN,参数为Rd 和Rm DCI 0xe16a0e20:OR:($Rd:SHL:12):OR:$Rm MEND
- 语法格式:
SPACE指令
- SPACE 用于分配一块内存单元并初始化为0。
- 语法格式:
- (标号) SPACE 操作数
- 标号确定数据的地址(标号可有可无)
- SPACE 也可用“%”代替。
- 操作数为需要分配的内存字节数。
- 举例:
DataBuf SPACE 1000
- 解析:分配1000 字节空间。
- 语法格式:
AREA指令
- AREA 伪指令用于定义一个代码段或数据段。ARM 汇编程序设计采用分段式设计,一个ARM 源程序至少需要一个代码段,大的程序可以包含多少个代码段及数据段。
- 伪指令格式:
- AREA sectionname{,attr}{,attr}…
- sectionname 所定义的代码段或数据段的名称。如果该名称是以数据开头的,则该名称必须用“|”括起来,如|1_datasec|。还有一些代码段具有的约定的名称。如|text|表示C 语言编译器产生的代码段或者与C 语言库相关的代码段。
- attr 该代码段或数据段的属性。在AREA 伪指令中,各属性之间用逗号隔开。
- 以下为段属性及相关说明:
- ALIGN = expr。默认的情况下,ELF 的代码段和数据段是4 字节对齐的,expr 可以取0~31 的数值,相应的对齐方为2^expr 字节对齐。如expr=3 时为字节对齐。对于代码段,expr 不能为0 或1。
- ASSOC = section。指定与本段相关的ELF 段。任何时候连接section 段也必须包括sectionname 段。
- DODE 为定义代码段。默认属性为READONLY。
- COMDEF 定义一个通用的段。该段可以包含代码或者数据。在其它源文件中,同名的COMDEF 段必须相同。
- COMMON 定义一个通用的段。该段不包含任何用户代码和数据,连接器将其初始化为此。各源文件中同名的COMMON 段共用同样的内存单元,连接器为其分配合适的尺寸。
- DATA 为定义段。默认属性为READWRITE。
- NOINIT 指定本数据段仅仅保留了内存单元,而没有将各初始写入内存单元,或者将内存单元值初始化为0。
- READONLY 指定本段为只读,代码段的默认属性为READONLY。
- READWRITE 指定本段为可读可写。数据段的默认属性为READWRITE。
- 使用AREA 伪指令将程序分为多个ELF 格式的段,段名称可以相同, 这时同名的段被放在同一个ELF 段中。
- 举例:
AREA Example ,CODE,READNOLY ;声明一个代码,名为Example
- 伪指令格式:
指令后缀
[S]后缀
- 描述:更新APSR(应用程序状态寄存器,如进位,溢出,零和负标志)
- 举例:
ADDS R0,R1 ;该操作会更新APSR
[EQ,NE,CS,CC,MI,PL,VS,VC,HI,LS,GE,LT,GT,LE]后缀
- 描述:指令带上这些后缀均为条件执行,可用于条件跳转
- EQ=等于
- NE=不等于
- LT=小于
- GT=大于
- 举例:
BEQ label ;若之前的操作得到相等的状态,则跳转至label
ADDEQ R0,R1,R2 ;若之前的操作得到的是相等的状态,则执行加法运算
[.N,.W]后缀
- 描述:指定使用的是16位指令(narrow)或32位指令(wide)
[.32,.F32]后缀
- 描述:指定32位单精度运算
[.64,.F64]后缀
- 描述:指定64位双精度运算
处理器内传送数据
处理器内传送数据的指令
指令 | 目的 | 源 | 操作 |
---|---|---|---|
MOV | R4, | R0 | ;从R0复制数据到R4 |
MOVS | R4, | R0 | ;从R0复制数据到R4,且更新APSR(标志) |
MRS | R7, | PRIMASK | ;将数据从PRIMASK(特殊寄存器)复制到R7 |
MSR | CONTROL, | R2 | ;将数据从R2复制到CONTROL(特殊寄存器) |
MOV | R3, | #0x34 | ;设置R3为0x34 |
MOVS | R3, | #0x34 | ;设置R3为0x34,且更新APSR(标志) |
MOVM | R6, | #0x1234 | ;设置R6为16位常量0x1234 |
MOVT | R6, | #0x8765 | ;设置R6的高16位为0x8765 |
MVN | R3, | R7 | ;将R7中数据取反后送至R3 |
浮点单元与内核寄存器间传送数据的指令
指令 | 目的 | 源 | 操作 |
---|---|---|---|
VMOV | R0, | S0 | ;将数据从浮点寄存器S0复制到通用目的寄存器R0 |
VMOV | S0, | R0 | ;将数据从通用目的寄存器R0复制到浮点寄存器S0 |
VMOV | S0, | S1 | ;将数据从浮点寄存器S1复制到浮点寄存器S0(单精度) |
VMRS.F32 | R0, | FPSCR | ;将数据从浮点单元系统寄存器FPSCR复制到R0 |
VMRS | APSR_nzcv, | FPSCR | ;复制FPSCR中的标志到APSR 中的标志 |
VMSR | FPSCR , | R3 | ;复制R3到浮点单元系统寄存器FPSCR |
VMOV.F32 | S0, | #1.0 | ;将单精度数据送到浮点单元寄存器S0 |
算术运算
算术数据运算指令
常用算术指令(可选后缀未列出来) | 描述 | 操作 |
---|---|---|
ADD Rd,Rn,Rm | ;Rd = Rn + Rm | 加法运算 |
ADD Rd,Rn,#immed | ;Rd = Rn + #immed | 加法运算 |
ADC Rd,Rn,Rm | ;Rd = Rn + Rm + 进位 | 带进位的加法运算 |
ADC Rd,Rn,#immed | ;Rd = Rn + #immed + 进位 | 带进位的加法运算 |
ADDW Rd,Rn,#immed | ;Rd = Rn + #immed | 寄存器和12位立即数相加 |
SUB Rd,Rn,Rm | ;Rd = Rn - Rm | 减法运算 |
SUB Rd,#immed | ;Rd = Rd - #immed | 减法运算 |
SUB Rd,Rn,#immed | ;Rd = Rn - #immed | 减法运算 |
SBC Rd,Rn,Rm | ;Rd = Rn - Rm - 借位 | 带借位的减法运算 |
SBC Rd,Rn,#immed | ;Rd = Rn - #immed - 借位 | 带借位的减法运算 |
SUBW Rd,Rn,#immed | ;Rd = Rn - #immed | 寄存器和12位立即数相减 |
RSB Rd,Rn,Rm | ;Rd = Rm - Rn | 反转减法运算 |
RSB Rd,Rn,#immed | ;Rd = #immed - Rn | 反转减法运算 |
MUL Rd,Rn,Rm | ;Rd = Rn * Rm | 乘法运算(32位) |
UDIV Rd,Rn,Rm | ;Rd = Rn / Rm | 无符号除法 |
SDIV Rd,Rn,Rm | ;Rd = Rn / Rm | 有符号除法 |
- 上述指令在使用时可以选择带着或不带S后缀以及指明APSR是否更新
- 若出现被零除的情况,UDIV和SDIV的默认结果为零,可以设置NVIC配置控制寄存器中的DIVBYZERO位,这样在产生被0除的情况就会产生异常
乘法和乘累加(MAC)指令
指令(由于APSR不更新,因此无S后缀) | 描述 | 操作 |
---|---|---|
MLA Rd,Rn,Rm,Ra | ;Rd = Ra + Rn * Rm | 32位MAC指令,32位结果 |
MLS Rd,Rn,Rm,Ra | ;Rd = Ra - Rn * Rm | 32位乘减指令,32位结果 |
SMULL RdLo,RdHi,Rn,Rm | ;{RdHi,RdLo} = Rn * Rm | 有符号数据的32位乘&MAC指令,64位结果 |
SMLAL RdLo,RdHi,Rn,Rm | ;{RdHi,RdLo} += Rn * Rm | 有符号数据的32位乘&MAC指令,64位结果 |
UMULL RdLo,RdHi,Rn,Rm | ;{RdHi,RdLo} = Rn * Rm | 无符号数据的32位乘&MAC指令,64位结果 |
UMLAL RdLo,RdHi,Rn,Rm | ;{RdHi,RdLo} += Rn * Rm | 无符号数据的32位乘&MAC指令,64位结果 |
逻辑运算
逻辑运算指令
指令(可选的S后缀未列出来) | 描述 | 操作 |
---|---|---|
AND Rd,Rn | ;Rd = Rd & Rn | 按位与 |
AND Rd,Rn,#immed | ;Rd = Rn & #immed | 按位与 |
AND Rd,Rn,Rm | ;Rd = Rn & Rm | 按位与 |
ORR Rd,Rn | ;Rd = Rd 或 Rn | 按位或 |
ORR Rd,Rn,#immed | ;Rd = Rn 或 #immed | 按位或 |
ORR Rd,Rn,Rm | ;Rd = Rn 或 Rm | 按位或 |
BIC Rd,Rn | ;Rd = Rd & (~Rn) | 位清除 |
BIC Rd,Rn,#immed | ;Rd = Rn & (~#immed) | 位清除 |
BIC Rd,Rn,Rm | ;Rd = Rn & (~Rm) | 位清除 |
ORN Rd,Rn,#immed | ;Rd = Rn 或 (w#immed) | 按位或非 |
ORN Rd,Rn,Rm | ;Rd = Rn 或 (wRm) | 按位或非 |
EOR Rd,Rn | ;Rd = Rd ^ Rn | 按位异或 |
EOR Rd,Rn,#immed | ;Rd = Rn ^ #immed | 按位异或 |
EOR Rd,Rn,Rm | ;Rd = Rn ^ Rm | 按位异或 |
- 注意:若使用这些指令的16位版本,则只能操作两个寄存器,且目的寄存器需要为源寄存器之一。另外还必须是低寄存器(R0~R7),而且要使用S后缀(APSR更新)。ORN指令没有16位模式。
移位和循环移位指令
移位和循环移位的指令
指令(可选的S后缀未列出来) | 描述 | 操作 |
---|---|---|
ASR Rd,Rn | ;Rd = Rd >> Rn | 算术右移 |
ASR Rd,Rn,#immed | ;Rd = Rn >> immed | 算术右移 |
ASR Rd,Rn,Rm | ;Rd = Rn >> Rm | 算术右移 |
LSR Rd,Rn | ;Rd = Rd >> Rn | 逻辑右移 |
LSR Rd,Rn,#immed | ;Rd = Rn >> immed | 逻辑右移 |
LSR Rd,Rn,Rm | ;Rd = Rn >> Rm | 逻辑右移 |
ROR Rd,Rn | ;Rd = Rd 右移 Rn | 循环右移 |
ROR Rd,Rn,Rm | ;Rd = Rn 右移 Rm | 循环右移 |
RRX Rd,Rn | ;{C,Rd}={Rn,C} | 循环右移并展开 |
LSL Rd,Rn | ;Rd = Rd << Rn | 逻辑左移 |
LSL Rd,Rn,#immed | ;Rd = Rn << immed | 逻辑左移 |
LSL Rd,Rn,Rm | ;Rd = Rn << Rm | 逻辑左移 |
- 注意:若使用这些指令的16位版本,则只能操作低寄存器(R0~R7),而且要使用S后缀(APSR更新)。RRX指令没有16位模式。
位域处理指令
位域处理指令
指令 | 描述 |
---|---|
BFC Rd, #< lsb >, #< width > | ;清除寄存器中的位域 |
BFI Rd, Rn, #< lsb >, #< width > | ;将位域插入寄存器 |
CLZ Rd, Rn | ;前导零计数 |
RBIT Rd, Rn | ;反转寄存器中的位顺序 |
SBFX Rd, Rn, #< lsb >, #< width > | ;从源中复制位域并有符号展开 |
UBFX Rd, Rn, #< lsb >, #< width > | ;从源寄存器中复制位域 |
位域处理指令解析
BFC
- 解析:清除寄存器任意相邻的1~31位。
- 举例:
LDR R0, =0x1234 FFFF BFC R0, #4, #8 ;这样得到结果为R0 = 0x1234 F00F
BFI
- 解析:将一个寄存器的1~31位(#< width >)复制到另外一个寄存器的任意位置上。
- 举例:
LDR R0, =0x1234 5678 LDR R1, =0x3355 AACC BFI R1, R0, #8, #16 ;将R0[15:0]插入到R1[23:8] ;这样得到的结果为R1 = 0x3356 78CC
CLZ
- 解析:CLZ计算前导零的个数,若没有位为1,则结果为32,而所有位都为1,则结果为0。
RBIT
-
解析:该指令常在数据通信中用于串行位数据流的处理。反转寄存器中的位顺序即是 1位换位置到32位
-
举例:
;若R0为0xB4E1 0C23 二进制位 1011_0100_1110_0001_0000_1100_0010_0011 RBIT R0, R1 ;R1会变为0xC430 872D (二进制为 1100_0100_0011_0000_1000_0111_0010_1101)
UBFX和SBFX
-
解析:UBFX从寄存器中的任意位置(由操作数 #< lsb > 指定)开始提取任意宽度(由操作数 #< width > 指定)的位域,将其零展开后放入目的寄存器
-
举例:
LDR R0, =0x5678 ABCD UBFX R1, R0, #4, #8 ;这样得到的结果为R1 = 0x0000 00BC(0xBC的零展开) LDR R0, =0x5678 ABCD SBFX R1, R0, #4, #8 ;这样得到的结果为R1 = 0xFFFF FFBC(0xBC的有符号展开)
数据转换运算(展开和反序)
有符号和无符号的展开
- 以下指令的16位版本只能访问低寄存器(R0~R7),32位形式可以访问高寄存器
指令 | 描述 | 操作 |
---|---|---|
SXTB Rd,Rn | ;Rd = 有符号展开(Rn[7:0]) | 有符号展开字节为字 |
SXTH Rd,Rn | ;Rd = 有符号展开(Rn[15:0]) | 有符号展开半字为字 |
UXTB Rd,Rn | ;Rd = 无符号展开(Rn[7:0]) | 无符号展开字节为字 |
UXTH Rd,Rn | ;Rd = 无符号展开(Rn[15:0]) | 无符号展开半字为字 |
- 举例:
;若R0为0x55AA 8765 SXTB R1,R0 ;R1 = 0x0000 0065 SXTH R1,R0 ;R1 = 0xFFFF 8765 UXTB R1,R0 ;R1 = 0x0000 0065 UXTH R1,R0 ;R1 = 0x0000 8765
具有可选循环移位的有符号和无符号展开
- 以下指令的32位形式可以访问高寄存器,并且可以选择在进行有符号和无符号展开运算前将输入数据循环右移
指令 | 描述 | 操作 |
---|---|---|
SXTB Rd,Rn{,ROR #n} | ;n = 8/16/24 | 有符号展开字节为字 |
SXTH Rd,Rn{,ROR #n} | ;n = 8/16/24 | 有符号展开半字为字 |
UXTB Rd,Rn{,ROR #n} | ;n = 8/16/24 | 无符号展开字节为字 |
UXTH Rd,Rn{,ROR #n} | ;n = 8/16/24 | 无符号展开半字为字 |
数据反转指令
- 以下指令的16位版本只能访问低寄存器(R0~R7)
指令 | 描述 | 操作 |
---|---|---|
REV Rd,Rn | ;Rd = rev(Rn) | 反转字中的字节 |
REV16 Rd,Rn | ;Rd = rev16(Rn) | 反转每个半字中的字节 |
REVSH Rd,Rn | ;Rd = revsh(Rn) | 反转低半字中的字节并将结果有符号展开 |
比较和测试
比较和测试
- 比较和测试用于更新APSR中的标志,这些指令不存在S后缀,这些标志随后可能会用于条件跳转或条件执行
指令 | 描述 |
---|---|
CMP < Rn >, < Rm > | ;比较:计算Rn - Rm,APSR更新,但是结果不会保存 |
CMP < Rn >, #immed | ;比较:计算Rn - 立即数,APSR更新,但是结果不会保存 |
CMN < Rn >, < Rm > | ;比较:计算Rn + Rm,APSR更新,但是结果不会保存 |
CMN < Rn >, #immed | ;比较:计算Rn + 立即数,APSR更新,但是结果不会保存 |
TST < Rn >, < Rm > | ;测试(按位与):计算Rn 和 Rm 相与后的结果,APSR中的N位和Z位更新,但是结果不会保存,若使用了桶形移位则更新C位 |
TST < Rn >, #immed | ;测试(按位与):计算Rn 和 立即数 相与后的结果,APSR中的N位和Z位更新,但是结果不会保存 |
TEQ < Rn >, < Rm > | ;测试(按位异或):计算Rn 和 Rm 异或后的结果,APSR中的N位和Z位更新,但是结果不会保存,若使用了桶形移位则更新C位 |
TEQ < Rn >, #immed | ;测试(按位异或):计算Rn 和 立即数 异或后的结果,APSR中的N位和Z位更新,但是结果不会保存 |
饱和运算
饱和运算
Cortex-M3和Cortex-M4支持两个用于有符号和无符号数据饱和调整的指令:SSAT(用于有符号数据)和USAT(用于无符号数据),Cortex-M4还额外支持用于饱和算法的其他指令。
饱和运算的实质是通过将数据强制置为最大允许值,减少数据畸变。
指令中的含义
- < Rn >为输入值
- < shift >为饱和前可选的移位操作,可以为 # LSL N 或 # ASR N
- < immed >为执行饱和的位的位置
- < Rd >为目的寄存器
执行运算后,除了目的寄存器,APSR中的Q位也会受结果的影响。若在运算中出现了饱和,Q标志就会置位,它可以通过写APSR清除。
指令 | 描述 |
---|---|
SSAT < Rd >, #< immed >, < Rn > {,< shift >} | ;有符号数据的饱和 |
USAT < Rd >, #< immed >, < Rn > {,< shift >} | ;有符号数据转换为无符号数据的饱和 |
举例:
SSAT R1, #16, R0
上一例子结果如下图:
USAT R1, #16, R0
上一例子结果如下图:
异常,休眠指令
异常相关指令
管理调用(SVC)指令用于产生SVC异常(异常类型为11)。
语法如下:
SVC #< immed >
其中立即数为8位,数值不会影响SVC异常的动作,不过SVC处理可以在程序中提取出这个数值并将其用作输入参数。
PRIMASK和FAULTMASK的设置及清除指令如下
指令 | 描述 |
---|---|
CPSIE I | ;使能中断(清除PRIMASK)和_enable_irq()相同 |
CPSID I | ;禁止中断(设置PRIMASK),NIM和HardFault不受影响和_disable_irq()相同 |
CPSIE F | ;使能中断(清除FAULTMASK)和_enable_fault_irq()相同 |
CPSID F | ;禁止错误中断(设置FAULTMASK),NMI不受影响和_disable_fault_irq()相同 |
休眠模式相关指令
指令 | 描述 |
---|---|
WFI(等待中断) | ;和_WFI();相同 |
WFE(等待事件) | ;和_WFE();相同 |
SEV(发送事件) | ;和_SEV();相同 |
- WFI会使处理器立即进入休眠模式,中断,复位或调试操作可以将处理器从休眠中唤醒。
- 在Cortex-M3和Cortex-M4处理器内部,一个只有一位的寄存器会记录事件。若该位寄存器置位,WFE指令不会进入休眠模式而只是清除事件寄存器并继续执行下一条指令;若该寄存器清零,则处理器会进入休眠而且会被事件唤醒,事件可以是中断,调试操作,复位或外部事件输入的脉冲信号(例如、事件脉冲可由另外一个处理器或外设产生)
存储器访问
各种数据大小的存储器访问指令
数据类型 | 加载(读存储器) | 存储(写存储器) |
---|---|---|
8位无符号 | LDRB | STRB |
8位有符号 | LDRSB | STRB |
16位无符号 | LDRH | STRH |
16位有符号 | LDRSH | STRH |
32位 | LDR | STR |
多个32位 | LDM | STM |
双字(64位) | LDRD | STRD |
栈操作(32位) | POP | PUSH |
LDR指令示例
LDR R0, [R1] ;将存储器地址为R1的字数据读入寄存器R0。
LDR R0, [R1, R2] ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR R0, [R1, #8] ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR R0, [R1, R2] ! ;将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR R0, [R1, #8] ! ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。
LDR R0, [R1], R2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR R0, [R1, R2, LSL#2]! ;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
LDR R0, [R1], R2, LSL#2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
LDRB指令示例
LDRB R0, [R1] ;将存储器地址为R1的字节数据读入寄存器R0,并将R0的高24位清零。
LDRB R0, [R1, #8] ;将存储器地址为R1+8的字节数据读入寄存器R0,并将R0的高24位清零
LDRH指令示例
LDRH R0, [R1] ;将存储器地址为R1的半字数据读入寄存器R0,并将R0的高16位清零。
LDRH R0, [R1, #8] ;将存储器地址为R1+8的半字数据读入寄存器R0,并将R0的高16位清零。
LDRH R0, [R1, R2] ;将存储器地址为R1+R2的半字数据读入寄存器R0,并将R0的高16位清零。
LDM指令示例
虽然貌似是LDR的升级,但是,千万要注意,这个指令运行的方向和LDR是不一样的,是从左到右运行的。该指令是将内存中堆栈内的数据,批量的赋值给寄存器,即是出栈操作;其中堆栈指针一般对应于SP,注意SP是寄存器R13,实际用到的却是R13中的内存地址,只是该指令没有写为[R13],同时,LDM指令中寄存器和内存地址的位置相对于前面两条指令改变了.
例子:
LDMFD SP!, {R0, R1, R2} ;可以理解为 LDMFD [SP]!, {R0, R1, R2}
;把sp指向的3个连续地址段(应该是3*4=12字节(因为为r0,r1,r2都是32位))中的数据拷贝到r0,r1,r2这3个寄存器中去。
STR指令示例
STR R0, [R1], #8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。
STR R0, [R1, #8] ;将R0中的字数据写入以R1+8为地址的存储器中。
STRB指令示例
STRB R0, [R1] ;将寄存器R0中的字节数据写入以R1为地址的存储器中。
STRB R0, [R1, #8] ;将寄存器R0中的字节数据写入以R1+8为地址的存储器中。
STRH指令示例
STRH R0, [R1] ;将寄存器R0中的半字数据写入以R1为地址的存储器中。
STRH R0, [R1, #8] ;将寄存器R0中的半字数据写入以R1+8为地址的存储器中。
STM指令示例
此指令与LDM是配对使用的,其指令格式上也相似,即区别于STR,是将堆栈指针写在左边,而把寄存器组写在右边。
例子:
STMFD SP!, {R0} ;可理解为: STMFD [SP]!, {R0}
;意思是:把R0保存到堆栈(sp指向的地址)中。
浮点单元的存储器访问指令
数据类型 | 加载(读存储器) | 存储(写存储器) |
---|---|---|
单精度数据(32位) | VLDR.32 | VSTR.32 |
双精度数据(64位) | VLDR.64 | VSTR.64 |
多数据 | VLDM | VSTM |
栈操作 | VPOP | VPUSH |
举例:
具有立即数偏移的存储器访问指令
-
加载:LDRB,LDRSB,LDRH,LDRSH,LDR,LDRD,VLSR.32,VLSR.64
-
存储:STRB,STRH,STR,STRD,VSTR.32,VSTR.64
LDRB R0,[R1,#0x3] ;从地址R1+0x3中读取一个字节存入R0 LDRD Rd1,Rd2,[Rn,#offest] ;从存储器位置Rn+#offest读取双字
具有立即数偏移并写回的存储器访问指令
-
加载:LDRB,LDRSB,LDRH,LDRSH,LDR,LDRD
-
存储:STRB,STRH,STR,STRD
LDR R0,[R1,#0x8]! ;在访问存储器地址[R1+0x8]后R1被更新为R1+0x8 ;指令中的感叹号!表示指令完成时是否更新存放地址的寄存器(写回)
PC相关寻址
-
加载:LDRB,LDRSB,LDRH,LDRSH,LDR,LDRD,VLSR.32,VLSR.64
-
存储:
LDR Rt,[PC,#offest] ;利用PC偏移加载字数据到Rt
寄存器偏移(前序)
-
加载:LDRB,LDRSB,LDRH,LDRSH,LDR
-
存储:STRB,STRH,STR
-
语法格式:指令 Rd,[Rn,Rm,{,LSL #n}] ;从存储器位置Rn+(Rm<<n)处加载/存储
LDR R3,[R0,R2,LSL #2] ;将存储器[R0+(R2<<2)]读入R3 STR R5,[R0,R7] ;将R5写入存储器[R0+R7]
寄存器偏移(后序)
- 加载:LDRB,LDRSB,LDRH,LDRSH,LDR,LDRD
- 存储:STRB,STRH,STR,STRD
- 语法格式:指令 Rd,[Rn],#offest ;加载/存储存储器[Rn]处的内容到Rd,然后更新Rn到Rn+offest
多加载和多存储(具有写回功能)
- 加载:LDMIA,LDMDB,VLDMIA.32,VLDMDB.32,VLDMIA.64,VLDMDB.64
- 存储:STMIA,STMDB,VSTMIA.32,VSTMDB.32,VSTMIA.64,VSTMDB.64
- IA后缀:在每次读/写后增加地址
- DB后缀:在每次读/写后减少地址
- 语法格式:指令 Rn{!},< reg list > ;从/往Rn指定的存储器位置读取/写入多个字,地址在每次读取/写入后增加/减少,其中感叹号!表示是否需要写回功能
-
< reg list >为寄存器列表,其中至少包含一个寄存器
-
使用“-”(连字符)表示范围
LDR R4,=0x2000 000 ;将R4设置为0x2000 000(地址) LDMIA R4,{R0-R3} ;读取四个字并将其存入R0-R4 LDR R8,=0x8000 ;将R8设置为0x8000(地址) STMIA R8!,{R0-R3} ;存储后将R8变为0x8010
-
压栈和出栈
-
将寄存器存入栈中:PUSH(压栈),VPUSH.32,VPUSH.64
-
从栈中恢复寄存器:POP(出栈),VPOP.32,VPOP.64
-
语法格式:指令 < reg list >
PUSH {R0,R4-R7,R9} ;将R0,R4,R5,R6,R7,R9压入栈中 POP {R2,R3} ;将栈中内容存至R2,R3 PUSH {R4-R6,LR} ;在子程序开始处保存R4-R6和LR(链接寄存器) POP {R4-R6,PC} ;从栈中恢复R4-R6和返回地址,返回地址直接存入PC,这样会触发跳转(子程序返回)
非特权访问等级下的加载和存储
- 加载:LDRBT,LDRSBT,LDRHT,LDRSHT,LDRT
- 存储:STRBT,STRHT,STRT
- 语法格式:指令 Rd,[Rn,#offest] ;从/往存储器位置Rn+offest读取/存储内容
排他访问
- 处理器内部存在一个仅有一位的寄存器,请求资源的排他访问流程可以描述为下图
- 排他标识有三种情况会被清零:
- 执行CLREX命令;
- 执行STREX等命令(无论成功与否);
- 处理器产生异常或中断。
- 当且仅当排他标识为1时,STREX指令才能成功执行并返回0,否则STREX指令执行失败并返回1。因此,排他访问提供了检验原子操作被交叉运行的方法。通过检验STREX的返回结果,判断出原子操作被交叉运行,从而重新执行新的“读-改-写”操作,更新变量的值。
排他访问示例 | 描述 |
---|---|
LDREXB Rt, [Rn] | 从存储器位置Rn排他读取字节 |
LDREXH Rt, [Rn] | 从存储器位置Rn排他读取半字 |
LDREX Rt, [Rn, #offest] | 从存储器位置Rn+offest排他读取字 |
STREXB Rd, Rt, [Rn] | 往存储器位置Rt排他存储字节,返回状态位于Rd中 |
STREXH Rd, Rt, [Rn] | 往存储器位置Rt排他存储半字,返回状态位于Rd中 |
STREX Rd, Rt, [Rn, #offest] | 往存储器位置Rt排他存储Rn+offest字,返回状态位于Rd中 |
CLREX | 强制本地排他访问监控清零,使得下一次排他存储失败。 |
程序流控制
跳转
- 多个指令可以引发跳转操作
- 跳转指令(如B、BX)
- 更新R15(PC)的数据处理指令(如MOV、ADD)
- 写入PC的读存储器指令(如LDR、LDM、POP)
- 跳转指令
指令 | 描述 |
---|---|
B < label > | ;跳转到 label ,若跳转范围超过 +/- 2KB,则可以指定B.W < label > 使用32位版本的跳转指令 |
;这样可以得到较大的范围 | |
B.W < label > | ;跳转到 label ,若跳转范围超过 +/- 2KB,则可以指定B.W < label > 使用32位版本的跳转指令 |
;这样可以得到较大的范围 | |
BX < Rm > | 间接跳转。跳转到存放于Rm中的地址值,并且基于Rm第0位设置处理器的执行状态(T位) |
;(由于Cortex-M处理器只支持Thumb状态,Rm的第0位必须为1) |
函数调用
指令 | 描述 |
---|---|
BL < label > | 跳转到标号地址并将返回地址保存在LR中 |
BLX < Rm > | 跳转到Rm指定的地址,并将返回地址保存在LR中,以及更新EPSR中的T位为Rm的最低位 |
当执行这些指令时:
- 程序计数器被置为跳转目标地址
- 链接寄存器(LR/R14)被更新为返回地址,这也是已执行的BL/BLX后指令的地址
- 若指令为BLX,则EPSR中的Thumb位也会被更新为存放跳转目标地址的寄存器的最低位
由于Cortex-M3和M4处理器只支持Thumb状态,BLX操作中使用的寄存器的最低位必须要置1,要不然,它就表示试图要切换到ARM状态,这样会引发错误异常
举例:
main
...
BL functionA ;注意:LR中的返回地址会发生变化
...
functionA
PUSH {LR} ;将LR的内容保存在栈中
...
BL functionB ;注意:LR中的返回地址会发生变化
...
POP {PC} ;使用压栈的LR内容返回到main
functionB
PUSH {LR} ;将LR的内容保存在栈中
...
POP {PC} ;使用压栈的LR内容返回到functionA
;另外,若调用的子程序为C函数,且寄存器R0~R3和R12的内容稍后会用到,则可能还需要将他们保存在栈中
条件跳转
条件跳转是基于APSR的当前值条件执行(N、Z、C、V标志)
标志 | FSR位 | 描述 |
---|---|---|
N | 31 | 负标志(上一次运算结果为负值) |
Z | 30 | 零(上一次运算结果得到零值,例如比较两个数值相同的寄存器) |
C | 29 | 进位(上一次执行的运算有进位或者没有借位,还可以是移位或者循环移位操作中移出的最后一位) |
V | 28 | 溢出(上一次运算的结果溢出) |
APSR受到以下情况影响
- 多数16位数据处理指令
- 带有S后缀的32位(Thumb-2)数据处理指令,如ADDS.W
- 比较(如CMP)和测试(如TST、TEQ)
- 直接写APSR/xPSR
bit[27]为另外一个标志,也就是Q标志,用于饱和算术运算而非条件跳转。
条件跳转发生时所需的条件由后缀指定(下表中表示为 < cond >)。条件跳转指令具有16位和32位的形式,他们的跳转范围不同。如下表所示
指令 | 描述 |
---|---|
B < cond > < label > | ;若条件为true,则跳转label |
B < cond > .W< label > | ;若所需的跳转范围超过了+/-254字节,则可能需要指定使用32位版本的跳转指令,以增加跳转范围 |
举例:
CMP R0, #1
BEQ loop ;若R0等于1,则跳转到loop
< cond >为下表的14个可能的条件后缀之一
后缀 | 条件跳转 | 标志(APSR) |
---|---|---|
EQ | 相等 | Z置位 |
NE | 不相等 | Z清零 |
CS | 进位置位 | C置位 |
HS | 无符号大于或相等 | C置位 |
CC | 进位清零 | C清零 |
LO | 无符号小于 | C清零 |
MI | 减/负数 | N置位(减) |
PL | 加/正数或0 | N清零 |
VS | 溢出 | V置位 |
VC | 无溢出 | V清零 |
HI | 无符号大于 | C置位Z清零 |
LS | 无符号小于或相等 | C清零或Z置位 |
GE | 有符号大于或相等 | N置位V置位,或N清零V清零(N == V) |
LT | 有符号小于 | N置位V清零,或N清零V置位(N != V) |
GT | 有符号大于 | Z清零,且N置位V置位,或N清零V清零(Z == 0,N == V) |
LE | 有符号小于或相等 | Z置位,且N置位V清零,或N清零V置位(Z == 1,N != V) |
比较和条件跳转的组合
ARMv7-M架构提供了两个新的指令,他们合并了和零比较以及条件跳转操作。这两个指令为CBZ(比较为0则跳转)和CBNZ(比较非零则跳转),他们只支持前向跳转,不支持后向跳转。
注意:APSR的值不受CBZ和CBNZ指令影响
举例:
CBZ Rn, label ;可等同于下述代码
CMP Rn, #0
BEQ label ;以上两行代码可等同于 CBZ Rn, label
CBNZ Rn, label ;可等同于下述代码
CMP Rn, #0
BNE label ;以上两行代码可等同于 CBNZ Rn, label
编译前:
i=5;
while(i!=0)
{
func1();
i--;
}
编译后:
MOV R0, #5 ;设置循环变量
loop1 CBZ R0, loop1exit ;若循环变量为0则跳出循环
BL func1 ;调用函数
SUBS R0, #1 ;循环变量减少
B loop1 ;下一个循环
loopexit
编译前:
status = strchr(email_address,'@');
if(status == 0)
{
show_err_message();
exit(1);
}
编译后:
...
BL strchr
CBNZ R0, email_looks_okay ;结果非0则跳转
BL show_err_message
BL exit
email_looks_okay
条件执行(IF-THEN)
IT指令块如下表所示
注意:
- < x > 表示第二个指令执行的条件
- < y > 表示第三个指令执行的条件
- < z > 表示第四个指令执行的条件
- < cond >指定指令块的基本条件,若< cond >为true,则执行IT后的第一条指令
- 每一个 < x > < y > < z > 可以为T(true)或E(else)
IT块 | 例子 | |
---|---|---|
只有一个条件指令 | IT < cond > | IT EQ |
instr1 < cond > | ADDEQ R0, R0, R1 | |
两个条件指令 | IT < x > < cond > | ITE GE |
instr1 < cond > | ADDGE R0, R0, R1 | |
instr2 < cond or ~(cond) > | ADDLT R0, R0, R3 | |
三个条件指令 | IT < x > < y > < cond > | ITET GT |
instr1 < cond > | ADDGT R0, R0, R1 | |
instr2 < cond or ~(cond) > | ADDLE R0, R0, R3 | |
instr3 < cond or ~(cond) > | ADDGT R2, R4, #1 | |
四个条件指令 | IT < x > < y > < z > < cond > | ITETT NE |
instr1 < cond > | ADDNE R0, R0, R1 | |
instr2 < cond or ~(cond) > | ADDEQ R0, R0, R3 | |
instr3 < cond or ~(cond) > | ADDNE R2, R4, #1 | |
instr4 < cond or ~(cond) > | MOVNE R5, R3 |
IT指令块中的数据处理指令不应修改APSR的数值,当有些16位的数据处理指令在IT指令块中使用时,APSR不会更新,这一点和他们正常操作更新APSR的情况不同。这样就可以在IT指令块中使用16位的数据处理指令以降低代码大小。
表格跳转
Cortex-M3和Cortex-M4支持两个表格跳转指令:TBB(表格跳转字节)和TBH(表格跳转半字),他们和跳转表一起使用,通常用于实现C代码中的switch语句。
指令 | 描述 |
---|---|
TBB [Rn, Rm] | ;Rn存放跳转表的基地址,Rm则为跳转表偏移 |
TBH [Rn, Rm, LSL #1] | ;Rn存放跳转表的基地址,Rm则为跳转表偏移 |
TBB偏移计算用的立即数位于存储器地址[Rn+Rm]。假定R15/PC用作Rn,则TBB操作如下图
TBH情况类似TBB,只不过跳转表的每个入口都是双字节大小,假定R15/PC用作Rn,则TBH操作如下图