STM32之ARM通用汇编指令详解:从指令格式到寻址方式

1. 引言

ARM 汇编语言是嵌入式开发中理解底层运行机制的重要工具。本文围绕 STM32 平台,系统梳理 ARM 汇编程序的基本构成、指令类型、指令格式、寻址方式以及常用指令的详细用法,帮助读者建立从指令到程序的完整认知。

2. 汇编程序构成

ARM 汇编程序由三类指令组成:汇编指令、伪指令和宏指令。三者作用不同,需要区分理解。

类别 说明 典型示例
汇编指令 由 CPU 直接执行的机器指令助记符,编译后生成对应机器码 MOV、ADD、LDR、STR
伪指令 供汇编器识别处理的指令,不生成机器码,用于定义数据、分配空间、设置符号等 EQU、AREA、DCB、DCD
宏指令 由用户定义的指令序列,汇编时展开为多条汇编指令 MACRO / MEND 定义的宏

一个典型的 ARM 汇编程序通常包含段定义、符号定义、数据定义和指令序列四部分。段通过 AREA 伪指令声明,代码段与数据段分开组织,便于链接器处理。

3. ARM指令类型

ARM 指令集按功能可划分为以下几大类:

  • 数据处理指令:完成算术、逻辑、比较等运算,如 ADD、SUB、AND、ORR、CMP。
  • 加载与存储指令:在寄存器和内存之间搬运数据,如 LDR、STR、LDM、STM。
  • 分支指令:控制程序跳转,如 B、BL、BX。
  • 程序状态寄存器指令:读写 CPSR 和 SPSR,如 MRS、MSR。
  • 协处理器指令:用于协处理器操作,如 CDP、LDC、STC。
  • 异常产生指令:主动触发异常,如 SWI(软中断)。

理解指令类型有助于在阅读反汇编代码时快速判断指令用途,也有助于编写高效的汇编程序。

4. ARM指令格式

ARM 指令采用固定 32 位长度,编码格式规整。以数据处理指令为例,其基本格式如下:

armasm 复制代码
{cond} 00 I <opcode> {S} <Rn> <Rd> {,<Operand2>}
31-28 27-26 25 24-21 20 19-16 15-12 11-0

注:<> 必须有的,{} 可选的

各字段含义如下:

  • cond(31-28 位):条件码,决定指令是否执行,如 EQ、NE、GT 等。
  • 00(27-26 位):指令类型标识,00 表示数据处理指令。
  • I(25 位):操作数类型,0 表示寄存器操作数,1 表示立即数操作数。
  • opcode(24-21 位):操作码,指明具体运算,如 ADD 为 0100。
  • S(20 位):是否影响 CPSR 中的条件标志位。
  • Rn(19-16 位):第一操作数寄存器。
  • Rd(15-12 位):目标寄存器。
  • Operand2(11-0 位):第二操作数,可为寄存器、移位寄存器或立即数。

cond 详情请看下图《指令条件码表》

加载与存储指令、分支指令的格式略有不同,但整体遵循 32 位定长编码原则。掌握指令格式有助于理解指令编码与反汇编结果。

5. ARM寻址方式

寻址方式决定指令如何获取操作数。ARM 指令的寻址方式主要包括以下几种:

5.1 立即数寻址

操作数直接写在指令中,以 # 开头。例如:

armasm 复制代码
MOV R0, #0x10

该指令将立即数 0x10 送入 R0。立即数寻址速度快,但受编码位数限制,并非所有数值都能直接表示。

立即数在指令中的存放方式与编码规则密切相关。ARM 指令采用 32 位定长编码,立即数并不直接以完整 32 位数值形式存放在指令中,而是通过 12 位 Operand2 字段表示,其中高 4 位存放循环右移的位数(rotate_imm),低 8 位存放基础常数(imm8)。CPU 在执行时,将 imm8 循环右移 rotate_imm 指定的偶数位,还原出真正的 32 位立即数。

这种存放方式带来两个特点:一是立即数在指令中占用空间小,节省编码位;二是合法立即数的范围受限,只有能由 8 位常数循环右移偶数位得到的数值才能直接编码。对于无法直接编码的数值,汇编器会提示错误,此时应改用 LDR 伪指令,由汇编器自动将数值存入常量池,再通过 PC 相对寻址加载到寄存器。

在 ARM 指令中,并非所有数值都能作为立即数直接使用,需要满足一定的编码规则。ARM 采用 8 位立即数循环右移的编码方式,即一个合法的立即数必须能够由 8 位常数通过循环右移偶数位得到。判断方法如下:

  • 8 位立即数范围:立即数必须能表示为一个 8 位二进制数(0 到 255),即 0x00 到 0xFF。
  • 循环右移规则:将 8 位常数循环右移偶数位(0、2、4、...、30),得到的 32 位结果即为合法立即数。
  • 判断步骤:先将目标数值写成二进制,若其有效位(非零位)能压缩到连续的 8 位以内,且通过循环右移偶数位可还原,则该数值为合法立即数。

例如,0x10 可以表示为 8 位常数 0x10 循环右移 0 位,因此是合法立即数;而 0x1FF 无法用 8 位常数通过循环右移偶数位得到,因此不能直接作为立即数使用,需要改用寄存器加载或通过 LDR 伪指令间接实现。

立即数的计算公式为:k = (x >> (y * 2)),其中 x 为 8 位基础常数(imm8),y 为循环右移的位数(rotate_imm),k 为还原后的 32 位立即数。该公式以 32 位为基准进行循环右移,即每次右移 2 位,共右移 y 次,最终得到完整的 32 位立即数值。

例如,若 imm8 = 0x10,rotate_imm = 0,则 k = (0x10 >> (0 * 2)) = 0x10,即立即数为 0x10;若 imm8 = 0x10,rotate_imm = 4,则 k = (0x10 >> (4 * 2)) = (0x10 >> 8) = 0x00100000,即立即数为 0x00100000。通过该公式可以快速验证一个数值是否为合法立即数。

5.2 寄存器寻址

操作数存放在寄存器中,指令直接引用寄存器。例如:

armasm 复制代码
ADD R0, R1, R2

该指令将 R1 与 R2 相加,结果存入 R0。

5.3 寄存器间接寻址

寄存器中存放的是内存地址,指令通过该地址访问内存。例如:

armasm 复制代码
LDR R0, [R1]

该指令从 R1 指向的内存地址读取数据到 R0。

寄存器间接寻址的指令格式为:LDR/STR Rd, Rn,其中 Rd 为目标寄存器,Rn 为存放内存地址的基址寄存器,方括号表示按寄存器中的地址访问内存。LDR 用于从内存读取数据到寄存器,STR 用于将寄存器数据写入内存。

armasm 复制代码
LDR R0, [R1]        ; 将 R1 指向的内存地址处的数据读取到 R0
STR R0, [R1]        ; 将 R0 中的数据写入 R1 指向的内存地址

寄存器间接寻址常用于访问数组元素、结构体成员或外设寄存器等场景。与立即数寻址不同,这里的内存地址存放在寄存器中,可在程序运行过程中动态变化,因此适合处理地址不固定的数据访问。

LDR 指令的完整格式为:LDR{cond}{S}{B/H} Rd, <地址>,其中各字段含义如下:

  • cond:条件码,可选,决定指令是否执行,如 EQ、NE 等。
  • S:可选后缀,表示是否影响 CPSR 中的条件标志位。
  • B/H:可选后缀,B 表示按字节(8 位)加载,H 表示按半字(16 位)加载;不加后缀时默认按字(32 位)加载。
  • Rd:目标寄存器,用于存放从内存读取的数据。
  • <地址>:内存地址,由寄存器间接寻址方式提供,如 Rn。

例如,以下指令分别按字、字节和半字方式从 R1 指向的地址加载数据:

armasm 复制代码
LDR R0, [R1]        ; 按字(32 位)加载
LDRB R0, [R1]       ; 按字节(8 位)加载
LDRH R0, [R1]       ; 按半字(16 位)加载

STR 指令的格式与 LDR 类似,为 STR{cond}{S}{B/H} Rd, <地址>,用于将寄存器数据写入内存,同样支持按字节、半字和字三种宽度存储。

5.4 基址变址寻址

在寄存器地址基础上加上偏移量形成有效地址。例如:

armasm 复制代码
LDR R0, [R1, #4]

该指令从 R1+4 地址处读取数据。偏移量可以是立即数或寄存器,还可配合前变址、后变址等模式。

寄存器偏移寻址是基址变址寻址的一种形式,其偏移量由寄存器提供,而非立即数。指令格式为:LDR/STR Rd, Rn, Rm,其中 Rn 为基址寄存器,Rm 为偏移寄存器,有效地址为 Rn + Rm。例如:

armasm 复制代码
LDR R0, [R1, R2]        ; 从 R1 + R2 地址处读取数据到 R0
STR R0, [R1, R2]        ; 将 R0 中的数据写入 R1 + R2 地址处

寄存器偏移寻址的偏移量还可以配合移位操作,格式为 LDR/STR Rd, Rn, Rm, LSL #n,即先将 Rm 左移 n 位后再与 Rn 相加形成有效地址。例如:

armasm 复制代码
LDR R0, [R1, R2, LSL #2]        ; 有效地址 = R1 + (R2 << 2)

寄存器偏移寻址常用于数组元素访问,尤其是元素大小为 2 的幂次时,可通过移位快速计算元素地址,避免在循环中反复执行乘法运算,提高代码执行效率。

5.5 多寄存器寻址

多寄存器寻址是一条指令同时访问多个寄存器的寻址方式,常用于批量数据搬运,如函数入口保存现场、函数返回恢复现场、批量复制数据等场景。ARM 中实现多寄存器寻址的指令为 LDM(Load Multiple)和 STM(Store Multiple)。

LDM 指令的完整格式为:LDM{cond}{mode} Rn{!}, {寄存器列表},其中各字段含义如下:

  • cond:条件码,可选,决定指令是否执行,如 EQ、NE 等。
  • mode:地址变化模式,决定基址寄存器 Rn 的地址在访问前或访问后是否变化以及如何变化,常见模式有 IA、IB、DA、DB。
  • Rn:基址寄存器,存放连续内存区域的起始地址。
  • !:可选后缀,加上后表示指令执行完毕后将更新后的地址写回基址寄存器 Rn。
  • 寄存器列表:用花括号括起来的寄存器集合,如 {R0-R3} 或 {R0, R2, R4},表示本次访问涉及的多个寄存器。

LDM 与 STM 的地址变化模式共有四种,分别对应不同的访问顺序和地址增减方式:

模式 含义 说明
IA Increment After 先访问 Rn 指向的地址,访问后地址加 4
IB Increment Before 先地址加 4,再访问新地址
DA Decrement After 先访问 Rn 指向的地址,访问后地址减 4
DB Decrement Before 先地址减 4,再访问新地址

例如,以下指令从 R0 指向的连续内存地址依次读取数据到 R1 至 R4,并自动更新 R0:

armasm 复制代码
LDMIA R0!, {R1-R4}      ; 从 R0 地址开始依次读取 4 个字到 R1-R4,R0 自动加 16

该指令执行后,R1 到 R4 分别获得 R0、R0+4、R0+8、R0+12 地址处的数据,同时由于带 ! 后缀,R0 更新为 R0+16,指向下一批数据的起始位置。

STM 指令的格式与 LDM 类似,为 STM{cond}{mode} Rn{!}, {寄存器列表},用于将多个寄存器的数据批量写入连续内存。例如:

armasm 复制代码
STMIA R0!, {R1-R4}      ; 将 R1-R4 的数据依次写入 R0 起始的连续地址,R0 自动加 16

多寄存器寻址在函数调用中非常实用。函数入口处常用 STMDB 将需要保存的寄存器压入栈中,函数返回前用 LDMIA 恢复现场。例如:

armasm 复制代码
STMDB SP!, {R0-R3, LR}  ; 函数入口,将 R0-R3 和 LR 压栈,SP 自动递减
LDMIA SP!, {R0-R3, PC}  ; 函数返回,恢复 R0-R3 并从栈中弹出返回地址到 PC

需要注意的是,寄存器列表中的寄存器编号按升序排列,且不能重复。多寄存器寻址一次最多可访问 16 个寄存器,相比单寄存器 LDR/STR 指令,能显著减少指令条数,提高批量数据搬运效率。

5.6 堆栈寻址

堆栈寻址是 ARM 中一种特殊的寻址方式,它借助堆栈指针寄存器 SP(R13)管理内存中的栈区域,通过压栈(Push)和出栈(Pop)操作实现数据的临时保存与恢复。堆栈寻址在函数调用、中断处理和现场保护等场景中应用极为广泛。

ARM 中的堆栈按增长方向可分为两种类型:

  • 向上增长(Ascending):堆栈从低地址向高地址方向增长,压栈时 SP 递增。
  • 向下增长(Descending):堆栈从高地址向低地址方向增长,压栈时 SP 递减。ARM 处理器默认采用向下增长方式,这也是大多数嵌入式系统使用的堆栈模型。

堆栈寻址通常与多寄存器指令 LDM/STM 配合使用,通过选择不同的地址变化模式实现压栈与出栈。ARM 将堆栈操作与 LDM/STM 的四种地址模式对应起来,形成四种堆栈类型:

堆栈类型 对应指令 说明
满递减(FD) STMDB / LDMIA SP 指向栈顶已存数据,压栈先减后存,出栈先取后加
空递减(ED) STMDA / LDMIB SP 指向栈顶空位置,压栈先存后减,出栈先加后取
满递增(FA) STMIB / LDMDA SP 指向栈顶已存数据,压栈先加后存,出栈先取后减
空递增(EA) STMIA / LDMDB SP 指向栈顶空位置,压栈先存后加,出栈先减后取

在 ARM 汇编中,最常用的是满递减(FD)堆栈,即压栈使用 STMDB,出栈使用 LDMIA。例如:

armasm 复制代码
STMDB SP!, {R0-R3, LR}      ; 压栈:先将 SP 减 4,再依次存入 R0-R3 和 LR
LDMIA SP!, {R0-R3, PC}      ; 出栈:依次恢复 R0-R3,并将栈顶值弹出到 PC

上述代码是函数调用中典型的现场保护与恢复写法。函数入口处用 STMDB 将需要保存的寄存器压入栈中,SP 自动向低地址方向递减;函数返回前用 LDMIA 恢复寄存器,并将返回地址弹出到 PC,实现子程序返回。

堆栈寻址需要注意以下几点:

  • SP 的维护:压栈和出栈必须成对出现,保证 SP 在函数返回时恢复到进入前的值,否则会导致栈指针错乱。
  • 栈方向一致性:整个程序应统一使用同一种堆栈模型(通常为满递减),避免混用导致数据覆盖或访问越界。
  • 寄存器列表顺序:寄存器编号按升序排列,压栈时编号小的寄存器先入栈,出栈时后出栈,保证数据对应关系正确。

堆栈寻址是理解函数调用、中断嵌套和任务切换的基础。在 STM32 启动文件和 RTOS 内核代码中,堆栈操作随处可见,掌握其原理对阅读底层代码至关重要。

5.7 相对寻址

以程序计数器 PC 为基准计算目标地址,主要用于分支指令。例如:

armasm 复制代码
B label

汇编器计算 label 与当前 PC 的偏移,生成跳转指令。

6. ARM 指令

本节选取 STM32 开发中最常用的 ARM 指令逐一说明。

6.1 数据处理指令

数据处理指令只能对寄存器 R0-R15 操作,不能对内存进行操作。

6.1.1 数据传送指令

数据传送指令用于在寄存器之间或向寄存器传送数据,是 ARM 汇编中最基础、最常用的指令之一。其核心指令为 MOV(Move),格式为:MOV{cond}{S} Rd, Operand2,其中各字段含义如下:

  • cond:条件码,可选,决定指令是否执行,如 EQ、NE 等。
  • S:可选后缀,加上后表示指令执行结果会影响 CPSR 中的条件标志位。
  • Rd:目标寄存器,用于存放传送后的数据。
  • Operand2:源操作数,可为寄存器或立即数。

MOV 指令的典型用法如下:

armasm 复制代码
MOV R0, R1      ; R0 = R1,将 R1 的值传送到 R0
MOV R0, #0xFF   ; R0 = 0xFF,将立即数 0xFF 传送到 R0
MOV R0, R1, LSL #2  ; R0 = R1 << 2,将 R1 左移 2 位后传送到 R0

MOV 指令支持将寄存器、立即数以及移位后的寄存器作为源操作数。当源操作数为寄存器时,实现寄存器之间的数据复制;当源操作数为立即数时,将立即数直接装入目标寄存器。此外,MOV 还可以配合移位操作,将移位后的结果传送到目标寄存器,这在需要快速实现乘除 2 的幂次运算时非常实用。

MOV 指令默认不影响条件标志位,除非加上 S 后缀。例如:

armasm 复制代码
MOVS R0, R1     ; R0 = R1,并根据结果更新 CPSR 中的 N、Z 标志位

加上 S 后缀后,指令执行结果会同步更新 CPSR 中的条件标志位,便于后续条件分支指令根据结果进行判断。

与 MOV 相关的还有 MVN(Move Negative)指令,格式为 MVN{cond}{S} Rd, Operand2,用于将源操作数按位取反后传送到目标寄存器。例如:

armasm 复制代码
MVN R0, R1      ; R0 = ~R1,将 R1 按位取反后传送到 R0
MVN R0, #0      ; R0 = 0xFFFFFFFF,将立即数 0 取反得到全 1

MVN 常用于生成全 1 掩码或对数据进行取反操作。需要注意的是,MOV 和 MVN 都属于数据处理指令,只能对寄存器 R0-R15 进行操作,不能直接访问内存。

6.1.2 算术运算指令

算术运算指令用于完成加、减、乘、除等数值运算,是数据处理指令中应用最广泛的一类。ARM 中常用的算术运算指令包括 ADD(加法)、ADC(带进位加法)、SUB(减法)、SBC(带借位减法)、RSB(反向减法)、RSC(反向带借位减法)以及 MUL(乘法)等。这些指令既可以操作寄存器,也可以与立即数组合运算,但都只能对寄存器 R0-R15 进行操作,不能直接访问内存。

算术运算指令的基本格式为:{cond}{S} Rd, Rn, Operand2,其中各字段含义如下:

  • cond:条件码,可选,决定指令是否执行,如 EQ、NE 等。
  • S:可选后缀,加上后表示指令执行结果会影响 CPSR 中的条件标志位(N、Z、C、V)。
  • Rd:目标寄存器,用于存放运算结果。
  • Rn:第一操作数寄存器。
  • Operand2:第二操作数,可为寄存器、移位寄存器或立即数。
6.1.2.1 加法指令

ADD(Add)指令用于将两个操作数相加,结果存入目标寄存器。其格式为:ADD{cond}{S} Rd, Rn, Operand2。典型用法如下:

armasm 复制代码
ADD R0, R1, R2      ; R0 = R1 + R2
ADD R0, R1, #1      ; R0 = R1 + 1
ADD R0, R1, R2, LSL #2  ; R0 = R1 + (R2 << 2)

ADD 指令支持寄存器与寄存器、寄存器与立即数、寄存器与移位寄存器三种组合方式。当需要实现 2 的幂次乘法时,可配合移位操作,例如 ADD R0, R1, R2, LSL #3 表示 R0 = R1 + R2 * 8。

ADC(Add with Carry)指令用于带进位加法,格式为:ADC{cond}{S} Rd, Rn, Operand2,其运算结果为 Rd = Rn + Operand2 + C(C 为 CPSR 中的进位标志位)。ADC 常用于多字节数相加,处理低位相加产生的进位。例如:

armasm 复制代码
ADDS R0, R2, R4     ; 低位相加,R0 = R2 + R4,并更新进位标志 C
ADC  R1, R3, R5     ; 高位相加,R1 = R3 + R5 + C

上述代码实现了两个 64 位数的加法,其中 R2:R3 和 R4:R5 分别存放两个 64 位数的低 32 位和高 32 位。先通过 ADDS 完成低位相加并更新进位标志,再用 ADC 将进位加入高位相加结果中。

6.1.2.2 减法指令

SUB(Subtract)指令用于将第一操作数减去第二操作数,结果存入目标寄存器。其格式为:SUB{cond}{S} Rd, Rn, Operand2。典型用法如下:

armasm 复制代码
SUB R0, R1, R2      ; R0 = R1 - R2
SUB R0, R1, #1      ; R0 = R1 - 1
SUB R0, R1, R2, LSL #2  ; R0 = R1 - (R2 << 2)

SUB 指令同样支持寄存器、立即数和移位寄存器三种操作数组合。在循环计数、地址计算和数值递减等场景中应用广泛。

SBC(Subtract with Carry)指令用于带借位减法,格式为:SBC{cond}{S} Rd, Rn, Operand2,其运算结果为 Rd = Rn - Operand2 - (1 - C),其中 C 为 CPSR 中的进位标志位。SBC 常用于多字节数相减,处理低位相减产生的借位。例如:

armasm 复制代码
SUBS R0, R2, R4     ; 低位相减,R0 = R2 - R4,并更新进位标志 C
SBC  R1, R3, R5     ; 高位相减,R1 = R3 - R5 - (1 - C)

上述代码实现了两个 64 位数的减法。先通过 SUBS 完成低位相减并更新借位标志,再用 SBC 将借位从高位相减结果中扣除。

6.1.2.3 反向减法指令

RSB(Reverse Subtract)指令用于反向减法,格式为:RSB{cond}{S} Rd, Rn, Operand2,其运算结果为 Rd = Operand2 - Rn,即操作数顺序与 SUB 相反。RSB 常用于计算某个值与寄存器值的差,例如:

armasm 复制代码
RSB R0, R1, #0      ; R0 = 0 - R1 = -R1,实现取负操作
RSB R0, R1, R2      ; R0 = R2 - R1

RSB 配合立即数 0 可以快速实现寄存器取负运算,这在符号转换和数值取反场景中非常实用。

RSC(Reverse Subtract with Carry)指令用于反向带借位减法,格式为:RSC{cond}{S} Rd, Rn, Operand2,其运算结果为 Rd = Operand2 - Rn - (1 - C)。RSC 常用于多字节反向减法运算。

6.1.2.4 乘法指令

MUL(Multiply)指令用于两个 32 位寄存器相乘,结果存入目标寄存器。其格式为:MUL{cond}{S} Rd, Rm, Rs,运算结果为 Rd = Rm * Rs。典型用法如下:

armasm 复制代码
MUL R0, R1, R2      ; R0 = R1 * R2
MULS R0, R1, R2     ; R0 = R1 * R2,并更新 CPSR 中的 N、Z 标志位

MUL 指令的注意事项:

  • MUL 指令的两个操作数都必须是寄存器,不能使用立即数。
  • 目标寄存器 Rd 不能与第一操作数寄存器 Rm 相同,即 Rd 不能等于 Rm。
  • MUL 指令只更新 N 和 Z 标志位,不影响 C 和 V 标志位(除非使用 S 后缀)。
  • 乘法结果只保留低 32 位,高 32 位被丢弃。若需要 64 位乘法结果,可使用 UMULL 或 SMULL 指令。

例如,以下代码演示了 MUL 指令的典型应用:

armasm 复制代码
MOV R1, #5          ; R1 = 5
MOV R2, #6          ; R2 = 6
MUL R0, R1, R2      ; R0 = 5 * 6 = 30
6.1.3 比较指令

比较指令用于比较两个操作数的大小关系,并根据比较结果更新 CPSR 中的条件标志位,为后续的条件分支指令提供判断依据。ARM 中常用的比较指令包括 CMP(Compare)和 CMN(Compare Negative),此外还有用于测试位状态的 TST(Test)和 TEQ(Test Equivalence)。比较指令不保存运算结果,只影响条件标志位。

CMP(Compare)指令用于比较第一操作数与第二操作数的大小,其格式为:CMP{cond} Rn, Operand2,运算过程相当于执行 Rn - Operand2,但结果不写入寄存器,只更新 CPSR 中的 N、Z、C、V 标志位。典型用法如下:

armasm 复制代码
CMP R0, R1          ; 比较 R0 与 R1,更新标志位
CMP R0, #10         ; 比较 R0 与立即数 10
CMP R0, R1, LSL #2  ; 比较 R0 与 (R1 << 2)

CMP 指令执行后,各标志位的含义如下:

  • Z(零标志):若 Rn = Operand2,则 Z = 1,表示两操作数相等。
  • N(负标志):若 Rn < Operand2,则 N = 1,表示第一操作数小于第二操作数。
  • C(进位标志):若 Rn >= Operand2(无符号比较),则 C = 1,表示无借位。
  • V(溢出标志):若发生有符号溢出,则 V = 1。

CMP 指令常与条件分支指令配合使用,实现条件判断和流程控制。例如:

armasm 复制代码
CMP R0, #10         ; 比较 R0 与 10
BEQ equal           ; 若 R0 = 10(Z = 1),跳转到 equal
BGT greater         ; 若 R0 > 10(有符号),跳转到 greater
BLT less            ; 若 R0 < 10(有符号),跳转到 less

上述代码中,CMP 指令先比较 R0 与 10,随后根据不同的条件码执行不同的跳转。BEQ 在相等时跳转,BGT 在大于时跳转,BLT 在小于时跳转,实现了典型的多分支判断结构。

CMN(Compare Negative)指令用于比较第一操作数与第二操作数的相反数,其格式为:CMN{cond} Rn, Operand2,运算过程相当于执行 Rn + Operand2,结果同样不写入寄存器,只更新条件标志位。CMN 常用于判断两个数相加是否为零或是否发生溢出。例如:

armasm 复制代码
CMN R0, R1          ; 比较 R0 与 -R1,即判断 R0 + R1 是否为 0
BEQ zero            ; 若 R0 + R1 = 0(Z = 1),跳转到 zero

TST(Test)指令用于测试第一操作数的某些位是否为 0,其格式为:TST{cond} Rn, Operand2,运算过程相当于执行 Rn & Operand2,结果不写入寄存器,只更新 N、Z 标志位。TST 常用于检查寄存器中某个位或某组位的状态。例如:

armasm 复制代码
TST R0, #0x01       ; 测试 R0 的第 0 位是否为 1
BEQ bit_clear       ; 若第 0 位为 0(Z = 1),跳转到 bit_clear
TST R0, #0x80       ; 测试 R0 的第 7 位是否为 1
BNE bit_set         ; 若第 7 位为 1(Z = 0),跳转到 bit_set

TEQ(Test Equivalence)指令用于测试两个操作数是否相等,其格式为:TEQ{cond} Rn, Operand2,运算过程相当于执行 Rn ^ Operand2(按位异或),结果不写入寄存器,只更新 N、Z 标志位。TEQ 常用于判断两个操作数是否相等,且不关心进位标志。例如:

armasm 复制代码
TEQ R0, R1          ; 测试 R0 与 R1 是否相等
BEQ same            ; 若 R0 = R1(Z = 1),跳转到 same

比较指令在 ARM 汇编中应用极为广泛,是条件分支、循环控制和状态判断的基础。在 STM32 开发中,比较指令常用于判断外设状态、比较传感器数值以及实现各种控制逻辑。需要注意的是,比较指令默认不影响条件标志位以外的任何寄存器,且不能直接对内存操作,只能对寄存器 R0-R15 进行比较。

6.1.4 逻辑运算指令

逻辑运算指令用于对寄存器中的数据进行按位逻辑操作,是位操作和寄存器配置中最常用的指令。ARM 中常用的逻辑运算指令包括 AND(按位与)、ORR(按位或)、EOR(按位异或)和 BIC(位清除)。这些指令只能对寄存器 R0-R15 进行操作,不能直接访问内存。

逻辑运算指令的基本格式为:{cond}{S} Rd, Rn, Operand2,其中各字段含义如下:

  • cond:条件码,可选,决定指令是否执行,如 EQ、NE 等。
  • S:可选后缀,加上后表示指令执行结果会影响 CPSR 中的条件标志位(N、Z、C、V)。
  • Rd:目标寄存器,用于存放运算结果。
  • Rn:第一操作数寄存器。
  • Operand2:第二操作数,可为寄存器、移位寄存器或立即数。
6.1.4.1 按位与指令

AND(And)指令用于将两个操作数按位进行与运算,结果存入目标寄存器。其格式为:AND{cond}{S} Rd, Rn, Operand2,运算结果为 Rd = Rn & Operand2。典型用法如下:

armasm 复制代码
AND R0, R0, #0x0F   ; R0 = R0 & 0x0F,保留低 4 位,清除高 28 位
AND R0, R1, R2      ; R0 = R1 & R2
AND R0, R1, R2, LSL #2  ; R0 = R1 & (R2 << 2)

AND 指令常用于掩码操作,即保留寄存器中某些指定位,清除其余位。例如,AND R0, R0, #0x0F 将 R0 的高 28 位清零,只保留低 4 位,这在读取外设寄存器中某个字段时非常实用。

6.1.4.2 按位或指令

ORR(Or)指令用于将两个操作数按位进行或运算,结果存入目标寄存器。其格式为:ORR{cond}{S} Rd, Rn, Operand2,运算结果为 Rd = Rn | Operand2。典型用法如下:

armasm 复制代码
ORR R0, R0, #0x10   ; R0 = R0 | 0x10,将第 4 位置 1
ORR R0, R1, R2      ; R0 = R1 | R2
ORR R0, R1, R2, LSL #2  ; R0 = R1 | (R2 << 2)

ORR 指令常用于将寄存器中某些指定位置 1,而不影响其他位。例如,ORR R0, R0, #0x10 将 R0 的第 4 位置 1,这在配置外设寄存器时用于使能某个功能位。

6.1.4.3 按位异或指令

EOR(Exclusive Or)指令用于将两个操作数按位进行异或运算,结果存入目标寄存器。其格式为:EOR{cond}{S} Rd, Rn, Operand2,运算结果为 Rd = Rn ^ Operand2。典型用法如下:

armasm 复制代码
EOR R0, R0, R1      ; R0 = R0 ^ R1
EOR R0, R0, #0xFF   ; R0 = R0 ^ 0xFF,将低 8 位取反
EOR R0, R1, R2, LSL #2  ; R0 = R1 ^ (R2 << 2)

EOR 指令常用于位翻转操作。例如,EOR R0, R0, #0xFF 将 R0 的低 8 位全部取反,高 24 位保持不变。异或运算还可用于寄存器清零,即 EOR R0, R0, R0 使 R0 = 0。

6.1.4.4 位清除指令

BIC(Bit Clear)指令用于将第一操作数中与第二操作数对应位置 1 的位清零,结果存入目标寄存器。其格式为:BIC{cond}{S} Rd, Rn, Operand2,运算结果为 Rd = Rn & ~Operand2。典型用法如下:

armasm 复制代码
BIC R0, R0, #0x0F   ; R0 = R0 & ~0x0F,将低 4 位清零
BIC R0, R1, R2      ; R0 = R1 & ~R2
BIC R0, R1, R2, LSL #2  ; R0 = R1 & ~(R2 << 2)

BIC 指令常用于将寄存器中某些指定位置 0,而不影响其他位。例如,BIC R0, R0, #0x0F 将 R0 的低 4 位清零,这在配置外设寄存器时用于关闭某个功能位。

6.1.4.5 条件标志位

逻辑运算指令默认不影响 CPSR 中的条件标志位,除非加上 S 后缀。加上 S 后缀后,指令执行结果会同步更新 N、Z、C、V 标志位,便于后续条件分支指令根据结果进行判断。例如:

armasm 复制代码
ANDS R0, R0, #0x0F  ; R0 = R0 & 0x0F,并更新 N、Z 标志位
BEQ  zero           ; 若结果为 0(Z = 1),则跳转到 zero

逻辑运算指令在寄存器配置和状态判断中应用极为广泛。例如,在 STM32 外设寄存器配置中,常先用 AND 清除某些位,再用 ORR 设置新的值,最后用 BIC 关闭不需要的功能位。掌握这些指令有助于高效地进行位操作。

6.2 存储访问指令

存储访问指令用于在寄存器和内存之间搬运数据,是嵌入式开发中使用频率最高的一类指令。ARM 中存储访问指令主要分为单寄存器操作指令和多寄存器操作指令两大类,分别适用于单次数据访问和批量数据搬运场景。

6.2.1 单寄存器指令

单寄存器操作指令一次只访问一个内存单元,在寄存器和内存之间搬运一个字、半字或字节数据。ARM 中最常用的单寄存器操作指令为 LDR(Load Register)和 STR(Store Register),此外还有 LDRB、LDRH、STRB、STRH 等按不同数据宽度操作的变体。

LDR 指令用于从内存读取数据到寄存器,其完整格式为:LDR{cond}{S}{B/H} Rd, <地址>,其中各字段含义如下:

  • cond:条件码,可选,决定指令是否执行,如 EQ、NE 等。
  • S:可选后缀,表示是否影响 CPSR 中的条件标志位。
  • B/H:可选后缀,B 表示按字节(8 位)加载,H 表示按半字(16 位)加载;不加后缀时默认按字(32 位)加载。
  • Rd:目标寄存器,用于存放从内存读取的数据。
  • <地址>:内存地址,可由寄存器间接寻址、基址变址寻址等多种方式提供。

STR 指令的格式与 LDR 类似,为 STR{cond}{S}{B/H} Rd, <地址>,用于将寄存器数据写入内存,同样支持按字节、半字和字三种宽度存储。

例如,以下指令分别按字、字节和半字方式从 R1 指向的地址加载数据:

armasm 复制代码
LDR R0, [R1]        ; 按字(32 位)加载
LDRB R0, [R1]       ; 按字节(8 位)加载
LDRH R0, [R1]       ; 按半字(16 位)加载

对应的存储指令示例如下:

armasm 复制代码
STR R0, [R1]        ; 按字(32 位)存储
STRB R0, [R1]       ; 按字节(8 位)存储
STRH R0, [R1]       ; 按半字(16 位)存储

单寄存器操作指令还支持多种寻址模式,包括寄存器间接寻址、基址变址寻址、前变址、后变址等,可灵活访问数组元素、结构体成员和外设寄存器。例如:

armasm 复制代码
LDR R0, [R1, #4]        ; 从 R1 + 4 地址处读取数据到 R0
LDR R0, [R1, R2]        ; 从 R1 + R2 地址处读取数据到 R0
LDR R0, [R1, R2, LSL #2]    ; 从 R1 + (R2 << 2) 地址处读取数据到 R0

在 STM32 开发中,访问外设寄存器通常使用 LDR/STR 指令。例如,读取 GPIO 端口输入数据寄存器:

armasm 复制代码
LDR R0, =0x40010810    ; 将 GPIOA->IDR 地址加载到 R0
LDR R1, [R0]           ; 读取 GPIOA 端口输入数据到 R1
6.2.2 多寄存器指令

多寄存器操作指令一条指令即可同时访问多个寄存器,常用于批量数据搬运,如函数入口保存现场、函数返回恢复现场、批量复制数据等场景。ARM 中实现多寄存器操作的指令为 LDM(Load Multiple)和 STM(Store Multiple)。

LDM 指令的完整格式为:LDM{cond}{mode} Rn{!}, {寄存器列表},其中各字段含义如下:

  • cond:条件码,可选,决定指令是否执行,如 EQ、NE 等。
  • mode:地址变化模式,决定基址寄存器 Rn 的地址在访问前或访问后是否变化以及如何变化,常见模式有 IA、IB、DA、DB。
  • Rn:基址寄存器,存放连续内存区域的起始地址。
  • !:可选后缀,加上后表示指令执行完毕后将更新后的地址写回基址寄存器 Rn。
  • 寄存器列表:用花括号括起来的寄存器集合,如 {R0-R3} 或 {R0, R2, R4},表示本次访问涉及的多个寄存器。

STM 指令的格式与 LDM 类似,为 STM{cond}{mode} Rn{!}, {寄存器列表},用于将多个寄存器的数据批量写入连续内存。

LDM 与 STM 的地址变化模式共有四种,分别对应不同的访问顺序和地址增减方式:

模式 含义 说明
IA Increment After 先访问 Rn 指向的地址,访问后地址加 4
IB Increment Before 先地址加 4,再访问新地址
DA Decrement After 先访问 Rn 指向的地址,访问后地址减 4
DB Decrement Before 先地址减 4,再访问新地址

例如,以下指令从 R0 指向的连续内存地址依次读取数据到 R1 至 R4,并自动更新 R0:

armasm 复制代码
LDMIA R0!, {R1-R4}      ; 从 R0 地址开始依次读取 4 个字到 R1-R4,R0 自动加 16

该指令执行后,R1 到 R4 分别获得 R0、R0+4、R0+8、R0+12 地址处的数据,同时由于带 ! 后缀,R0 更新为 R0+16,指向下一批数据的起始位置。

对应的批量存储指令示例如下:

armasm 复制代码
STMIA R0!, {R1-R4}      ; 将 R1-R4 的数据依次写入 R0 起始的连续地址,R0 自动加 16

多寄存器操作指令在函数调用中非常实用。函数入口处常用 STMDB 将需要保存的寄存器压入栈中,函数返回前用 LDMIA 恢复现场。例如:

armasm 复制代码
STMDB SP!, {R0-R3, LR}  ; 函数入口,将 R0-R3 和 LR 压栈,SP 自动递减
LDMIA SP!, {R0-R3, PC}  ; 函数返回,恢复 R0-R3 并从栈中弹出返回地址到 PC

需要注意的是,寄存器列表中的寄存器编号按升序排列,且不能重复。多寄存器操作指令一次最多可访问 16 个寄存器,相比单寄存器 LDR/STR 指令,能显著减少指令条数,提高批量数据搬运效率。

6.3 跳转指令

跳转指令用于改变程序的执行流程,是控制程序走向的核心指令。ARM 中常用的跳转指令包括 B(Branch)和 BL(Branch with Link),此外还有 BX(Branch and Exchange)等。跳转指令通过修改程序计数器 PC 的值,使 CPU 从目标地址继续执行。

跳转指令的格式为:B{cond} label 或 BL{cond} label,其中 cond 为可选条件码,label 为目标地址标号。汇编器在编译时计算目标地址与当前 PC 之间的偏移量,并将其编码到指令中。

根据目标地址与当前指令地址的相对位置,跳转可分为向前跳转和向后跳转两种:

  • 向前跳转(Forward Branch):目标地址大于当前 PC 值,即跳转到程序后面的指令处。向前跳转常用于跳过一段代码,例如条件判断后跳过不执行的分支,或跳转到程序后部的子程序。
  • 向后跳转(Backward Branch):目标地址小于当前 PC 值,即跳转到程序前面的指令处。向后跳转常用于循环结构,例如循环体执行完毕后跳回循环起始位置,实现重复执行。

下面通过示例说明向前跳转和向后跳转的用法。向前跳转示例:

armasm 复制代码
        CMP R0, #0          ; 比较 R0 与 0
        BEQ skip            ; 若相等则向前跳转到 skip 处
        ADD R1, R1, #1      ; R0 不为 0 时执行
skip:
        MOV R2, R1          ; 跳转后执行此处

上述代码中,当 R0 等于 0 时,BEQ 指令向前跳转到 skip 标号处,跳过 ADD 指令;当 R0 不为 0 时,顺序执行 ADD 指令后再到达 skip 处。向前跳转常用于条件分支,跳过不需要执行的代码段。

向后跳转示例:

armasm 复制代码
loop:
        ADD R0, R0, #1      ; R0 加 1
        CMP R0, #10         ; 比较 R0 与 10
        BNE loop            ; 若不相等则向后跳转到 loop 处

上述代码中,loop 标号位于循环体起始处,BNE 指令在 R0 不等于 10 时向后跳转到 loop 处,重复执行 ADD 和 CMP,直到 R0 等于 10 时顺序退出循环。向后跳转是循环结构的基础。

BL 指令与 B 指令的区别在于,BL 在跳转前会将当前指令的下一条地址保存到链接寄存器 LR(R14)中,用于子程序返回。例如:

armasm 复制代码
        BL func             ; 跳转到 func,并将返回地址保存到 LR
        MOV R0, #1          ; 子程序返回后执行此处
func:
        ADD R1, R1, #1      ; 子程序体
        MOV PC, LR          ; 通过 MOV PC, LR 返回调用处

BL 常用于函数调用,子程序结束时通过 MOV PC, LR 将 LR 中的返回地址写回 PC,实现返回。BX 指令则用于在 ARM 状态和 Thumb 状态之间切换跳转,格式为 BX Rm,其中 Rm 存放目标地址,最低位为 1 时切换到 Thumb 状态。

跳转指令的偏移量范围有限,ARM 中 B/BL 指令的偏移量为 24 位有符号数,可表示约 ±32MB 的跳转范围。对于超出范围的跳转,需要借助 LDR 伪指令从常量池加载目标地址,再通过 MOV PC, Rm 或 BX Rm 实现长距离跳转。

6.4 伪指令

伪指令是供汇编器识别处理的指令,不生成机器码,用于定义数据、分配空间、设置符号等。在存储访问场景中,伪指令常用于加载常量地址、定义数据缓冲区以及声明外部符号,是编写 ARM 汇编程序时不可或缺的辅助工具。

存储访问中常用的伪指令主要包括以下几类:

  • LDR 伪指令 :用于将一个 32 位常量或地址加载到寄存器,格式为 LDR Rd, =常量/地址。当立即数无法直接编码时,汇编器会自动将该数值存入常量池,再通过 PC 相对寻址加载到寄存器,常用于加载外设寄存器地址。
  • EQU 伪指令 :用于定义符号常量,格式为 名称 EQU 表达式。汇编时用表达式替换符号,常用于定义外设寄存器地址、掩码等常量,便于程序阅读和维护。
  • DCB / DCD 伪指令:用于在内存中定义数据。DCB 定义字节数据,DCD 定义字(32 位)数据,常用于定义数据缓冲区、查找表或初始化数据段。
  • SPACE 伪指令 :用于分配指定字节数的内存空间,格式为 SPACE 字节数,常用于预留数据缓冲区。
  • AREA 伪指令 :用于声明代码段或数据段,格式为 AREA 段名, CODE/DATA, READONLY/READWRITE,是组织汇编程序段结构的基础。

LDR 伪指令与 LDR 汇编指令在形式上相似,但作用完全不同。LDR 汇编指令用于从内存读取数据到寄存器,而 LDR 伪指令用于将常量或地址加载到寄存器。例如:

armasm 复制代码
LDR R0, =0x40010810    ; 伪指令:将 GPIOA->IDR 地址加载到 R0
LDR R1, [R0]           ; 汇编指令:从 R0 指向的地址读取数据到 R1

上述代码中,第一行是 LDR 伪指令,汇编器会将 0x40010810 存入常量池,再通过 PC 相对寻址加载到 R0;第二行是 LDR 汇编指令,从 R0 指向的内存地址读取数据到 R1。两者配合使用,是 STM32 外设寄存器访问的典型写法。

EQU 伪指令常用于定义外设寄存器地址常量,例如:

armasm 复制代码
GPIOA_IDR  EQU  0x40010810    ; 定义 GPIOA 输入数据寄存器地址
LDR R0, =GPIOA_IDR            ; 将 GPIOA_IDR 地址加载到 R0
LDR R1, [R0]                  ; 读取 GPIOA 端口输入数据到 R1

使用 EQU 定义常量后,程序中可直接引用符号名称,便于阅读和维护。当需要修改寄存器地址时,只需修改 EQU 定义处,无需逐一修改所有引用位置。

DCB 和 DCD 伪指令常用于定义数据缓冲区或初始化数据段。例如:

armasm 复制代码
        AREA  Data, DATA, READWRITE
Buffer  SPACE 64            ; 分配 64 字节缓冲区
Table   DCD   0x12345678, 0x9ABCDEF0   ; 定义两个字数据
Msg     DCB   "Hello", 0    ; 定义字符串并以 0 结尾

上述代码中,SPACE 伪指令分配了 64 字节的缓冲区,DCD 伪指令定义了两个 32 位数据,DCB 伪指令定义了字符串数据。这些数据段在程序运行时可通过 LDR/STR 指令访问。

需要注意的是,伪指令不生成机器码,仅由汇编器在汇编阶段处理。合理使用伪指令可以显著提高汇编程序的可读性和可维护性,尤其在 STM32 外设寄存器地址定义、数据缓冲区分配和常量定义等场景中应用广泛。

6.5 SWP 指令

SWP(Swap)指令用于在寄存器和内存之间进行数据交换,是 ARM 中实现原子操作的重要指令。其格式为:SWP{cond}{B} Rd, Rm, Rn,其中各字段含义如下:

  • cond:条件码,可选,决定指令是否执行,如 EQ、NE 等。
  • B:可选后缀,加上后表示按字节(8 位)交换,不加时默认按字(32 位)交换。
  • Rd:目标寄存器,用于存放从内存读出的原值。
  • Rm:源寄存器,其值将被写入内存。
  • Rn:内存地址,由基址寄存器 Rn 指定。

SWP 指令的执行过程分为两步:先将 Rn 指向的内存数据读取到 Rd,再将 Rm 的值写入 Rn 指向的内存单元。整个过程在一条指令内完成,期间不会被中断打断,因此可用于实现信号量、互斥锁等同步机制。典型用法如下:

armasm 复制代码
SWP R0, R1, [R2]        ; R0 = [R2],然后将 R1 写入 [R2]
SWPB R0, R1, [R2]       ; R0 = [R2](字节),然后将 R1 的低 8 位写入 [R2]

例如,以下代码演示了利用 SWP 实现一个简单的自旋锁:

armasm 复制代码
lock:
        MOV R1, #1              ; R1 = 1,表示占用锁
        SWP R0, R1, [R2]        ; 交换:R0 = 锁状态,锁被置为 1
        CMP R0, #0              ; 判断锁之前是否空闲
        BNE lock                ; 若锁已被占用,则继续等待

上述代码中,SWP 指令将锁变量 R2 的原值读取到 R0,同时将 1 写入锁变量。若 R0 为 0,说明锁之前空闲,当前任务成功获得锁;若 R0 非 0,说明锁已被其他任务占用,则跳回 lock 处继续循环等待。释放锁时只需将 0 写入锁变量即可。

需要注意的是,SWP 指令在 ARMv6 及以后的架构中已不推荐使用,取而代之的是 LDREX/STREX 指令对,后者支持更灵活的独占访问机制。但在 ARM7、ARM9 等经典内核以及部分 STM32 的 Cortex-M 系列中,SWP 指令仍可用于实现简单的互斥操作。

6.6 MRS/MSR 指令

MRS(Move from Status Register)和 MSR(Move to Status Register)指令用于读写程序状态寄存器 CPSR 和 SPSR,是 ARM 汇编中访问状态寄存器的唯一途径。通过这两条指令,程序可以读取或修改处理器的工作模式、中断使能状态以及条件标志位。

MRS 指令用于将程序状态寄存器的值读取到通用寄存器,其格式为:MRS{cond} Rd, CPSR/SPSR,其中各字段含义如下:

  • cond:条件码,可选,决定指令是否执行,如 EQ、NE 等。
  • Rd:目标寄存器,用于存放读取到的状态寄存器值。
  • CPSR/SPSR:要读取的状态寄存器。当前模式下读取 CPSR,异常模式下可读取 SPSR 以获取进入异常前的状态。

MRS 指令的典型用法如下:

armasm 复制代码
MRS R0, CPSR        ; 将 CPSR 的值读取到 R0
MRS R1, SPSR        ; 将 SPSR 的值读取到 R1(仅在异常模式下有效)

MSR 指令用于将通用寄存器的值或立即数写入程序状态寄存器,其格式为:MSR{cond} CPSR/SPSR, Rm 或 MSR{cond} CPSR_f/SPSR_f, #立即数。其中 CPSR_f 表示只修改 CPSR 的标志位域(N、Z、C、V),SPSR_f 同理。典型用法如下:

armasm 复制代码
MSR CPSR, R0        ; 将 R0 的值写入 CPSR
MSR CPSR_f, #0x30   ; 修改 CPSR 的标志位域,设置 N 和 Z 标志位

MRS/MSR 指令常用于以下场景:

  • 读取处理器模式:通过 MRS 读取 CPSR,再与模式位掩码比较,判断当前处于用户模式、中断模式还是管理模式。
  • 修改中断使能:通过 MSR 修改 CPSR 中的 I 位和 F 位,实现全局中断的开启与关闭。
  • 切换处理器模式:在特权模式下,通过 MSR 修改 CPSR 的模式位,切换到其他工作模式。
  • 保存与恢复状态:在异常处理或任务切换时,先用 MRS 保存 CPSR,处理完毕后再用 MSR 恢复。

例如,以下代码演示了如何读取并修改 CPSR 中的中断使能位:

armasm 复制代码
MRS R0, CPSR        ; 读取 CPSR 到 R0
ORR R0, R0, #0x80   ; 将第 7 位置 1,即关闭 IRQ 中断(I 位)
MSR CPSR_c, R0      ; 将修改后的值写回 CPSR 的控制域

上述代码中,先通过 MRS 将 CPSR 读取到 R0,再用 ORR 将 I 位置 1,最后通过 MSR 写回 CPSR 的控制域(_c 后缀表示只修改控制域),从而实现关闭 IRQ 中断。需要注意的是,修改 CPSR 属于特权操作,在用户模式下无法直接执行,需要切换到特权模式后才能使用 MSR 修改 CPSR。

MRS 和 MSR 指令是 ARM 汇编中访问程序状态寄存器的核心手段,在中断处理、任务切换和处理器模式管理等底层代码中应用极为广泛。掌握这两条指令,有助于深入理解 STM32 启动文件和 RTOS 内核中的状态管理逻辑。

6.7 移位指令

移位指令用于对寄存器中的数据进行按位移动操作,是 ARM 汇编中实现乘除 2 的幂次运算、位字段提取和位标志设置的重要工具。ARM 中常用的移位指令包括 LSL(逻辑左移)、LSR(逻辑右移)和 ASR(算术右移),此外还有 ROR(循环右移)和 RRX(带扩展的循环右移)。

移位指令的基本格式为:{cond}{S} Rd, Rm, #移位位数,其中各字段含义如下:

  • cond:条件码,可选,决定指令是否执行,如 EQ、NE 等。
  • S:可选后缀,加上后表示指令执行结果会影响 CPSR 中的条件标志位(N、Z、C)。
  • Rd:目标寄存器,用于存放移位后的结果。
  • Rm:源寄存器,存放被移位的操作数。
  • #移位位数:立即数,指定移位的位数,范围为 0 到 31。

三种基本移位指令的典型用法如下:

armasm 复制代码
LSL R0, R0, #2      ; 逻辑左移 2 位,R0 = R0 << 2
LSR R0, R0, #2      ; 逻辑右移 2 位,R0 = R0 >> 2
ASR R0, R0, #2      ; 算术右移 2 位,R0 = R0 >> 2(符号位扩展)

LSL(Logical Shift Left)指令将源操作数向左移动指定位数,低位补 0。逻辑左移 n 位相当于乘以 2 的 n 次方,常用于快速实现乘法运算。例如,LSL R0, R0, #3 表示 R0 = R0 * 8。

LSR(Logical Shift Right)指令将源操作数向右移动指定位数,高位补 0。逻辑右移 n 位相当于无符号数除以 2 的 n 次方,常用于快速实现无符号除法运算。

ASR(Arithmetic Shift Right)指令将源操作数向右移动指定位数,高位用符号位填充。算术右移 n 位相当于有符号数除以 2 的 n 次方,常用于有符号数的除法运算。例如:

armasm 复制代码
MOV R0, #-8        ; R0 = -8(0xFFFFFFF8)
ASR R0, R0, #2     ; R0 = -2(0xFFFFFFFE),符号位扩展

ROR(Rotate Right)指令用于循环右移,即移出的低位会补到高位,实现循环移位。其格式为:ROR{cond}{S} Rd, Rm, #移位位数。RRX(Rotate Right with Extend)指令则带进位标志 C 进行 33 位循环右移,将 C 作为最高位参与移位。例如:

armasm 复制代码
ROR R0, R0, #4     ; 循环右移 4 位,移出的低 4 位补到高 4 位
RRX R0, R0         ; 带进位循环右移 1 位,C 作为最高位

移位指令在 ARM 汇编中应用极为广泛,主要体现在以下几个方面:

  • 快速乘除运算:通过 LSL 和 ASR 实现乘除 2 的幂次运算,比 MUL 指令执行速度更快。
  • 位字段提取:配合 AND 指令,先移位再掩码,可提取寄存器中指定位置的位字段。
  • 位标志设置:通过移位和 ORR 指令组合,可灵活设置或清除寄存器中的指定位。
  • Operand2 移位操作数:移位指令的结果可作为其他指令的 Operand2 使用,如 ADD R0, R1, R2, LSL #2 表示 R0 = R1 + (R2 << 2)。

需要注意的是,移位指令默认不影响条件标志位,除非加上 S 后缀。加上 S 后缀后,指令执行结果会同步更新 N、Z、C 标志位,便于后续条件分支指令根据结果进行判断。例如:

armasm 复制代码
LSLS R0, R0, #1    ; R0 = R0 << 1,并更新 N、Z、C 标志位
BEQ  zero          ; 若结果为 0(Z = 1),则跳转到 zero

移位指令是 ARM 汇编中实现高效位操作和快速运算的基础。在 STM32 开发中,移位指令常用于外设寄存器位字段的提取与设置、数据压缩与解压、以及各种算法实现中。掌握这些指令,有助于编写高效、简洁的汇编代码。

7. 总结

本文围绕 STM32 平台,系统梳理了 ARM 汇编语言的核心知识体系。从汇编程序的构成出发,明确了汇编指令、伪指令与宏指令的区别;随后介绍了 ARM 指令的类型划分与 32 位定长编码格式,帮助读者建立指令与机器码之间的对应关系。

在寻址方式部分,详细讲解了立即数寻址、寄存器寻址、寄存器间接寻址、基址变址寻址、多寄存器寻址、堆栈寻址和相对寻址七种方式,重点说明了立即数的编码规则与合法判断方法,以及堆栈寻址在函数调用和现场保护中的典型应用。

在指令详解部分,逐一介绍了数据处理指令(MOV、ADD、SUB、MUL、CMP、AND、ORR、EOR、BIC 等)、存储访问指令(LDR/STR、LDM/STM)、跳转指令(B、BL、BX)、伪指令(LDR、EQU、DCB、DCD 等)、SWP 指令、MRS/MSR 指令以及移位指令(LSL、LSR、ASR、ROR、RRX),并结合 STM32 外设寄存器访问、中断处理、任务切换等实际场景给出了示例。

掌握这些基础内容后,读者可以进一步阅读 STM32 启动文件 startup.s,结合具体代码加深对 ARM 汇编的理解。建议在 Keil 或 GCC 环境中实际编译运行示例,观察反汇编结果,逐步建立指令与机器码之间的对应关系,从而提升阅读底层代码和调试嵌入式程序的能力。

相关推荐
创界工坊工作室2 小时前
22-基于STM32的汽车防盗系统设计
stm32·单片机·嵌入式硬件·proteus
雨田言炎4 小时前
STM32之RCC系统时钟控制(芯片的心脏控制器)
stm32·单片机·嵌入式硬件
额额额对了4 小时前
IMX6ULL 杂项设备 LED 驱动
stm32·单片机·嵌入式硬件
szxinmai主板定制专家5 小时前
基于RK3588+FPGA+AI的储能电站边缘网关,实现电力数据采集与智能预警
arm开发·人工智能·fpga开发·rk3576·半导体设备
半杯咖啡半行码6 小时前
STM32开发之IIC通信详解:从协议基础到FM24CL04实战
stm32·单片机·嵌入式硬件
XMAIPC_Robot6 小时前
储能控制器产品化:ARM+FPGA整机高低温、EMC可靠性测试实战总结
arm开发·人工智能·fpga开发·arm+fpga·rk3588+fpga
Seeing56 小时前
音乐111111
汇编
硅基手札7 小时前
【I2C 技术系列 05】I2C 多主仲裁与时钟同步
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设