02-ARM汇编基础.md 17 KB


title: ARM汇编基础 tags: [ARM, 汇编, 嵌入式Linux, IMX6ULL, STM32, RK3568] created: 2026-09-17 updated: 2026-09-17

pdf_ref: "正点原子 I.MX6U嵌入式Linux驱动开发指南V2.0.1 第七章 ARM汇编基础"

ARM汇编基础

前置知识: [[01-Cortex-A7架构详解]] | [[STM32学习笔记/01-STM32开发环境搭建]]

一、ARM指令集概述

1.1 ARM指令集 vs Thumb指令集

特性 ARM指令集 Thumb指令集 Thumb-2指令集
指令宽度 32位 16位 16/32位混合
寄存器访问 全部R0-R15 低寄存器R0-R7 全部R0-R15
条件执行 所有指令 仅部分指令 所有指令
性能 最高 较低 接近ARM
代码密度
典型应用 启动代码、关键路径 低功耗应用 通用嵌入式

1.2 指令格式

ARM 32位指令格式:

| 31-28 | 27-25 | 24-21 | 20 | 19-16 | 15-12 | 11-0 |
|-------|-------|-------|----|-------|-------|------|
|  cond  |  001  | opcode|  S |  Rn   |  Rd   | Operand2 |
  • cond: 条件码(4位)
  • opcode: 操作码(4位)
  • S: 状态标志更新(1位)
  • Rn: 第一源寄存器(4位)
  • Rd: 目标寄存器(4位)
  • Operand2: 第二操作数(12位)

1.3 寻址模式

寻址模式 语法 示例 说明
立即数寻址 #immediate MOV R0, #0x10 立即数在指令中
寄存器寻址 Rm ADD R0, R1, R2 操作数在寄存器
寄存器间接寻址 [Rm] LDR R0, [R1] 地址在寄存器
基址变址寻址 [Rn, #offset] LDR R0, [R1, #4] 基址+偏移
前索引寻址 [Rn, #offset]! LDR R0, [R1, #4]! 先更新基址
后索引寻址 [Rn], #offset LDR R0, [R1], #4 先访问后更新
相对寻址 label B loop PC相对跳转

二、数据处理指令

2.1 数据传输指令

MOV R0, #10          @ R0 = 10
MOV R1, R2           @ R1 = R2
MOV R0, R1, LSL #2   @ R0 = R1 << 2(左移2位)
MVN R0, #0           @ R0 = 0xFFFFFFFF (-1)
MVN R1, #0xFF        @ R1 = 0xFFFFFF00

2.2 算术指令

ADD R0, R1, R2       @ R0 = R1 + R2
ADD R0, R1, #10      @ R0 = R1 + 10
ADC R0, R1, R2       @ R0 = R1 + R2 + C(带进位加法)
SUB R0, R1, R2       @ R0 = R1 - R2
SBC R0, R1, R2       @ R0 = R1 - R2 - !C(带借位减法)

2.3 逻辑指令

AND R0, R1, #0xFF    @ R0 = R1 & 0xFF(按位与)
ORR R0, R1, #0xFF    @ R0 = R1 | 0xFF(按位或)
EOR R0, R1, #0xFF    @ R0 = R1 ^ 0xFF(按位异或)
BIC R0, R1, #0xFF    @ R0 = R1 & ~0xFF(位清除)

2.4 比较指令

CMP R0, R1           @ 更新标志位:R0 - R1
CMN R0, R1           @ 更新标志位:R0 + R1(负数比较)
TST R0, #0xFF        @ 更新标志位:R0 & 0xFF(位测试)
TEQ R0, R1           @ 更新标志位:R0 ^ R1(等值测试)

2.5 乘法指令

MUL R0, R1, R2       @ R0 = R1 * R2
MLA R0, R1, R2, R3   @ R0 = R1 * R2 + R3(乘加)

三、Load/Store指令

3.1 单寄存器传输

LDR R0, [R1]         @ R0 = *R1
LDR R0, [R1, #4]     @ R0 = *(R1+4)
LDR R0, [R1, #4]!    @ 先更新R1=R1+4,再R0=*R1(前索引)
LDR R0, [R1], #4     @ 先R0=*R1,再更新R1=R1+4(后索引)
LDR R0, =0x0209C000  @ 伪指令:加载32位地址

STR R0, [R1]         @ *R1 = R0
STR R0, [R1, #4]     @ *(R1+4) = R0
LDRB R0, [R1]        @ R0 = *(uint8_t*)R1(字节加载)
LDRH R0, [R1]        @ R0 = *(uint16_t*)R1(半字加载)

3.2 批量寄存器传输

LDM R0, {R1, R2, R3}     @ R1=*R0, R2=*(R0+4), R3=*(R0+8)
STM R0, {R1, R2, R3}     @ *R0=R1, *(R0+4)=R2, *(R0+8)=R3
LDM R0!, {R1-R3}         @ 先加载,然后R0=R0+12

3.3 堆栈操作

PUSH {R0-R3, LR}        @ 压栈(满递减FD):STMDB SP!, {R0-R3, LR}
POP {R0-R3, PC}         @ 出栈:LDMIA SP!, {R0-R3, PC}

3.4 批量传输寻址模式

后缀 模式 用途
IA 后递增 连续内存读取
IB 前递增 连续内存写入
DA 后递减 逆序内存读取
DB 前递减 堆栈压入
FD 满递减 堆栈操作(ARM常用)

四、跳转指令

4.1 跳转指令详解

B loop              @ 无条件跳转到loop
BNE loop            @ 不等于时跳转(Z=0)
BEQ loop            @ 等于时跳转(Z=1)
BL function         @ 跳转并保存返回地址到LR(用于函数调用)
BX LR               @ 返回(可能切换指令集)
BLX function        @ 跳转、保存返回地址、可能切换指令集

4.2 条件码

条件码 助记符 含义 标志位
0000 EQ 相等 Z=1
0001 NE 不相等 Z=0
0010 CS/HS 无符号大于等于 C=1
0011 CC/LO 无符号小于 C=0
0100 MI 负数 N=1
0101 PL 非负数 N=0
1000 HI 无符号大于 C=1且Z=0
1001 LS 无符号小于等于 C=0或Z=1
1010 GE 有符号大于等于 N=V
1011 LT 有符号小于 N≠V
1100 GT 有符号大于 Z=0且N=V
1101 LE 有符号小于等于 Z=1或N≠V
1110 AL 总是执行 -

4.3 条件执行示例

CMP R0, #0
BNE not_zero
MOV R0, #1          @ R0=0时执行
not_zero:

@ ARM特有:条件执行
CMP R0, #10
MOVEQ R1, #0        @ R0==10时执行
MOVNE R1, #1        @ R0!=10时执行

五、伪指令

5.1 数据定义伪指令

.word 0x12345678     @ 定义32位数据
.hword 0x1234        @ 定义16位数据
.byte 0x12           @ 定义8位数据
.asciz "Hello"       @ 定义字符串(带\0结束符)

5.2 符号定义伪指令

.equ GPIO1_BASE, 0x0209C000    @ 定义常量(推荐)
.set GPIO1_BASE, 0x0209C000    @ 等同于.equ

5.3 段控制伪指令

.text               @ 代码段
.data               @ 已初始化数据段
.bss                @ 未初始化数据段
.rodata             @ 只读数据段

5.4 符号可见性伪指令

.global _start      @ 全局符号(类似C的extern)
.weak weak_func     @ 弱符号(可被覆盖)

5.5 对齐伪指令

.align 4            @ 4字节对齐
.balign 4           @ 4字节对齐(填充NOP)

5.6 宏定义伪指令

.macro SET_GDIR reg, bit
    LDR \reg, =0X0209C004
    LDR r1, =\bit
    STR r1, [\reg]
.endm

SET_GDIR r0, 0x8    @ 使用宏

六、GNU汇编语法

6.1 文件结构

@ 文件头注释
.syntax unified          @ 统一语法(ARM/Thumb通用)
.cpu cortex-a7           @ 指定CPU

.text                    @ 代码段
.align 2                 @ 4字节对齐

.global _start           @ 入口点声明

_start:                  @ 程序入口
    @ 代码...

6.2 注释语法

@ 单行注释(推荐)
// C风格单行注释(也支持)
/* 多行注释 C风格 */

6.3 标签语法

loop:                   @ 标签定义
    B loop              @ 跳转到标签

function:               @ 函数标签
    PUSH {LR}           @ 保存返回地址
    POP {PC}            @ 返回

6.4 常量表示

MOV R0, #255            @ 十进制
MOV R0, #0xFF           @ 十六进制
MOV R0, #0b11111111     @ 二进制
MOV R0, #0377           @ 八进制
MOV R0, #'A'            @ 字符常量

七、完整示例分析

7.1 LED灯汇编驱动

@ LED灯汇编驱动 - IMX6ULL
@ 功能:点亮GPIO1_IO03连接的LED
@ 文件:led.s

.global _start

_start:
    @ ========================================
    @ 步骤1:使能GPIO1时钟
    @ ========================================
    LDR R0, =0X020C4068    @ CCGR0寄存器地址
    LDR R1, =0XFFFFFFFF    @ 所有时钟使能
    STR R1, [R0]           @ 写入CCGR0

    LDR R0, =0X020C406C    @ CCGR1寄存器地址
    STR R1, [R0]           @ 写入CCGR1

    LDR R0, =0X020C4070    @ CCGR2寄存器地址
    STR R1, [R0]           @ 写入CCGR2

    LDR R0, =0X020C4074    @ CCGR3寄存器地址
    STR R1, [R0]           @ 写入CCGR3

    LDR R0, =0X020C4078    @ CCGR4寄存器地址
    STR R1, [R0]           @ 写入CCGR4

    LDR R0, =0X020C407C    @ CCGR5寄存器地址
    STR R1, [R0]           @ 写入CCGR5

    LDR R0, =0X020C4080    @ CCGR6寄存器地址
    STR R1, [R0]           @ 写入CCGR6

    @ ========================================
    @ 步骤2:设置GPIO1_IO03复用为GPIO1_IO03
    @ ========================================
    LDR R0, =0X020E0068    @ SW_MUX_GPIO1_IO03_BASE
    LDR R1, =0X5           @ MUX_MODE = 5 (GPIO模式)
    STR R1, [R0]           @ 写入复用寄存器

    @ ========================================
    @ 步骤3:设置GPIO1_IO03的IO属性
    @ ========================================
    @ bit 16:0 HYS关闭
    @ bit [15:14]: 00 默认速率
    @ bit [13]: 0 keeper使能
    @ bit [12]: 1 pull/keeper使能
    @ bit [11]: 0 关闭开路输出
    @ bit [7:6]: 10 速度100Mhz
    @ bit [5:3]: 110 R0/6驱动能力
    @ bit [0]: 0 低转换率
    LDR R0, =0X020E02F4    @ SW_PAD_GPIO1_IO03_BASE
    LDR R1, =0X10B0        @ IO属性值
    STR R1, [R0]           @ 写入属性寄存器

    @ ========================================
    @ 步骤4:设置GPIO1_IO03为输出
    @ ========================================
    LDR R0, =0X0209C004    @ GPIO1_GDIR寄存器
    LDR R1, =0X00000008    @ bit3=1, 输出模式
    STR R1, [R0]           @ 写入方向寄存器

    @ ========================================
    @ 步骤5:点亮LED0 - 设置GPIO1_IO03输出低电平
    @ ========================================
    LDR R0, =0X0209C000    @ GPIO1_DR寄存器
    LDR R1, =0             @ 输出低电平
    STR R1, [R0]           @ 写入数据寄存器

loop:
    B loop                 @ 死循环,保持LED亮

7.2 代码逐行解释

行号 指令 说明
1 LDR R0, =0X020C4068 伪指令,将CCGR0地址加载到R0
2 LDR R1, =0XFFFFFFFF 伪指令,将0xFFFFFFFF加载到R1
3 STR R1, [R0] 将R1的值存储到R0指向的地址
4 LDR R0, =0X020E0068 加载GPIO复用寄存器地址
5 LDR R1, =0X5 GPIO模式值5
6 STR R1, [R0] 配置GPIO复用功能
7 LDR R0, =0X020E02F4 加载IO属性寄存器地址
8 LDR R1, =0X10B0 IO属性配置值
9 STR R1, [R0] 配置IO属性
10 LDR R0, =0X0209C004 加载GPIO方向寄存器地址
11 LDR R1, =0X00000008 设置bit3为输出
12 STR R1, [R0] 配置GPIO为输出模式
13 LDR R0, =0X0209C000 加载GPIO数据寄存器地址
14 LDR R1, =0 输出低电平值
15 STR R1, [R0] 输出低电平,点亮LED
16 B loop 无限循环,保持状态

7.3 链接脚本

/* 链接脚本 - imx6ul.lds */
SECTIONS {
    . = 0x87800000;  /* DDR起始地址 */

    .text : {
        *(.text)         /* 代码段 */
    }

    .rodata : {
        *(.rodata*)      /* 只读数据段 */
    }

    .data : {
        *(.data)         /* 已初始化数据段 */
    }

    __bss_start = .;
    .bss : {
        *(.bss)          /* 未初始化数据段 */
        *(COMMON)        /* 通用符号 */
    }
    __bss_end = .;
}

八、跨平台对比

8.1 IMX6ULL vs STM32 vs RK3568

特性 IMX6ULL STM32F4 RK3568
内核 Cortex-A7 Cortex-M4 Cortex-A55
汇编语法 GNU汇编 ARM汇编 GNU汇编
启动文件 start.S startup_stm32f4xx.s start.S
时钟使能 手动写CCGR寄存器 HAL_RCC_GPIOx_CLK_ENABLE() 手动写CRU寄存器
GPIO配置 手动写IOMUXC+GPIO寄存器 HAL_GPIO_Init() 手动写PMU+GPIO寄存器
延迟实现 简单循环延迟 HAL_Delay() 简单循环延迟
代码效率 中等
开发难度

8.2 汇编语法差异

@ IMX6ULL (GNU汇编)
.syntax unified
.cpu cortex-a7
ldr r0, =0x0209C000
str r1, [r0]

@ STM32 (ARM汇编)
AREA    |.text|, CODE, READONLY
THUMB
LDR R0, =0x40020000
STR R1, [R0]

@ RK3568 (GNU汇编)
.syntax unified
.cpu cortex-a55
ldr x0, =0xFF720000
str w1, [x0]

8.3 寄存器差异

寄存器 IMX6ULL STM32F4 RK3568
GPIO数据 GPIOx_DR GPIOx_ODR GPIOx_DR
GPIO方向 GPIOx_GDIR GPIOx_MODER GPIOx_DDR
GPIO输入 GPIOx_DR GPIOx_IDR GPIOx_EXT_PORT
时钟控制 CCM_CCGRx RCC_AHB1ENR CRU_CLKGATE_CON

九、面试精选

题目1:ARM汇编中,LDR伪指令和LDR指令有什么区别?

答案

  • LDR指令:真正的加载指令,从内存中读取数据

    LDR R0, [R1]        @ 从R1指向的内存地址加载数据
    
  • LDR伪指令:用于加载32位常量或地址

    LDR R0, =0x12345678 @ 加载32位立即数
    LDR R0, =_start     @ 加载标号地址
    
  • 区别

    1. LDR指令的立即数范围有限(8位循环右移),LDR伪指令可以加载任意32位值
    2. LDR伪指令会被汇编器转换为LDR指令+常量池
    3. LDR伪指令更灵活,但会占用常量池空间

题目2:ARM汇编中,PUSH和STM指令有什么关系?

答案

  • PUSH是STM的封装,等同于STMDB SP!, {reg_list}
  • STM有多种寻址模式,PUSH固定使用前递减满堆栈
  • 示例

    PUSH {R0-R3, LR}     @ 等同于 STMDB SP!, {R0-R3, LR}
    STMDB SP!, {R0-R3, LR}  @ 直接使用STM指令
    
  • 堆栈模型

    • 满递减(FD):ARM默认使用
    • 满递增(FA):某些架构使用
    • 空递减(ED):x86使用
    • 空递增(EA):较少使用

题目3:如何在ARM汇编中实现条件执行?有什么优势?

答案

  • 条件执行:ARM指令集的条件码(cond域)可以条件执行任何指令
  • 优势
    1. 减少分支跳转,提高流水线效率
    2. 减少代码尺寸
    3. 简化编译器生成代码
  • 示例

    @ 传统方式
    CMP R0, #0
    BEQ zero_case
    MOV R1, #1
    B end
    zero_case:
      MOV R1, #0
    end:
    
    @ 条件执行(更简洁)
    CMP R0, #0
    MOVEQ R1, #0
    MOVNE R1, #1
    

题目4:ARM汇编中,什么是位置无关代码?如何实现?

答案

  • 位置无关代码(PIC):代码可以在内存中任意位置运行,无需修改
  • 实现方法
    1. 使用PC相对寻址
    2. 避免使用绝对地址
    3. 使用ADRL或LDR PC相对指令
  • 示例

    @ 位置相关代码(不推荐)
    LDR R0, =_start     @ 加载绝对地址
    
    @ 位置无关代码(推荐)
    ADRL R0, _start     @ 加载相对地址
    LDR R0, [PC, #offset] @ PC相对加载
    
  • 应用场景

    1. Bootloader
    2. 内核启动代码
    3. 动态链接库

题目5:如何在ARM汇编中实现一个简单的函数调用?

答案

  • 函数调用:使用BL指令跳转并保存返回地址
  • 函数实现

    @ 函数定义
    my_function:
      PUSH {LR}           @ 保存返回地址
      PUSH {R4-R7}        @ 保存 callee-saved 寄存器
    
      @ 函数体
      MOV R0, #1          @ 返回值
    
      POP {R4-R7}         @ 恢复寄存器
      POP {PC}            @ 返回
    
    @ 函数调用
    BL my_function          @ 调用函数
    MOV R1, R0              @ 使用返回值
    
  • 调用约定

    1. R0-R3:参数和返回值
    2. R4-R11:callee-saved寄存器
    3. R12:IP,过程间调用暂存
    4. R13:SP,栈指针
    5. R14:LR,链接寄存器
    6. R15:PC,程序计数器

相关知识: [[01-Cortex-A7架构详解]] | [[03-时钟与电源管理]] | [[04-GPIO与中断系统]]

参考来源: 正点原子 I.MX6U嵌入式Linux驱动开发指南V2.0.1 最后更新: 2026-09-17