嵌入式笔记2.1 ARM Cortex-M3M4汇编指令集
一、Cortex-M 处理器的指令集#
Cortex-M 处理器支持的指令集#
正如上图中所见,从 Cortex-M0 到 Cortex-M3 以及后面的 Cortex-M4,Cortex-M 处理器的指令集设计是向上兼容的,因此,为Cortex-MO/M0+/M1 处理器编译的代码在 Cortex-M3 或 Cortex-M4 处理器上也能运行,而为 Cortex-M3 编译的代码也可以在 CortexM4 上运行。
ARMv6-M的多数指令都是16位的,有些则具有16位和32位两种格式。当操作能以16位执行时,编译器通常会选择使用16位版本的指令这样会降低代码大小。32位的指令使用更大寄存器范围(如高寄存器)、更大的立即数、更宽的地址区域以及更多的寻址模式。不过,对应同一个操作,16 位版本和 32 位版本的指令所需的执行时间是相同的。
Cortex-M 处理器指令集的选择#

对于一般的数据处理和 I/O 控制任务,Cortex-M0 和 Cortex-M0+ 处理器完全能够应付。例如,Cortex-M0 和 Cortex-M0+ 性能可以达到 2.15CoreMark/MHz,这已经是其他 16 位微控制器在相同运行频率下性能的两倍。若应用需要处理更多的复杂数据、执行快速乘法运算或需要较快完成数据处理,则可能需要升级到 Cortex-M3 或 Cortex-M4 处理器。若需要在DSP应用或浮点运算中达到最优的效果,Cortex-M4 将是一个更好的选。
尽管 Cortex-M 处理器支持的指令不少,不过由于 C 编译器可以生成相当高效的代码,也不用了解全部指令的细节。另外,免费的CMSIS-DSP 库和各种中间件(如软件库)有助于软件开发人员实现高性能的DSP应用,而无须深人了解每条指令的细节。
二、寄存器组详解#
1. 通用寄存器 R0~R12#
R0~R12 是最具“通用目的”的 32 位通用寄存器,用于数据缓冲操作。该类寄存器分为两组:一组被称为低位寄存器, R0~R7,它们能够被所有通用寄存器指令访问;另一组被称为高位寄存器, R8~R12,它们能够被所有 32 位通用寄存器指令访问,而不能被所有 16 位通用寄存器指令访问。
2. 栈指针#
寄存器 R13 被用作栈指针,用于访问 RAM 中的栈区。在 Arm 架构中, SP 的最低两位被忽略,相当于 SP 的最低两位永远是 0,所以 SP 的值是 4 的整数倍,那么 SP 指向的 RAM 地址也是 4 的整数倍,即是按照 4 字节对齐的。 Arm 是 32 位机,机器字长为 32 位, 4 字节对齐表示栈中的数据存储是按照字对齐的。在图中, SP(R13)右侧的箭头 “→” 指向了SP的两个名字: PSP、 MSP。主栈指针MSP是复位后缺省使用的栈指针,用于操作系统内核以及异常处理例程(包括中断处理程序)。“Handler”模式通常使用主栈指针(Main Stack Pointer,MSP),但是也可以使用进程栈指针(Processor Stack Pointer, PSP)。
3. 连接寄存器#
寄存器 R14 也称作连接寄存器(Link Rigister,LR),用 于保存函数或子程序调用时的返回地址。 LR 也被用于异常返回。在其他情况下,可以将 R14 作为通用寄存器来使用。
4. 程序计数寄存器#
寄存器R15 是程序计数寄存器,指示将要执行的指令在存储器中的位置。复位的时候,处理器的硬件机制自动将复位向量值放入PC。 如果修改它的值,就能改变程序的执行流。该寄存器的第 0 位若为 0,则指令总是按照字对齐或者半字对齐。 PC能以特权或者非特权模式进行访问。
5. 程序状态字寄存器(xPSR)#
程序状态字寄存器在内部分为 3 个子寄存器: APSR、 IPSR、 EPSR。 3 个子寄存器既可以被单独访问,也可以将两个或三个组合到一起访问。使用三合一方式访问时,把该寄存器称为 xPSR,各个寄存器组合名称与读写属性如下表所示。其中 xPSR、 IPSR 和 EPSR 寄存器只能够在特权模式下被访问,而 APSR 寄存器能够在特权或者非特权模式下被访问,具体描述详见《CM4 用户指南》。
Arm Cortex-M4 程序状态寄存器(xPSR)
寄存器 | 类型 | 结合 |
---|---|---|
xPSR | RW | APSR、 IPSR、和 EPSR |
IEPSR | RO | IPSR 和 EPSR |
IAPSR | RW | APSR 和 IPSR |
EAPSR | RW | APSR 和 EPSR |
CM4F 程序状态字寄存器(xPSR)
数据位 | 31 | 30 | 29 | 28 | 27 | 26~25 | 24 | 23~20 | 19~16 | 15~10 | 9 | 8~0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
APSR | N | Z | C | V | Q | GE[3:0] | ||||||
IPSR | 异常号 | |||||||||||
EPSR | ICI/IT | T | ICI/IT | |||||||||
xPSR | N | Z | C | V | Q | ICI/IT | T | GE[3:0] | ICI/IT | 异常号 |
-
应用程序状态寄存器(APSR):显示算术运算单元 ALU 状态位的一些信息。
负标志 N:若结果最高位为 1,相当于有符号运算中结果为负,则置 1,否则清 0。
零标志 Z:若结果为 0,则置 1,否则清 0。
进(借)位标志 C:若有最高位的进位(减法为借位),则置 1,否则清 0。
溢出标志 V:若溢出,则置 1,否则清 0。以上各位在 Cortex-M 系列处理器中同 M0、 M0+、 M3、 M4 的定义是一样的。这些位会在条件转移指令中被用到,复位之后是随机的。
饱和标志位 Q:在实现 DSP 扩展的处理器中,如果在运算中出现饱和,处理器就会将该位置 1,此即为饱和。该位只在 Cortex-M3、 Cortex-M4 中存在。
大于或等于标志位 GE:仅用于 DSP 扩展, SIMD 指令更新这些标志用以指明结果来自操作的单个字节或半字。该位只在 Cortex-M4 中存在。更多信息请参考《Armv7-M参考手册》。
-
中断程序状态寄存器(IPSR):每次异常处理完成之后,处理器会实时更新 IPSR 内的异常号, 相关值只能被 MRS 指令读写。进程模式下(可以理解为处于无操作系统的主循环中,或者有操作系统情况下的某一任务程序中),值为 0。 Handler 模式(处理异常的模式, 可简单地理解为中断状态)下,存放当前异常的异常号。复位之后,寄存器被自动清零。复位异常号是一个暂时值,复位时, 其是不可见的。 在 Cortex-M 系列处理器中 M0 和 M0+ 的异常号占用 0~5 位, M3、 M4 使用 0~8 位,这与处理器所能支持的异常或中断数量有关。
-
执行程序状态寄存器(EPSR): T 标志位指示当前运行的是否为 Thumb 指令,该位是不能被软件读取的, 运行复位向量对应的代码时置 1。如果该位为 0,会发生硬件异常,并进入硬件中断处理程序。在 Cortex-M 系列处理器中这一位的定义相同。
ICI/IT 标志位:指示异常可继续指令状态或保存的 IT 状态,该位存在于 Cortex-M3 与 Cortex-M4 中。该位的更多信息请参考《Armv7-M 参考手册》。
6. 特殊功能寄存器#
PRIMASK、FAULTMASK 和 BASEPRI 寄存器都用于异常或中断屏蔽,每个异常(包括中断)都具有一个优先等级,数值小的优先级高,而数值大的则优先级低。这些特殊寄存器可基于优先等级屏蔽异常,只有在特权访问等级才可以对它们进行操作(非特权状态下的写操作会被忽略,而读出则会返回 0)。它们默认全部为 0,也就是屏蔽(禁止异常/中断)不起作用。

- 中断屏蔽寄存器(PRIMASK)
PRIMASK 寄存器为 1 位宽的中断屏蔽寄存器。在置位时,它会阻止不可屏蔽中断(NMI)和 HardFault 异常之外的所有异常(包括中断)。实际上,它是将当前异常优先级提升为0,这也是可编程异常/中断的最高优先级。
PRIMASK 最常见的用途为,在时间要求很严格的进程中禁止所有中断,在该进程完成后,需要将 PRIMASK 清除以重新使能中断。
使用特殊指令(MSR, MRS)可以访问中断屏蔽寄存器,当其某些位被置位时,除不可屏蔽中断和硬件错误外,其余所有中断都会被屏蔽。 - 错误屏蔽寄存器(FAULTMASK)
FAULTMASK 与 PRIMASK 的区别在于 FAULTMASK 能够屏蔽掉优先级更高的硬件错误(HardFault)异常。
FAULTMASK 实际上是将异常优先级提升到了 -1。错误处理代码可以使用 FAULTMASK 以免在错误处理期间引发其他的错误(只有几种)。例如,FAULTMASK 可用于旁路 MPU 或屏蔽总线错误(这些都是可配置的),这样,错误处理代码执行修复措施也就更加容易了。与 PRIMASK 不同 FAULTMASK 在异常返回时会被自动清除。 - 基本优先级屏蔽寄存器(BASEPRI)
BASEPRI 提供了一种更加灵活的中断屏蔽机制,通过设置该寄存器可以屏蔽特定优先级的中断,当该寄存器设置为一个非零值时,所有优先级值(中断的优先级数值越大优先级越低)大于或等于该值的中断都会被屏蔽,当该寄存器为零时不起作用。寄存器只能在特权模式下访问。复位时,基本优先级屏蔽寄存器被清除。 - 控制寄存器(CONTROL)
CONTROL 用于控制和确定处理器的操作模式以及当前执行任务的特性。
7. 浮点控制寄存器#
浮点控制寄存器只在 Cortex-M4F 处理器中存在,其中包含了用于浮点数据处理与控制的寄存器,在这里只进行简单的介绍,详细内容请参考《CM3/4 权威指南》《 Armv7-M 参考手册》。此外,浮点单元中还引入一些经过存储器映射的寄存器, 如协处理器访问控制寄存器(CPACR)等。需要注意的是,为降低功耗,浮点单元默认是被禁用的,如须使用浮点运算就要通过设置 CPACR 来启用浮点单元。
三、指令集详解(Cortex-M3 和 Cortex-M4 都支持的)#
基本指令保留字简表

1. 寻址方式#
1.1 立即数寻址#
在立即数寻址方式中,操作数直接通过指令给出,数据包含在指令中,随着指令一起被汇编成机器码,存储于程序空间中。将“#”作为立即数的前导标识符。
MOV R0,#0xFF //立即数 0xFF 装入 R0 寄存器 机器码: 20ff
SUB R1,R0,#1 //R1←R0-1 机器码: 1e41
得出机器码比较简单的方法是通过汇编环境,汇编后在.lst 文件中可找到机器码。在.lst 文件中看到的机器码要注意大小端存放问题。
1.2 寄存器直接寻址#
在寄存器直接寻址方式中,操作数来自于寄存器。
MOV R1,R2 //R1←R2 机器码: 1c11
SUB R0,R1,R2 //R0←R1-R2 机器码: 1a88
1.3 直接地址寻址#
在直接地址寻址方式中,操作数来自于存储单元,指令中直接给出存储单元地址。指令码中,显式表明了数据的位数,有字( 4 字节)、半字( 2 字节)、单字(1字节) 3 种情况。
LDR R1,label //从 label 处连续读取 4 字节至寄存器 R1 中
LDRH R1,label //从 label 处连续读取 2 字节至寄存器 R1 中
LDRB R1,label //从 label 处读取 1 字节至寄存器 R1 中
1.4 寄存器加偏移间接寻址#
在偏移寻址中,操作数来自存储单元,指令中通过寄存器及偏移量给出存储单元的地址,偏移量为 0 的偏移寻址也称为寄存器间接寻址。
LDR R3,[PC, #100] //从地址( PC + 100)处读取 4 字节到 R3 中 机器码: 4b19
LDR R3,[R4] //以 R4 中内容为地址,读取 4 字节到 R3 中 机器码: 6823
2. 指令详解#
ARM 指令的典型编码格式如下:
其中,每部分编码含义如下所示:
- cond:指令执行的条件编码
- opcode:指令操作符编码
- S:决定指令的执行是否影响 APRS 的值
- Rn:包含第一个源操作数的寄存器编码
- Rd:目标寄存器编码
- shifter_operand:第二个源操作数
一条典型的 ARM 指令语法格式如下所示:
<opcode>{<cond>}{S} <Rd>,<Rn>,<shifter_operand>{!}
其中:
- <opcode>:指令助记符,表示指令的功能,例如,ADD 表示加法指令。
- {<cond>}:表示指令执行的条件,例如,EQ 表示相等时才执行该命令
- {S}:决定指令执行后,是否影响 APRS 的值。默认情况下,数据处理指令不影响条件码标志位,但可以选择通过添加 “S” 来影响。有些指令如 CMP 不需要增加 “S” 就可改变相应的标志位。一般情况下,需要影响 CPRS 的值则加 “S”,否则不加。
按照传统的 Thumb 语法(UAL之前),在使用 16 位的 Thumb 代码时,ADD 指令会修改 PSR 中的标志。不过,32 位 Thumb-2 指令可以修改这些标志,也可以不修改。为了区分这两种操作,根据统一汇编语言(UAL)语法,若后面的操作需要标志,则应该使用 S 后缀。
ADD R0,R1,R2 ;标志未变
ADDS R0,R1,R2 ;标志改变
- {!}:表示指令完成时是否更新存放地址的寄存器(写回:更新地址)。不管是否使用了感叹号 "!",数据传输的地址都使用 R1+R8 的和。
- <Rd>:表示目标寄存器
- <Rn>:表示包含第一个源操作数的寄存器,也可以是立即数 #imm
- <shifter_operand>:表示第二个源操作数
在 ARM 指令的语法格式中,<> 中的内容是必需的(后续省略),“{}” 表示其中为可选项,如 LDRH Rt, [Rn {, #imm}],表示有 “LDRH Rt, [Rn]” “LDRH Rt, [Rn , #imm]” 两种指令格式。 指令中的 “[ ]” 表示其中的内容为地址,“;” 表示注释。
reglist 为寄存器列表,其中至少包括一个寄存器,以及:
- 始为“{”,结束为“}”
- 使用 “_”(连字符)表示范围。例如,RO-R4 表示 RO、R1、R2、R3 以及 R4。
- 使用 “,”(逗号)隔开每个寄存器。
spec_reg 表示特殊寄存器: APSR、 IPSR、 EPSR、 IEPSR、 IAPSR、EAPSR、 PSR、 MSP、 PSP、 PRIMASK、 CONTROL。
例如,下面的指令读取地址 0x20000000~0x2000000F(4个字)的内容到RO~R3:
LDR R4,=0x20000000 ;将R4设置为0x20000000(地址)
LDMIA R4,{RO-R3} ;读取4个字并将其存人R0~R3
2.1 数据传送类指令#
2.1.1 处理器内(寄存器间)传送数据
MOV 指令用于 CPU 内部寄存器之间的数据传送。 Rd 表示目标寄存器; imm 为立即数或符号常量,注意其范围是 0x00~0xFF。这些指令影响 N、 Z 状态标志,但不影响 C、 V 状态标志。当 MOV 指令中的目标寄存器 Rd 为程序计数器(PC)时,传送值的第 0 位被清零, 然后进入程序计数器,这样 MOV 指令就变成了跳转指令。但跳转一般不使用 MOV 指令,而是使用跳转类指令。
- 将数据从一个寄存器送到另外一个寄存器。
- 在寄存器和特殊寄存器(如 CONTROL、PRIMASK、FAULTMASK 和 BASEPRI)间传送数据。
- 将立即数送到寄存器。
对于具有浮点单元的 Cortex-M4 处理器,还可以:
- 在内核寄存器组中的寄存器和浮点单元寄存器组中的寄存器间传送数据,在浮点寄存器组中的寄存器间传送数据。
- 在浮点系统寄存器(如FPSCR——浮点状态和控制寄存器)和内核寄存器间传送数据。
- 将立即数送入浮点寄存器
处理器内传送数据的指令
指令 | 说明 |
---|---|
MOV Rd,Rn | 从 Rn 复制数据到 Rd |
MRS Rd,spec_reg | 加载特殊功能寄存器值到通用寄存器。若当前执行模式不为特权模式,除 APSR 寄存器外,读其余所有寄存器值为 0 |
MSR spec_reg,Rn | 存储通用寄存器的值到特殊功能寄存器。 Rd 不允许为 SP 或 PC 寄存器,若当前执行模式不为特权模式,除 APSR 外,任何试图修改寄存器的操作均被忽视。影响 N、 Z、 C、 V 标志 |
MOVW Rd,#imm | 设置 Rd 为16 位常量 #imm |
MOVT Rd,#imm | 设置 Rd 的高 16 位为 #imm |
MVN Rd,Rn | 将 Rd 中数据取反后送至 Rn |
示例:
MOV Rd,Rm ;Rd←Rm, Rd 只可以是 R0~ R7
MOVS Rd,#imm ;Rd←#imm并修改APSR,立即数范围是 0x00~ 0xFF; MOV 指令也可用这种格式
MOV Rd,Rn,LSL#imm ;Rd←RN*2^imm
这些指令影响 N、 Z 状态标志,但不影响 C、 V 状态标志。当 MOV 指令中的目标寄存器 Rd 为程序计数器时,传送值的第 0 位被清零, 然后进入程序计数器,这样 MOV 指令就变成了跳转指令。但跳转一般不使用 MOV 指令。
浮点单位和内核寄存器间传送数据的指令
指令 | 说明 |
---|---|
VMOV Rd,Sn | 将数据从浮点寄存器 Sn 复制到通用目的寄存器 Rd |
VMOV Sd,Rn | 将数据从通用目的寄存器 Rn 复制到浮点寄存器 Sd |
VMOV Sd,Sn | 将数据从浮点寄存器 Sn 复制到 Sd(单精度) |
VMRS.F32 Rd,FPSCR | 将数据从浮点单元系统寄存器 FPSCR 复制到 Rd |
VMRS APSR_nzcv,FPSCR | 复制 FPSCR 中的标志到 APSR 中标志 |
VMRS FPSCR,Rd | 复制 Rd 到浮点单元系统寄存器 FPSCR |
VMOV.F32 Sd,#1.0 | 将单精度数据送到浮点单元寄存器 Sd |
要将寄存器设置为一个较大的立即数(9~16 位),可以使用 MOVW 指令。根据所使用的汇编器工具,若立即数位于 9~16 位,MOV 或 MOVS 可能会被自动转换为 MOVW。若需要将寄存器设置为 32 位立即数,可以使用多种方法。最常见的方法为利用一个名为 LDR 的伪指令。例如:
LDR R0,=0x12345678 ;将 R0 设置为 0x12345678
这是一个伪指令,汇编器会将其转换为存储器传输指令及存储在程序映像中的常量。
LDR R0,[PC,#offset]
...
DCD 0x12345678
LDR 读取 [PC + 偏移] 位置的数据,并将其存人 R0。注意,由于处理器的流水线结构,PC 的值并非 LDR 指令的地址。不过,汇编器会计算偏移,因此也不必担心。
文字池
汇编器通常会将字符数据(如上面例子中的 0x12345678)组成名为文字池的数据块。由于 LDR 指令中的偏移值有限,程序通常需要多个文字池,才能使 LDR 访问到文字数据。因此,需要插入 LTORG(或.poo0)之类的汇编伪指令,以告知汇编器何处可以插人文字池。否则,汇编器会尝试将所有的字符数据放到程序代码的末尾,这样可能会超出LDR指令可以访问的范围。
若需要将寄存器设置为程序代码中位于一定范围内的地址,则可以使用 ADR 伪指令,它会被转换为一个单独的指令;或者使用 ADRL 伪指令,它可以提供更大的地址范围,不过会被转换为两条指令。例如:
ADR R0,DataTable
...
ALIGN
DataTable
DCD 0,245,132,...
ADR 指令会被转换为基于程序计数器数值的“加法”或“减法”运算。另外一种生成 32 位立即数的方法为组合使用 MOVW 和 MOVT 指令。例如:
MOVW R0,#0x789A ;设置R0为0x0000789A
MOVT R0,#0x3456 ;将 R0 的高 16 位设置为 0x3456,目前R0 = 0x3456789
与使用 LDR 伪指令的方法相比,LDR 方法的可读性更好,而且若同一个常量在程序代码中使用了多次,汇编器可能会重用相同的常量数据以降低代码大小。不过,取决于存储器系统的设计,若使用了系统级的缓存目 LDR 会带来数据缓存丢失,有些情况下 MOVW + MOVI 方法生成的代码可能会执行更快。
2.1.2 存储器访问指令
各种数据大小的存储器访问指令简表
数据类型 | 取数(读存储器) | 存数(写存储器) |
---|---|---|
8位无符号 | LDRB | STRB |
8为有符号 | LDRSB | STRB |
16位无符号 | LDRH | STRH |
16为有符号 | LDRSH | STRH |
32位 | LDR | STR |
多个32位 | LDR | STR |
双字(64位) | LDRD | STRD |
栈操作(32位) | POP | PUSH |
注意:LDRSB 和 LDRSH 会对被加载数据自动执行有符号展开运算(符号扩展),将其转换为有符号的32位数据。例如,若 LDRB 指令读取的是 0x83,则数据在被放到目的寄存器前会被转换为 0xFFFFFF83。
浮点单元的存储器访问指令
数据类型 | 读存储器(加载) | 写存储器(存储) |
---|---|---|
单精度数据(32位) | VLDR.32 | VSTR.32 |
双精度数据(64位) | VLDR.64 | VSTR.64 |
多数据 | VLDM | VSTM |
栈操作 | VPOP | VPUSH |
取数指令
存储器( RAM 或 Flash) 用地址进行表征。把存储器中的内容加载到 CPU 内部寄存器中的指令被称为取数指令。其中, LDR、 LDRH、 LDRB 指令分别表示取存储器单元的一个字、半字和单字节(不足部分以 0 填充)到 CPU 内部寄存器中; LDRSH 和 LDRSB 指令表示取存储器单元的半字、单字节(有符号数扩展成 32 位)到指定寄存器 Rt(寄存器是32 位的 )。
指令 | 说明 |
---|---|
LDR Rt,[<Rn|SP>{,#imm}] | 从 SP/Rn+#imm 地址处取字到 Rt,imm=0,4,8,…,1020 |
LDR Rt,[Rn,Rm] | 从 Rn+Rm 地址处读取字到 Rt |
LDR Rt,label | 从 label 指定的存储器单元取数到寄存器,label 必须在当前指令的 -4 ~ 4KB 范围内,且应 4 字节对齐 |
LDRH Rt,[Rn{,#imm}] | 从 Rn+ #imm 地址处,取半字到 Rt,imm=0,2,4,…,62 |
LDRH Rt,[Rn,Rm] | 从 Rn+Rm 地址处读取半字到 Rt |
LDRB Rt,[Rn{,#imm}] | 从 Rn+ #imm 地址处,取字节到 Rt,imm=0~31 |
LDRB Rt,[Rn,Rm] | 从 Rn+Rm 地址处读取字节到 Rt |
LDRSH Rt,[Rn,Rm] | 从 Rn+Rm 地址处读取半字到 Rt,并带符号扩展成 32 位 |
LDRSB Rt,[Rn,Rm] | 从 Rn+Rm 地址处读取字节到 Rt,并带符号扩展成 32 位 |
LDM Rn{!},reglist | 从 Rn 地址处读取多个字并加载到 reglist 列表寄存器中,每读一个字后 Rn 自增一次 |
在 LDM Rn{!},reglist 指令中, Rn 表示存储器单元起始地址的寄存器;“!” 是一个可选的回写后缀, reglist 列表中包含 Rn 寄存器时不要回写后缀,否则须带回写后缀 “!”。带后缀时,在数据传送完毕后,将最后的地址写回到 Rn=Rn+4×(n-1),n 为 reglist 中寄存器的个数。 Rn 不能为 R15,reglist 可以为 R0~ R15 的任意组合,Rn 寄存器中的值必须字对齐。这些指令不影响 N、 Z、 C、 V 状态标志。
存数指令
将 CPU 内部寄存器中的内容存储至存储器中的指令被称为存数指令。STR、 STRH 和 STRB 指令将 Rt 寄存器中的字、低半字或低字节存储至存储器单元。存储器单元地址由 Rn 与 Rm 之和决定。 Rt、 Rn 和 Rm 必须为 R0~ R7 之一。
指令 | 说明 |
---|---|
STR Rt,[<Rn|SP> {,#imm}] | 把 Rt 中的字存储到地址 SP/Rn+#imm 处, imm=0,4,8,…,1020 |
STR Rt,[Rn,Rm] | 把 Rt 中的字存储到地址 Rn+ Rm 处 |
STRH Rt,[Rn{,#imm}] | 把 Rt 中的低半字存储到地址 SP/Rn+#imm 处, imm=0,2,4,…,62 |
STRH Rt,[Rn,Rm] | 把 Rt 中的低半字存储到地址 Rn+ Rm 处 |
STRB Rt, [Rn{,#imm}] | 把 Rt 中的低字节存储到地址 SP/Rn+#imm 处, imm=0,1,2…,31 |
STRB Rt,[Rn,Rm] | 把 Rt 中的低字节存储到地址 Rn+Rm 处 |
STM Rn!,reglist | 存储多个字到 Rn 处,每存一个字后 Rn 自增一次 |
STM Rn!,reglist 指令将 reglist 列表寄存器内容以字存储至 Rn 寄存器中的存储单元地址, 以 4 字节访问存储器地址单元,访问地址从 Rn 寄存器指定的地址值到 Rn+4×(n-1),n 为 reglist 中寄存器的个数。按寄存器编号递增顺序访问,最低编号使用最低地址空间,最高编号使用最高地址空间。对于 STM 指令,若 reglist 列表中包含了 Rn 寄存器,则 Rn 寄存器必须位于列表首位;若列表中不包含 Rn,则将位于 Rn+4×n 地址的指令回写到 Rn 寄存器中。这些指令不影响 N、 Z、 C、 V 状态标志。
浮点单元的存储器访问指令
指令(#offset 域为可选的) | 说明 |
---|---|
VLDR.32 Sd,[Rn,#offset] | 读取存储器中的单精度数据到单精度寄存器 Sd |
VLDR.64 Dc,[Rn,#offset] | 读取存储器中的双精度数据到双精度寄存器 Dd |
VSTR.32 Sd,[Rn,#offset] | 写单精度寄存器 Sd 中的单精度数据到存储器 |
VSTR.64 Dd,[Rn,#offset] | 写双精度寄存器 Dd 中的双精度数据到存储器 |
PC 相关寻址的存储器访问指令
存储器访问可以产生相对于当前PC的地址值和偏移值。它常用于将立即数加载到寄存器中,也可被称作文本池访问。
指令(#offset 域为可选的) | 描述 |
---|---|
LDRB Rt,[PC,#offset] | 利用 PC 偏移加载无符号字节到 Rt |
LDRSB Rt,[PC,#offset] | 对字节数据进行有符号展开并利用 PC 偏移加载到 Rt |
LDRH Rt,[PC,#offset] | 利用 PC 偏移加载无符号半字到 Rt |
LDRSH Rt,[PC,#offset] | 对半字数据进行有符号展开并利用 PC 偏移加载到 Rt |
LDR Rt,[PC,#offset] | 利用 PC 偏移加载字数据到 Rt |
LDRD Rt,Rt2,[PC,#offset] | 利用 PC 偏移加载双字数据到 Rt 和 Rt2 |
PC 相关寻址到浮点单元存储器访问指令
指令(#offset 域为可选的) | 描述 |
---|---|
VLDR.32 Sd,[PC,#offset] | 利用 PC 偏移加载单精度数据到单精度寄存器 Sd |
VLDR.64 Dd,[PC,#offset] | 利用 PC 偏移加载双精度数据到双精度寄存器 Dd |
寄存器偏移的存储器访问指令
处理的数据数组的地址为基地址和从索值计算出的偏移得到的情况。为了进一步提高地址计算的效率,在加到基地址寄存器前,索值可以进行 0~3 位的移位。
寄存器偏移访问实例 | 描述 |
---|---|
LDRB Rd,[Rn,Rm{,LSL #n}] | 从存储器位置 Rn+(Rm<<n) 处读取字节 |
LDRSB Rd,[Rn,Rm{,LSL #n}] | 从存储器位置 Rn+(Rm<<n) 处读取字节并进行有符号展开 |
LDRH Rd,[Rn,Rm{,LSL #n}] | 从存储器位置 Rn+(Rm<<n) 处读取半字 |
LDRSH Rd,[Rn,Rm{,LSL #n}] | 从存储器位置 Rn+(Rm<<n) 处读取半字并进行有符号展开 |
LDR Rd,[Rn,Rm{,LSL #n}] | 从存储器位置 Rn+(Rm<<n) 处读取字 |
STRB Rd,[Rn,Rm{,LSL #n}] | 往存储器位置 Rn+(Rm<<n) 存储字节 |
STRH Rd,[Rn,Rm{,LSL #n}] | 往存储器位置 Rn+(Rm<<n) 存储半字 |
STR Rd,[Rn,Rm{,LSL #n}] | 往存储器位置 Rn+(Rm<<n) 存储字 |
后序存储器访问指令
具有立即数寻址模式的存储器访问指令也有一个立即数偏移数值。不过,在存储器访问期间是不会用到偏移的,它会在数据传输结束后更新地址寄存器。
若使用后序存储器寻址模式,由于在数据传输成功完成时,基地址寄存器总会得到更新因此无须使用感叹号( ! )。
后序寻址模式在处理数组中的数据时非常有用,在访问数组中的元素时,地址寄存器可以自动调整,节省了代码大小和执行时间。
注意:后序指令中不能使用 R15(PC) 或 R14(SP),后序存储器访问指令都是 32 位的,偏移数值可以为整数或负数。
后序访问实例 | 描述 |
---|---|
LDRB Rd,[Rn],#offset | 读取存储器 [Rn] 处的字节到Rd,然后更新 Rn 到 Rn+offset |
LDRSB Rd,[Rn],#offset | 读取存储器 [Rn] 处的字节到 Rd 并进行有符号展开,然后更新 Rn 到 Rn+offset |
LDRH Rd,[Rn],#offset | 读取存储器 [Rn] 处的半字到Rd,然后更新 Rn 到 Rn+offset |
LDRSH Rd,[Rn],#offset | 读取存储器 [Rn] 处的半字到 Rd 并进行有符号展开,然后更新 Rn 到 Rn+offset |
LDR Rd,[Rn],#offset | 读取存储器 [Rn] 处的字到Rd,然后更新 Rn 到 Rn+offset |
LDRD Rd1,Rd2,[Rn],#offset | 读取存储器 [Rn] 处的双字到 Rd1、Rd2,然后更新 Rn 到 Rn+offset |
STRB Rd,[Rn],#offset | 存储字节到存储器 [Rn],然后更新 Rn 到 Rn+offset |
STRH Rd,[Rn],#offset | 存储半字到存储器 [Rn],然后更新 Rn 到 Rn+offset |
STR Rd,[Rn],#offset | 存储字到存储器 [Rn],然后更新 Rn 到 Rn+offset |
STRD Rd1,Rd2,[Rn],#offset | 存储双字到存储器 [Rn],然后更新 Rn 到 Rn+offset |
多加载/存储存储器访问指令
ARM架构的一个重要优势在于,可以读或写存储器中多个连续数据,LDM(加载多个寄存器)和 STM(存储多个寄存器)指令只支持32位数据,它们支持两种前序:
- IA:在每次读/写后增加地址。
- DB:在每次读/写前减小地址。
LDM 和 STM 指令在使用时可以不进行基地址写回。
多加载/存储实例 | 描述 |
---|---|
LDMIA Rn,reglist | 从 Rn 指定的存储器位置读取多个字,地址在每次读取后增加 |
LDMDB Rn,reglist | 从 Rn 指定的存储器位置读取多个字,地址在每次读取前减小 |
STMIA Rn,reglist | 往 Rn 指定的存储器位置写人多个字,地址在每次写入后增加 |
STMDB Rn,reglist | 往 Rn 指定的存储器位置写人多个字,地址在每次写人前减小 |
与其他的加载/存储指令类似,可以在 STM 和 LDM 中(Rn)使用写回 ( ! ) 。
浮点单元多加载/存储存储器访问指令
多加载/存储实例 | 描述 |
---|---|
VLDMIA.32 Rn,reglist | 读取多个单精度数据,地址在每次读取后增加 |
VLDMDB.32 Rn,reglist | 读取多个单精度数据,地址在每次读取前减小 |
VLDMIA.64 Rn,reglist | 读取多个双精度数据,地址在每次读取后增加 |
VLDMDB.64 Rn,reglist | 读取多个双精度数据,地址在每次读取前减小 |
VSTMIA.32 Rn,reglist | 写人多个单精度数据,地址在每次写入后增加 |
VSTMDB.32 Rn,reglist | 写人多个单精度数据,地址在每次写人前减小 |
VSTMIA.64 Rn,reglist | 写人多个双精度数据,地址在每次写人后增加 |
VSTMDB.64 Rn,reglist | 写人多个双精度数据,地址在每次写人前减小 |
栈操作指令
栈的 push 和 pop 为另外一种形式的多存储和多加载,它们利用当前选定的栈指针来生成地址。当前栈指针可以是主栈指针(MSP),也可以是进程栈指针(PSP)实际选择是由处理器的当前模式和 CONTROL 特殊寄存器的数值决定。
PUSH 指令将寄存器值存于栈中,最低编号寄存器使用最低存储地址空间,最高编号寄存器使用最高存储地址空间; POP 指令将值从栈中弹回寄存器,最低编号寄存器使用最低存储地址空间,最高编号寄存器使用最高存储地址空间。执行 PUSH指令后,更新 SP 寄存器值 SP=SP-4;执行 POP 指令后更新 SP 寄存器值 SP=SP+4。若 POP指令的 reglist 列表中包含了 PC 寄存器,则 POP 指令在执行完成后会跳转到 PC 所指地址处。该值最低位通常用于更新 xPSR 的 T 位, 此位必须置 1 才能确保程序正常运行。
栈操作 | 描述 |
---|---|
PUSH reglist | 进栈指令,SP 递减 4,reglist 为 R0-R7,LR |
POP reglist | 出栈指令,SP 递增 4,reglist 为 R0-R7,PC |
寄存器列表的语法和 LDM 与 STM 相同。
对于PUSH指令,通常会对应一个具有相同寄存器列表的 POP,不过这也并不是必然的。例如,异常中就有使用 POP 作为函数返回的情形:
PUSH {R4-R6,LR} ;在子程序开始处保存 R4-R6 和 LR(链接寄存器)
;LR 中包含返回地址
... ;子程序中的处理
POP {R4-R6,PC} ;从栈中恢复 R4-R6 和返回地址,返回地址直接存入 PC
;这样会触发跳转(子程序返回)
PUSH {R0,R4-R7} ;将 R0, R4, R5, R6, R7 寄存器值存入栈
PUSH {R2,LR} ;将 R2, LR 寄存器值存入栈
POP {R0,R6,PC} ;出栈值存入R0, R6, PC 中,同时程序跳转至PC 所指向的地址开始执行
除了将返回地址恢复到 IR 然后写入程序计数器(PC)外,可以将返回地址直接写人 PC 以减少指令数和周期数。
16 位的 PUSH 和 POP 只能使用低寄存器(RO~R7)、LR(用于PUSH)和 PC(用于POP)。因此,若在函数中某个高寄存器被修改时要保存寄存器的内容,则需要使用 32 位的 PUSH 和 POP 指令对。
浮点单元寄存器的压栈和出栈指令
若浮点单元存在,VPUSH 和 VPOP 指令也可用于执行针对浮点单元中寄存器的栈操作与 PUSH 和 POP 不同,VPUSH 和 VPOP 指令需要:
- 寄存器列表中寄存器是连续的。
- 每次 VPUSH 或 VPOP 压栈/出栈的寄存器的最大数量为16。
若需要保存超过 16 个的单精度浮点寄存器,可以使用双精度指令或者使用两组 VPUSH 和 VPOP。
栈操作示例 | 描述 |
---|---|
VPUSH.32 reglist | 将单精度寄存器存入栈中(如 S0~S31) |
VPUSH.64 reglist | 将双精度寄存器存入栈中(如 d0~d15) |
VPOP.32 reglist | 从栈中恢复单精度寄存器 |
VPOP.64 reglist | 从栈中恢复双精度寄存器 |
生成与指针 PC 相关的地址指令
ADR 指令用于将 PC 值加上一个偏移量得到的地址写进目标寄存器中。若利用 ADR 指令将生成的目标地址用于跳转指令 BX 或 BLX,则必须确保该地址最后一位为 1。Rd 为目标寄存器, label 为与 PC 相关的表达式。在该指令下, Rd 必须为 R0~ R7, label 的值必须字对齐且在当前 PC 值的 1024 字节以内。此指令不影响 N、 Z、 C、 V 状态标志。这条指令主要供汇编阶段使用, 一般可将其看成一条伪指令。
指令 | 说明 |
---|---|
ADR Rd,label | 生成与 PC 相关的地址,将 label 相对于当前指令的偏移地址值与 PC 相加或相减(label 有前后,即负、正) 后写入 Rd 中 |
非特权访问等级的存储器访问指令
利用一组加载和存储指令,外于特权访问等级的程序代码可以访问非特权访问权限的有存储器。
有些 OS 环境可能会需要这些指令,因为非特权应用程序可以访问以数据指针作为输入参数的 API 函数(运行在特权访问等级),而 API 操作的存储器数据由指针决定。若数据访问由普通的加载和存储指令完成,非特权应用任务则可以利用这些 API 修改被其他任务或 OS 内核使用的数据。而将这些 API 编码为非特权访问等级的特殊的加载和存储指令后,它们只能访问应用任务可以访问的数据。
非特权访问等级 LDR/STR 示例(#offset城可选) | 描述 |
---|---|
LDRBT Rd,[Rn,#offset] | 从存储器位置 Rn+offset 读取字节 |
LDRSBT Rd,[Rn,#offset] | 从存储器位置 Rn+offset 读取有符号展开的字节 |
LDRHT Rd,[Rn,#offset] | 从存储器位置 Rn+offset 读取半字 |
LDRSHT Rd,[Rn,#offset] | 从存储器位置 Rn+offset 读取有符号展开的半字 |
LDRT Rd,[Rn,#offset] | 从存储器位置 Rn+offset 读取字 |
STRBT Rd,[Rn,#offset] | 往存储器位置 Rn+offset 存储字节 |
STRHT Rd,[Rn,#offset] | 往存储器位置 Rn+offset 存储半字 |
STRT Rd,[Rn,#offset] | 往存储器位置 Rn+offset 存储字 |
排他访问指令
排他访问示例 | 描述 |
---|---|
LDREXB RT,[Rn] | 从存储器位置 Rn 排他读取字节 |
LDREXH RT,[Rn] | 从存储器位置 Rn 排他读取半字 |
LDREX Rt,[Rn,#offset] | 从存储器位置 Rn 排他读取字 |
STREXB Rd,Rt,[Rn] | 往存储器位置 Rt 排他存储字节,返回状态位于 Rd 中 |
STREXH Rd,Rt,[Rn] | 往存储器位置 Rt 排他存储半字,返回状态位于 Rd 中 |
STREX Rd,Rt,[Rn,#offset] | 往存储器位置 Rt 排他存储字,返回状态位于 Rd 中 |
CLREX | 强制本地排他访问监控清零,使得下一次排他存储失败。它并不是排他存储器访问指令,不过由于它的用法,在这里列了出来 |
2.2 数据操作类指令#
数据操作主要指算术运算、逻辑运算、移位等。 相对应的指令有算术运算类指令、逻辑运算类指令、移位类指令等。本节将对不同类型的各类指令进行介绍。
2.2.1 算术运算
算术数据运算指令
指令 | 说明 |
---|---|
ADC {Rd,}Rn,Rm | 带进位加法。 Rd←Rn+Rm+C,影响 N、 Z、 C 和 V 标志位 |
ADD {Rd} Rn, <Rm|#imm> | 加法。 Rd←Rn+Rm,影响 N、 Z、 C 和 V 标志位 |
ADDW Rd,Rn,#immed | 寄存器和 12 位立即数相加,Rd = Rn + #immed |
RSB {Rd,} Rn,<Rm|#imm> | 反向减法。 Rd←imm-Rn,影响 N、 Z、 C 和 V 标志位(部分汇编环境不支持) |
SBC {Rd,}Rn,<Rm|#imm> | 带借位减法。 Rd←Rn-Rm-C,影响 N、 Z、 C 和 V 标志位 |
SUB {Rd } Rn, <Rm|#imm> | 常规减法。 Rd←Rn-Rm/ #imm,影响 N、 Z、 C 和 V 标志位 |
SUBW Rd,Rn,#immed | 寄存器和 12 位立即数相减,Rd = Rn - #immed |
MUL Rd, Rn Rm | 常规乘法。 Rd←Rn×Rm,同时更新 N、 Z 状态标志,不影响 C、 V状态标志。该指令所得结果与操作数是否为无符号数、有符号数无关。 Rd、 Rn、 Rm 寄存器必须为 R0~ R7,且 Rd 与 Rm 须一致 |
UDIV Rd,Rn,Rm | 无符号除法,Rd = Rn/Rm |
SDIV Rd,Rn,Rm | 有符号除法,Rd = Rn/Rm |
这些指令在使用时可以带着或不带 S 后级以及指明 APSR 是否应更新。
若出现被零除的情况,UDIV 和 SDIV 指令的结果默认为 0。可以设置 NVIC 配置控制寄存器中的 DIVBYZERO 位,这样,在出现被零除时就可以产生异常(使用错误)。
ADC、 ADD、 RSB、 SBC 和 SUB 操作数限制条件
指令 | Rd | Rn | Rm | imm | 限制条件 |
---|---|---|---|---|---|
ADC | R0~R7 | R0~R7 | R0~R7 | Rd 和 Rn 必须相同 | |
ADD | R0~R15 | R0~R15 | R0~PC | Rd 和 Rn 必须相同 Rn 和 Rm 不能同时指定为 PC 寄存器 | |
ADD | R0~ R7 | SP 或 PC | 0~1020 | 立即数必须为 4 的整数倍 | |
ADD | SP | SP | 0~508 | 立即数必须为 4 的整数倍 | |
ADD | R0~R7 | R0~R7 | 0~7 | ||
ADD | R0~R7 | R0~R7 | 0~255 | Rd 和 Rn 必须相同 | |
ADD | R0~R7 | R0~R7 | R0~R7 | ||
RSB | R0~R7 | R0~R7 | |||
SBC | R0~ R7 | R0~ R7 | R0~ R7 | Rd 和 Rn 必须相同 | |
SUB | SP | SP | 0~508 | 立即数必须为 4 的整数倍 | |
SUB | R0~R7 | R0~R7 | 0~7 | ||
SUB | R0~R7 | R0~R7 | 0~255 | Rd 和 Rn 必须相同 | |
SUB | R0~R7 | R0~R7 | R0~R7 |
乘法和 MAC(乘累加)指令
指令(由于 APSR 不更新,因此无 S 后缀) | 操作 |
---|---|
MLA Rd,Rn,Rm,Ra | 32 位 MAC 指令,32 位结果,Rd = Ra + Rn * Rm |
MLS Rd,Rn,Rm,Ra | 32 位乘减指令,32 位结果,Rd = Ra - Rn * Rm |
SMULL RdLo,RdHi,Rn,Rm | 有符号数据 32 位乘指令,{RdHi,RdLo} = Rn * Rm |
SMLAL RdLo,RdHi,Rn,Rm | 有符号数据 32 位 MAC 指令,{RdHi,RdLo} += Rn * Rm |
UMULL RdLo,RdHi,Rn,Rm | 无符号数据 32 位乘指令,{RdHi,RdLo} = Rn * Rm |
UMLAL RdLo,RdHi,Rn,Rm | 无符号数据 32 位 MAC 指令,{RdHi,RdLo} += Rn * Rm |
2.2.2 逻辑运算指令
与算术指令类似,这些指令的 16 位版本会更新 APSR 中的标志。若未指定 S 后缀,汇编器会将它们转换为 32 位指令。
若使用这些指令的 16 位版本,则只能操作两个寄存器,且目的寄存器需要为源寄存器之另外,还必须是低寄存器(RO~R7),而且要使用 S 后缀(APSR更新)。ORN 指令没有 16 位的形式。
AND、 EOR 和 ORR 指令把寄存器 Rn、 Rm 的值逐位与、异或和或操作; BIC 指令是将寄存器 Rn 的值与 Rm 的值的反码按位做逻辑“与”操作,结果保存到 Rd 中,作用是实现 Rn 中对应 Rm 为 1 的位清零功能。这些指令更新 N、 Z 状态标志,不影响 C、 Z 状态标志。
Rd、 Rn 和 Rm 必须为 R0~ R7,其中 Rd 为目标寄存器, Rn 为存放第一个操作数寄存器且必须和目标寄存器 Rd 一致(即 Rd 就是 Rn),Rm 为存放第二个操作数寄存器。
逻辑运算类指令
指令 | 说明 | 举例 |
---|---|---|
AND {Rd,}Rn,<Rm|#imm> | 按位与 | AND R2,R2,R1 |
ORR {Rd,}Rn,<Rm|#imm> | 按位或 | ORR R2,R2,R5 |
EOR {Rd,}Rn,<Rm|#imm> | 按位异或 | EOR R7,R7,R6 |
BIC {Rd,}Rn,<Rm|#imm> | 位段清零 | BIC R0,R0,R1 |
ORN Rd,Rn,<Rm|#imm> | 按位或非 | ORN R0,R1,R2 |
2.2.3 移位和循环移位指令
若使用了 S 后缀,这些循环和移位指令也会更新 APSR 中的进位标志。若移位运算移动了寄存器中的多个位,进位标志 C 的数据就会为移出寄存器的最后一位。
要使用这些指令的 16 位版本,寄存器需要为低寄存器,而且应该使用 S 后缀(更新APSR)。RRX 指令没有 16 位的形式。
ASR、 LSL、 LSR 和 ROR 指令,将寄存器 Rm 的值由寄存器 Rs 或立即数 imm 决定移动位数,执行算术右移、逻辑左移、逻辑右移和循环右移操作。这些指令中, Rd、 Rm、 Rs 必须为 R0~R7。对于非立即数指令, Rd 和 Rm 必须一致。 Rd 为目标寄存器,若省去 Rd,则其值与 Rm 寄存器一致;Rm 为存放被移位数据寄存器; Rs 为存放移位长度寄存器; imm 为移位长度, ASR 指令的移位长度范围为 1~ 32, LSL 指令的移位长度范围为 0~ 31, LSR 指令的移位长度范围为 1~ 32。
指令(可选 S 后缀未列出) | 操作 |
---|---|
ASR {Rd,}Rm,<Rs|#immed> | 算术右移 Rd = Rm>>Rs |
LSL {Rd,}Rm,<Rs|#immed> | 逻辑左移 |
LSR {Rd,}Rm,<Rs|#immed> | 逻辑右移 |
ROR {Rd,} Rm, Rs | 循环右移 |
RRX Rd,Rn | 循环右移并展开,{C,Rd} = |

-
单向移位指令
算术右移指令 ASR 比较特别,它把要操作的字节当作有符号数,而符号位(b31)保持不变,其他位右移一位,即首先将 b0 位移入 C 中,其他位( b1~b31)右移一位,相当于操作数除以 2。为了保证符号不变, ASR 指令使符号位 b31 返回本身。逻辑右移指令 LSR 把 32位操作数右移一位,首先将 b0 位移入 C 中,其他一位右移一位, 0 移入 b31。根据结果,ASR、LSL、 LSR 指令对标志位 N、 Z 有影响;最后移出位更新 C 标志位。 -
循环移位指令
在循环右移指令 ROR 中,将 b0 位移入 b31 中的同时也移入 C 中,其他位右移一位,从b31~ b0 内部看循环右移了一位。根据结果,ROR 指令对标志位 N、 Z 有影响;最后移出位更新 C 标志位。
2.2.4 数据转换运算(展开和返序)
有符号和无符号展开
寄存器 Rm 存放待扩展操作数,寄存器 Rd 为目标寄存器,Rm、Rd 必须为 R0~R7。这些指令不影响 N、 Z、 C、 V 状态标志。
指令 | 操作 |
---|---|
SXTB Rd,Rm | 将操作数 Rm 的 bit[7:0]带符号扩展到 32 位,结果保存到 Rd 中 |
SXTH Rd,Rm | 将操作数 Rm 的 bit[15:0]带符号扩展到 32 位,结果保存到 Rd 中 |
UXTB Rd,Rm | 将操作数 Rm 的 bit[15:0]带符号扩展到 32 位,结果保存到 Rd 中 |
UXTH Rd,Rm | 将操作数 Rm 的 bit[15:0]无符号扩展到 32 位,结果保存到 Rd 中 |
这些指令的 32 位形式可以访问高寄存器,而且可以选择在进行有符号展开运算前将输入数据循环右移。
具有可选循环移位的有符号和无符号展开
指令 | 操作 |
---|---|
SXTB Rd,Rm | 有符号展开字节为字,n = 8/16/24 |
SXTH Rd,Rm | 有符号展开半字为字,n = 8/16/24 |
UXTB Rd,Rm | 无符号展开字节为字,n = 8/16/24 |
UXTH Rd,Rm | 无符号展开半字为字,n = 8/16/24 |
SXTB/SXTH 使用 Rn 的 bit[7]/bit[15] 进行有符号展开,而 UXTB 和 UXTH 则将数据以零展开的方式扩展为 32 位。
例如,R0 为 0x55AA8765
SXTB R1,R0 ; R1 = 0x00000065
SXTH R1,R0 ; R1 = 0xFFFF8765
UXTB R1,R0 ; R1 = 0x00000065
UXTH R1,R0 ; R1 = 0x00008765
这些指令可以用于不同数据类型间的转换,在从存储器中加载数据时,可能会同时产生有符号展开和无符号展开(如 IDRB 用于无符号数据,LDRSB用于有符号数据)。
数据序转指令
该指令用于改变数据的字节顺序,Rn 为源寄存器,Rd为目标寄存器,且必须为 R0~R7 之一。 这些指令不影响 N、 Z、C、V 状态标志。
指令 | 操作 |
---|---|
REV Rd,Rn | 反转字中的字节:将 32 位大端数据转为小端存放, 或将 32 位小端数据转为大端存放 |
REV16 Rd,Rn | 反转每个半字中的字节:将一个 32 位数据划分成两个 16 位大端数据,将这两个 16 位大端数据转为小端存放; 或将一个 32 位数据划分成两个 16 位小端数据,将这两个 16 位小端数据转为大端存放 |
REVSH Rd,Rn | 反转低半字中的字节并将结果有符号展开:将 16 位带符号大端数据转为 32 位带符号小端数据, 或将 16 位带符号小端数据转为 32 位带符号大端数据 |

2.3 位域处理指令#
指令 | 操作 |
---|---|
BFC Rd,#lsb,#width | 清除寄存器中的位域 |
BFI Rd,Rn,#lsb,#width | 将位域插入寄存器 |
CLZ Rd,Rm | 前导零计数 |
RBIT Rd,Rn | 反转寄存器中的位顺序 |
SBFX Rd,Rn,#lsb,#width | 从源中复制位域并有符号展开 |
UBFX Rd,Rn,#lsb,#width | 从源寄存器中复制位域 |
BFC 用于清除寄存器中的指定位段(Bit Field)。这种指令通常用于将寄存器中的特定位段设置为0,而不影响其他位。
-
lsb 是位段的最低有效位(Least Significant Bit)的位置。
-
width 是要清除的位段的宽度,即要清除的位的数量。
LDR R0,=0x1234FFFF
BFC R0,#4,#8
结果:R0 = 0x1234F00F
BFI 用于将一个位字段(Bit Field)插入到另一个寄存器的指定位置。该指令通常用于需要在一个寄存器中插入一段数据的情况,而不影响其他位的值。
- Rd 是目标寄存器,表示将要被修改的寄存器。
- Rn 是源寄存器,表示提供要插入的位字段的寄存器。
- lsb 是目标寄存器中要插入位字段的最低有效位(Least Significant Bit)的位置。
- width 是要插入的位字段的宽度,即要从源寄存器中插入的位数。
LDR R0,=0x12345678
LDR R1,=0x3355AACC
BFI R1,R0,#8,#16
结果:R0 = 0x335678CC
CLZ 用于计算寄存器中从最高位(MSB)开始的连续零位的个数。若没有位为 1 则结果为 32,而所有位都为 1 则结果为 0。
- Rd 是目标寄存器,用于存储从最高位开始的连续零位的个数。
- Rm 是源寄存器,从其中读取要进行前导零位计数的数据。
RBIT 用于反转寄存器中的位顺序。
- Rd 是目标寄存器,用于存储反转位顺序后的结果。
- Rm 是源寄存器,从其中读取要进行位反转的数据。
RBIT 指令的作用是将源寄存器 Rm 中的位顺序进行反转,并将结果存储到目标寄存 Rd 中。例如,如果源寄存器 Rm 的二进制表示是 1101,则目标寄存器 Rd 中存储的值将是 1011。
该指令常在数据通信中用于串行位数据流的处理。RBIT 指令对于一些位操作和加密算法非常有用。例如,在某些情况下,可以使用RBIT指令来实现位级别的混淆和反混淆,从而增强数据的安全性。此外,它还可以用于一些编码和解码操作,或者在通信协议中进行数据的转换和处理。
UBFX 用于从无符号数中提取指定的位字段(bit field)。该指令允许从寄存器中的任意位置(由操作数<#lsb>指定)开始提取任意宽度(由操作#<width>指定)的位域,将其零展开后放入目的寄存器。
- Rd 是目标寄存器,用于存储从源寄存器中提取的位字段。
- Rn 是源寄存器,从中提取位字段的无符号整数。
- lsb 是位字段的起始位索引(Least Significant Bit),即要提取的位段的最低有效位的位置。
- width 是要提取的位字段的宽度,即要从源寄存器中提取的位数。
LDR R0,=0x5678ABCD
UBFX R1,R0,#4,#8
结果:R1 = 0x000000BC
SBFX 指令的作用是从源寄存器 Rn 中的有符号整数中提取位字段,从给定的起始位索引<#lsb> 开始,提取指定数量的位数 #<width>,并将结果存储到目标寄存器 Rd 中。
- Rd 是目标寄存器,用于存储从源寄存器中提取的位字段。
- Rn 是源寄存器,从中提取位字段的有符号整数。
- lsb 是位字段的起始位索引(Least Significant Bit),即要提取的位段的最低有效位的位置。
- width 是要提取的位字段的宽度,即要从源寄存器中提取的位数。
LDR R0,=0x5678ABCD
SBFX R1,R0,#4,#8
结果:R1 = 0xFFFFFFBC
2.4 比较和测试指令#
指令 | 操作 |
---|---|
CMP Rn,<Rm|#imm> | (减)比较指令。 Rn-Rm/#imm,更新 N、 Z、 C 和 V 标志,但不保存所得结果。 Rn、 Rm 寄存器为 R0~ R7,立即数 imm 的范围 0~ 255 |
CMN Rn,<Rm|#imm> | 负比较:加比较指令。 Rn+Rm,更新 N、 Z、 C 和 V 标志,但不保存所得结果。 Rn、 Rm 寄存器必须为 R0~ R7 |
TST Rn,<Rm|#imm> | 测试(按位与):计算 Rn 和 Rm 相与后的结果,将 Rn 寄存器值逐位与 Rm 寄存器值进行与操作,但不保存所得结果。为测试寄存器 Rn 某位为 0 或 1,将 Rn 寄存器某位置 1,其余位清零。寄存器 Rn、Rm 必须为 R0~ R7。该指令根据结果,更新 N、 Z 状态标志,但不影响 C、 V 状态标志 |
TEQ Rn,<Rm|#imm> | 测试(按位异或):计算 Rn 和 Rm 异或后的结果,APSR 中的 N 位和 Z 位更新,但运算的结果不会保存,若使用了桶形移位则更新 C 位 |
由于 APSR 总是会更新,因此这些指令中不存在 S 后缀。
2.5 程序流控制#
2.5.1 跳转
可以引发跳转的指令:
- 跳转指令(如 B、BX)
- 更新 R15(PC)的数据处理指令(如 MOV、ADD)。
- 写人 PC 的读存储器指令(如LDR、LDM、POP)。
一般来说,尽管可以使用任意一种操作来实现跳转,比较常用的还是 B(跳转)、BX(间接跳转)以及 POP 指令(通常用于函数返回)。
无条件跳转指令
指令 | 跳转范围 | 操作 |
---|---|---|
B label | -256B~ +254B | 转移到 Label 处对应的地址。若跳转范围超过了 +/-2KB,则可以指定 B.W<label> 使用 32 位版本的跳转指令,这样可以得到较大的范围 |
B.W label | -8MB~ +8MB | 转移到 Label 处对应的地址。 |
BX Rm | 任意 | 间接跳转。转移到由寄存器 Rm 给出的地址,并且基于 Rm 第 0 位设置处理器的执行状态(T位)(由于 Cortex-M 处理器只支持 Thumb 状态,寄存器 Rm 的 bit[0]必须为 1,否则会导致硬件故障) |
2.5.2 函数调用
执行跳转并同时将返回地址保存到链接寄存器(LR),这样在函数调用结束后处理器还
以跳回之前的程序。
函数调用指令
指令 | 跳转范围 | 操作 |
---|---|---|
BL label | -16MB~ +16MB | 转移到 Label 处对应的地址,并且把转移前的下条指令地址保存到 LR,置寄存器 LR 的 bit[0]为 1,保证随后执行 POP {PC}或 BX 指令时成功返回分支 |
BLX Rm | 任意 | 转移到由寄存器 Rm 给出的地址,并且把转移前的下条指令地址保存到 LR,以及更新 EPSR 中的 T 为 Rm 的最低位。寄存器 Rm 的 bit[0]必须为 1,否则会导致硬件故障 |
当执行这些指令时:
- 程序计数器被置为跳转目标地址。
- 链接寄存器(LR/R14)被更新为返回地址,这也是已执行的 BL/BLX 后指令的地址。
- 若指令为 BLX,则 EPSR 中的 Thumb 位也会被更新为存放跳转目标地址的寄存器的最低位。
由于 Cortex-M3 和 M4 处理器只支持 Thumb 状态,BLX 操作中使用的寄存器的最低位必须要置为 1,要不然,它就表示试图切换至 ARM 状态,这样会引发错误异常。
若需要调用子程序则保存 LR,BL 指令会破坏 LR 寄存器的当前内容。因此,若程序代码稍后需要 LR 寄存器,则应该在执行 BL 前保存 LR。最常用的方法为在子程序开头处将 LR 压人栈中。
跳转控制类指令举例如下, 特别注意 BL 用于± 16MB 以内相对调用子程序。
BEQ label ;条件转移,标志位 Z=1 时转移到 label
BL func ;调用子程序 funC,把转移前的下条指令地址保存到 LR
BX LR ;返回到函数调用处
2.5.3 条件跳转
条件跳转基于 APSR 的当前值条件执行(N、Z、C、和 V 标志)。
APSR 中的标志(状态位),可用于条件跳转控制
标志 | 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 B<cond>.W ladel |
若条件为 true 则跳转到 label, 例如:CMP R0,#1 BEQ loop ;若 R0 等于 1 则跳转到 "loop" 若所需的跳转范围超过了 ±254 字节,则可能需要指定使用 32 位版本的跳转指令以增加跳转范围 |
条件执行和条件跳转用的后缀
条件后缀 | 标志位 | 含义 | 条件后缀 | 标志位 | 含义 |
---|---|---|---|---|---|
EQ | Z=1 | 相等 | HI | C=1 并且 Z=0 | 无符号数大于 |
NE | Z=0 | 不相等 | LS | C=1 或 Z=1 | 无符号数小于或等于 |
CS 或者 HS | C=1 | 无符号数大于或等于 | GE | N=V | 带符号数大于或等于 |
CC 或者 LO | C=0 | 无符号数小于 | LT | N!=V | 带符号数小于 |
MI | N=1 | 负数 | GT | Z=0 并且 N=V | 带符号数大于 |
PL | N=0 | 正数或零 | LE | Z=1 并且 N!=V | 带符号数小于或等于 |
VS | V=1 | 溢出 | AL | 任何情况 | 无条件执行 |
VC | V=0 | 未溢出 |
简单的条件跳转
CMP R0,#1 ; 比较R0和1
BEQ p2 ; 若相等则跳转p2
MOVS R3,#1 ; R3 = 1
B p3 ; 跳转到p3
p2
MOVS R3,#2
p3 ; 标号p3
...

2.5.4 比较和跳转
指令 | 操作 |
---|---|
CBZ <寄存器>, <目标地址> | 比较为零则跳转 |
CBNZ <寄存器>, <目标地址> | 比较非零则跳转 |
上面两个指令只支持向前跳转,不支持向后跳转。
CBZ:
<寄存器>
是一个寄存器标识符,用于存储需要检查是否为零的值。<目标地址>
是跳转到的目标地址,如果<寄存器>
中的值为零,则执行跳转到该地址。
CBZ R0, zero_detected ; 如果 R0 为零,则跳转到 zero_detected 标签处
; 继续执行其他指令
...
zero_detected:
; 在 R0 为零时执行的代码
例子:
i = 5;
while(i != 0)
{
funcl();
i--;
}
编译:
MOV R0,#5 ; 设置环境变量
loop1 CBZ R0,looplexit ; 若循环变量 0 则跳出循环
BL funcl ; 调用函数
SUBS R0,#1 ; 循环变量减小
B loopl ; 下一个循环
looplexit
CBNZ:
<寄存器>
是一个寄存器标识符,用于存储需要检查是否为零的值。<目标地址>
是跳转到的目标地址,如果<寄存器>
中的值不为零,则执行跳转到该地址。
CBNZ R0, nonzero_detected ; 如果 R0 不为零,则跳转到 nonzero_detected 标签处
; 继续执行其他指令
...
nonzero_detected:
; 在 R0 不为零时执行的代码
例子:
status = strchr(email_address, "@");
if(staus == 0) // 若 email 地址中无 @ 则 status 为 0
{
show_error_message();
exit(1);
}
编译:
...
BL strchr
CBNZ R0,email_looks_okay ;结果非零则跳转
BL show_error_message
BL exit
email_looks_okay
...
APSR 的值不受 CBZ 和 CBNZ 指令的影响。
2.5.5 条件执行
在 IT(IF-THEN)指令执行后,接下来,最多 4 个指令可以根据 IT 指令指定的条件以及 APSR 数值条件执行。
IT 指令块中包含 1 个指明条件执行细节的 IT 指令,后面为 1~4 个条件执行指令。条件执行指令可以为数据处理指令或存储器访问指令。IT 块中的最后一个条件执行指令也可以为条件跳转指令。
IT 指令语句中包含 IT 指令操作码并附加最多 3 个可选后缀 T(then) 以及 E(else),后面是要检查的条件,与条件跳转中的条件符号一样。T/E 表明 IT 指令块接下来还有几条指令以及在符合条件时它们是否应该执行。
注意,当使用 E 后缀时,IT 指令块中指令对应的执行条件必须要同 IT 指令指定的条件
相反。
各种大小的 IT 指令块
表例:
- <x> 指定第二个指令的执行条件
- <y> 指定第三个指令的执行条件
- <z> 指定第四个指令的执行条件
- <cond> 指定指令块的基本条件,若<cond>为 true,则执行 IT 后的第一条指令。
IT 块[每个<x>、<y>和<z>可以为 T 或 E] | 例子 | |
---|---|---|
只有一个条件指令 | IT <cond> instr1<cond> |
IT EQ ADDEQ R0,R0,R1 |
两个条件指令 | IT <x> <cond> instr1<cond> instr2<cond or ~(cond)> |
ITE GE ADDGE R0,R0,R1 ADDLT R0,R0,R3 |
三个条件指令 | IT <x><y> <cond> instr1<cond> instr2<cond or ~(cond)> instr3<cond or ~(cond)> |
ITET GT ADDGT R0,R0,R1 ADDLE R0,R0,R3 ADDGT R2,R4,#1 |
四个条件指令 | IT <x><y><z> <cond> instr1<cond> instr2<cond or ~(cond)> instr3<cond or ~(cond)> instr4<cond or ~(cond)> |
ITETT NE ADDNE R0,R0,R1 ADDEQ R0,R0,R3 ADDNE R2,R4,#1 MOVNE R5,R3 |
若<cond>为 AL,则在条件控制中不能使用 E,因为它表示指令永远不会执行。
IT 指令块中的数据处理指令不应修改 APSR 的数值,当有些 16 位的数据处理指令在 IT 指令块中使用时,APSR不会更新,这一点和它们正常操作更新 APSR 的情况不同。这样,就可以在 IT 指令块中使用 16 位的数据处理指令以降低代码大小。
对于一些汇编工具,代码中无须使用 IT 指令。通过简单地给普通指令添加条件后缀,汇编工具(如 DS-5 Professional 或 Keil MDK-ARM 的 ARM 汇编器)会在前面自动插入所需的 IT 指令。由于无须手动插人 IT 指令,因此这样有助于经典 ARM 处理器(如ARM7TDMI)往 Cortex-M3/M4 的移植。
初始汇编代码:
...
CMP R1,#2
ADDEQ R0,R1,#1
...
生成的目标文件中的反汇编代码
...
CMP R1,#2
IT
ADDEQ R0,R1,#1
...
2.5.6 表格跳转
TBB(表格跳转字节)和 TBH(表格跳转半字),它们和跳转表一起使用,通常用于实现 C 代码中的 switch 语句。由于程序计数器数值的第 0 位总是为 0,利用表格跳转指令的跳转表也就无须保存这一位,因此,在目标地址计算中跳转偏移被乘以 2。
2.6 饱和运算#
Cortex-M3 处理器支持两个用于有符号和无符号数据饱和调整的指令:SSAT(用于有符号数据)和USAT(用于无符号数据)。Cortex-M4 处理器同样支持这两条指令,而且还支持用于饱和算法的其他指令。
饱和多用于信号处理:

饱和运算通过将数据强制置为最大允许值,减小了数据畸变。畸变仍然是存在的,不过若数据没有超过最大范围太多,就不会有太大的问题。
饱和运算指令
指令 | 描述 |
---|---|
SSAT Rd,#immed,Rn, | 有符号数据的饱和 |
USAT Rd,#immed,Rn, | 有符号数据转换为无符号数据的饱和 |
- Rn 为输入值;
- shift 为饱和前可选的移位操作,可以为 #LSL N 或 #ASR N;
- immed 为执行饱和的位的位置;
- Rd 为目的寄存器
除了目的寄存器,APSR 中的 Q 位也会受结果的影响。若在运算中出现饱和 Q 标志就会置位,它可以通过写 APSR 清除。
SSAT指令例子:(32 位有符号数值要被饱和为16 位有符号数)
SSAT R1,#16,R0
有符号饱和结果实例
输入(R0) | 输出(R1) | Q位 |
---|---|---|
0x00020000 | 0x00007FFF | 置位 |
0x00008000 | 0x00007FFF | 置位 |
0x00007FFF | 0x00007FFF | 不变 |
0x00000000 | 0x00000000 | 不变 |
0xFFFF8000 | 0xFFFF8000 | 不变 |
0xFFFF7FFF | 0xFFFF8000 | 置位 |
0xFFFE0000 | 0xFFFF8000 | 置位 |

USAT指令例子:
USAT R1,#16,R0
无符号饱和结果示例
输入(R0) | 输出(R1) | Q位 |
---|---|---|
0x00020000 | 0x0000FFFF | 置位 |
0x00008000 | 0x00008000 | 不变 |
0x00007FFF | 0x00007FFF | 不变 |
0x00000000 | 0x00000000 | 不变 |
0xFFFF8000 | 0x00000000 | 置位 |
0xFFFF8001 | 0x00000000 | 置位 |
0xFFFFFFFF | 0x00000000 | 置位 |
2.7 异常相关指令#
管理调用(SVC)指令:SVC #immed
操作系统服务调用,带立即数调用代码。 SVC 指令触发 SVC 异常。处理器忽视立即数 imm,若需要,该值可通过异常处理程序重新取回,以确定哪些服务正在请求。执行 SVC 指令期间,当前任务的优先级大于或等于 SVC 指令调用处理程序时,将产生一个错误。不影响 N、 Z、 C、 V 标志
管理调用(SVC)指令用于产生 SVC 异常(异常类型为11)。SVC一般用于嵌入式OS/实时OS(RTOS),其中,运行在非特权执行状态的应用可以请求运行在特权状态的 OS 的服务。SVC 异常机制提供了从非特权到特权的转换。
SVC 机制可以作为应用任务访问各种服务(包括 OS 服务或其他 API 函数)的入口,这样应用任务就可以在无须了解服务的实际存储器地址的情况下请求所需服务。它只需知道SVC服务编号、输入参数和返回结果。
SVC 指令要求 SVC 异常的优先级高于当前的优先级,而且常没有被 PRIMASK 等存器屏蔽,不然就会触发错误异常。因此,由于 NMI 和 HardFaut 异常的优先级总是比 SVC 异常大,也就无法在这两个处理中使用 SVC。
其中的立即数为 8 位,数值自身不会影响 SVC 异常的动作,不过 SVC 处理可以在程序中提取出这个数值并将其用作输入参数,这样可以确定应用任务所请求的服务。
改变处理器状态(CPS)指令
对于 Cortex-M 处理器,可以使用这条指令来设置或清除 PRIMASK 和 FAULTMASK 等中断屏蔽寄存器。注意,这些寄存器也可以用 MSR 和 MRS 指令访问。
CPS 指令在使用时必须要带一个后缀:IE(中断使能)或 ID(中断禁止)。由于Cortex-M3 和 Cortex-M4 处理器具有多个中断屏蔽寄存器,因此,还得指定要设置/清除的寄存器。
RRIMASK 和 FAULTMASK 的设置及清除指令
指令 | 操作 |
---|---|
CPSIE I | 使能中断(清除 PRIMASK) 和 _enable_irq() 相同 |
CPSID I | 禁止中断(设置PRIMASK),NMI 和 HardFault 不受影响 和 _disable_irg()相同 |
CPSIE F | 使能中断(清除 FAULTMASK) 和 _enable_fault_irq() 相同 |
CPSID F | 禁止错误中断(设置FAULTMASK),NMI不受影响 和 _disable_fault_irg() 相同 |
切换 PRIMASK 和 FAULTMASK 可以禁止或使能中断,经常用于确保时序关键的任务在不被打断的情况下快速完成。
2.8 休眠模式相关指令#
进入休眠模式
WFI ;等待中断(进入休眠):休眠并且在发生事件时被唤醒。不影响 N、Z、C、V 标志
WFE ;等待事件(条件进入休眠):休眠并且在发生中断时被唤醒。不影响 N、Z、C、V 标志
C 编程(符合 CMSIS 的设备驱动库)
_WFI(); //等待中断(进入休眠)
_WFE(); //等待事件(条件进入休眠)
WFI(等待中断)指令会使处理器立即进入休眠模式,中断、复位或调试操作可以将处理器从休眠中唤醒。
WFE(等待事件),会使处理器有条件地进入休眠。
这两条指令,均只用于低功耗模式,并不产生其他操作(这一点类似于NOP指令)。休眠指令 WFE 执行情况由事件寄存器决定,若事件寄存器为 0,则只有在发生如下事件时才执行:发生异常,且该异常未被异常屏蔽寄存器或当前优先级屏蔽;在进入异常期间,系统控制寄存器的SEVONPEND置位 ;在使能调试模式时,触发调试请求;外围设备(Peripheral Equipment) 发出一个事件或在多重处理器系统中另一个处理器使用 SVC 指令。若事件寄存器为 1, WFE 指令请求该寄存器后立刻执行。休眠指令 WFI 执行条件为:发生异常或PRIMASK.PM 被清零,产生的中断将会先占,或发生触发调试请求(不论调试是否被使能)。
在 Cortex-M3/M4 处理器内部,一个只有一位的寄存器会记录事件。若该寄存器置位,WFE 指令不会进入休眠而只是清除事件寄存器并继续执行下一条指令;若该寄存器清零,则处理器会进入休眠而且会被事件唤醒,事件可以是中断、调试操作、复位或外部事件输入的脉冲信号(例如,事件脉冲可由另一个处理器或外设产生)。
Cortex-M 处理器的接口信号包括一个事件输人和一个事件输出。处理器的事件输人可由多处理器系统中其他处理器的事件输出产生,因此,处于 WFE 休眠(如等待自旋锁)的处理器可由其他的处理器唤醒。有些情况下,这些信号会被连接到 Cortex-M 微控制器的 I/O 端口,而其他一些 Cortex-M 微控制器的事件输入可能会被连接到低电平,而事件输出则可能会用不上。
发送事件指令
SEV ;发送事件指令。在多处理器系统中,向所有处理器发送一个事件,也可置位本地寄存器。不影响 N、Z、C、V 标志
C 编程(符合 CMSIS 的设备驱动库)
_SEV(); //发送事件
当执行 SEV 时,事件输出接口就会出现一个单周期的脉冲,SEV 指令还会设置同一处理器的事件寄存器。
2.9 存储器屏障指令#
存储器屏障指令可用于:
- 确保存储器访问的顺序。
- 确保存储器访问和另一个处理器操作间的顺序。
- 确保系统配置发生在后序操作之前。
存储器屏障指令
指令 | 描述 |
---|---|
DMB | 数据存储器屏障。确保在执行新的存储器访问前所有的存储器访问都已经完成(与流水线、 MPU 和 cache 等有关) |
DSB | 数据同步屏障。确保在下一条指令执行前所有的存储器访问都已经完成(与流水线、 MPU 和 cache 等有关) |
ISB | 指令同步屏障。清空流水线,确保在执行新的指令前,之前所有的指令都已完成(与流水线、 MPU 等有关) |
对于 C 编程,若使用符合 CMSIS 的设备驱动,可以利用下面的函数来实现这些指令:
void_DMB(void); //数据存储器屏障
void_DSB(void); //数据同步屏障
void_ISB(void); //指令同步屏障
由于 Cortex-M 处理器具有相对简单的流水线,而且 AHBLite 总线协议不允许对存储器系统中的传输重新排序,因此,即便没有存储器屏障指令,多数应用还是可以正常工作的。
存储器屏障指令应用场景示例
应用场景(用于当前的 Cortex-M3 和 Cortex-M4 设计) | 所需的屏障指令 |
---|---|
利用 MSR 指令更新 CONTROL 寄存器后,应该使用 ISB 指令,确保接下来的操作使用更新后的配置 | ISB |
若系统控制寄存器中的 SLEEPONEXIT 位在异常处理内变化了,则应该在异常退出前使用 DSB | DSB |
若挂起的异常是使能的且要确保挂起的异常在下面的操作前执行 | DSB,后跟着 ISB |
在利用 NVIC 清除中断寄存器禁止中断时,若要确保在执行下一条指令前中断禁止立即产生效果 | DSB,后跟着 ISB |
自修复代码(下面的指令已经被取出且需要清空) | DSB,后跟着 ISB |
程序存储器被外设中的控制寄存器修改,且需要立即启用新的程序存储器映射(假定存储器映射在写完成前立即更新) | DSB,后跟着 ISB |
数据存储器被外设中的控制寄存器修改,且需要立即启用新的数据存储器映射(假定存储器映射在写完成前立即更新) | DSB |
存储器保护单元(MPU)配置更新,然后在 MPU 配置变化影响的存储器区域中取出并执行一条指令 | DSB,后跟着 ISB |
架构定义存储器屏障指令应用场景示例
应用场景(基于架构推荐) | 所需的屏障指令 |
---|---|
存储器保护单元(MPU)配置更新,然后在 MPU 配置变化影响的存储器区域中取出并执行一条指令(只允许数据访问的区域,无取指) | DSB |
进入休眠前(WFI 或 WFE) | DSB |
信号量操作 | DMB 或 DSB |
修改异常(如 SVC)的优先级后触发 | DSB |
利用向量表偏移寄存器(VTOR)将向量表重置与新的位置,然后触发新向量的异常 | DSB |
修改向量表中的向量人口(若在 SRAM 中)后立即触发同一个异常 | DSB |
恰好处于自复位前(可能会有正在进行的数据传输) | DSB |
2.10 其他指令#
类型 | 指令 | 说明 |
---|---|---|
断点指令 | BKPT #imm | 如果调试被使能,则进入调试状态(停机);如果调试监视器异常被使能,则调用一个调试异常,否则调用一个错误异常。 处理器忽视立即数 imm,立即数范围为 0~ 255,表示断点调试的信息。 不影响 N、 Z、 C、 V 状态标志 |
空操作 | NOP | 空操作,但无法保证能够延迟时间,处理器可能在执行阶段之前就将此指令从线程中移除。不影响 N、 Z、 C、 V 标志 |
断点(BKPT)指令
在软件开发/调试过程中,断点(BKPT)指令用于实现应用程序中的软件断点。若程序在 SRAM 中执行,则该指令一般由调试器插入以替换原有的指令。当到达断点时,处理器会被暂停,然后调试器就会恢复原有的指令,用户也可以通过调试器执行调试任务。BKPT 指令也可以用于产生调试监控异常,它具有一个8位立即数,调试器或调试监控异常可以将该数据提取出来,并根据该信息确定要执行的动作。例如,可用某些特殊数值表示半主机请求(同工具链相关)。
BKPT #immed ;断点
immed 是一个 16 位的立即数,表示断点的标识符。通常情况下,这个立即数是用来标识不同的断点,但在实际的使用中,可能会忽略这个值,或者使用特定的约定来进行设置。
C 编程
_BKPT(immed);
除了 BKPT,Cortex-M3 和 Cortex-M4 处理器中还存在一个断点单元,它具有最多 8 个硬件断点,而且不用覆盖原有的程序映像。
NOP 指令:产生指令对齐或延时
NOP ;什么都不做
C 编程
_NOP(); //什么都不做
有一点需要注意,NOP 指令产生的延时一般是精确的(简单地占用了一个时钟周期),不同系统间可能会存在差异(如存储器等待状态和处理器类型)。若延时需要非常精确,就应该使用硬件定时器。
四、Cortex-M4 特有的指令#
待补充
五、指令集与机器码对应表#
CM4F 处理器部分指令集与机器码的对应关系如表所示。“机器码” 列中,v 代表 immed_value,n 代表 Rn,m 代表 Rm,s 代表 Rs,r 代表 register_list,c 代表 condition,d 代表 Rd,l 代表 label。 机器码均为大端对齐方式,即高位字节在低地址中,这样便于从左到右顺序阅读。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了