title: ARM汇编基础 tags: [ARM, 汇编, 嵌入式Linux, IMX6ULL, STM32, RK3568] created: 2026-09-17 updated: 2026-09-17
前置知识: [[01-Cortex-A7架构详解]] | [[STM32学习笔记/01-STM32开发环境搭建]]
| 特性 | ARM指令集 | Thumb指令集 | Thumb-2指令集 |
|---|---|---|---|
| 指令宽度 | 32位 | 16位 | 16/32位混合 |
| 寄存器访问 | 全部R0-R15 | 低寄存器R0-R7 | 全部R0-R15 |
| 条件执行 | 所有指令 | 仅部分指令 | 所有指令 |
| 性能 | 最高 | 较低 | 接近ARM |
| 代码密度 | 低 | 高 | 高 |
| 典型应用 | 启动代码、关键路径 | 低功耗应用 | 通用嵌入式 |
ARM 32位指令格式:
| 31-28 | 27-25 | 24-21 | 20 | 19-16 | 15-12 | 11-0 |
|-------|-------|-------|----|-------|-------|------|
| cond | 001 | opcode| S | Rn | Rd | Operand2 |
| 寻址模式 | 语法 | 示例 | 说明 |
|---|---|---|---|
| 立即数寻址 | #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相对跳转 |
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
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(带借位减法)
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(位清除)
CMP R0, R1 @ 更新标志位:R0 - R1
CMN R0, R1 @ 更新标志位:R0 + R1(负数比较)
TST R0, #0xFF @ 更新标志位:R0 & 0xFF(位测试)
TEQ R0, R1 @ 更新标志位:R0 ^ R1(等值测试)
MUL R0, R1, R2 @ R0 = R1 * R2
MLA R0, R1, R2, R3 @ R0 = R1 * R2 + R3(乘加)
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(半字加载)
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
PUSH {R0-R3, LR} @ 压栈(满递减FD):STMDB SP!, {R0-R3, LR}
POP {R0-R3, PC} @ 出栈:LDMIA SP!, {R0-R3, PC}
| 后缀 | 模式 | 用途 |
|---|---|---|
| IA | 后递增 | 连续内存读取 |
| IB | 前递增 | 连续内存写入 |
| DA | 后递减 | 逆序内存读取 |
| DB | 前递减 | 堆栈压入 |
| FD | 满递减 | 堆栈操作(ARM常用) |
B loop @ 无条件跳转到loop
BNE loop @ 不等于时跳转(Z=0)
BEQ loop @ 等于时跳转(Z=1)
BL function @ 跳转并保存返回地址到LR(用于函数调用)
BX LR @ 返回(可能切换指令集)
BLX function @ 跳转、保存返回地址、可能切换指令集
| 条件码 | 助记符 | 含义 | 标志位 |
|---|---|---|---|
| 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 | 总是执行 | - |
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时执行
.word 0x12345678 @ 定义32位数据
.hword 0x1234 @ 定义16位数据
.byte 0x12 @ 定义8位数据
.asciz "Hello" @ 定义字符串(带\0结束符)
.equ GPIO1_BASE, 0x0209C000 @ 定义常量(推荐)
.set GPIO1_BASE, 0x0209C000 @ 等同于.equ
.text @ 代码段
.data @ 已初始化数据段
.bss @ 未初始化数据段
.rodata @ 只读数据段
.global _start @ 全局符号(类似C的extern)
.weak weak_func @ 弱符号(可被覆盖)
.align 4 @ 4字节对齐
.balign 4 @ 4字节对齐(填充NOP)
.macro SET_GDIR reg, bit
LDR \reg, =0X0209C004
LDR r1, =\bit
STR r1, [\reg]
.endm
SET_GDIR r0, 0x8 @ 使用宏
@ 文件头注释
.syntax unified @ 统一语法(ARM/Thumb通用)
.cpu cortex-a7 @ 指定CPU
.text @ 代码段
.align 2 @ 4字节对齐
.global _start @ 入口点声明
_start: @ 程序入口
@ 代码...
@ 单行注释(推荐)
// C风格单行注释(也支持)
/* 多行注释 C风格 */
loop: @ 标签定义
B loop @ 跳转到标签
function: @ 函数标签
PUSH {LR} @ 保存返回地址
POP {PC} @ 返回
MOV R0, #255 @ 十进制
MOV R0, #0xFF @ 十六进制
MOV R0, #0b11111111 @ 二进制
MOV R0, #0377 @ 八进制
MOV R0, #'A' @ 字符常量
@ 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亮
| 行号 | 指令 | 说明 |
|---|---|---|
| 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 |
无限循环,保持状态 |
/* 链接脚本 - imx6ul.lds */
SECTIONS {
. = 0x87800000; /* DDR起始地址 */
.text : {
*(.text) /* 代码段 */
}
.rodata : {
*(.rodata*) /* 只读数据段 */
}
.data : {
*(.data) /* 已初始化数据段 */
}
__bss_start = .;
.bss : {
*(.bss) /* 未初始化数据段 */
*(COMMON) /* 通用符号 */
}
__bss_end = .;
}
| 特性 | 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() | 简单循环延迟 |
| 代码效率 | 中等 | 高 | 高 |
| 开发难度 | 高 | 中 | 高 |
@ 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]
| 寄存器 | 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 |
答案:
LDR指令:真正的加载指令,从内存中读取数据
LDR R0, [R1] @ 从R1指向的内存地址加载数据
LDR伪指令:用于加载32位常量或地址
LDR R0, =0x12345678 @ 加载32位立即数
LDR R0, =_start @ 加载标号地址
区别:
答案:
STMDB SP!, {reg_list}示例:
PUSH {R0-R3, LR} @ 等同于 STMDB SP!, {R0-R3, LR}
STMDB SP!, {R0-R3, LR} @ 直接使用STM指令
堆栈模型:
答案:
示例:
@ 传统方式
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
答案:
示例:
@ 位置相关代码(不推荐)
LDR R0, =_start @ 加载绝对地址
@ 位置无关代码(推荐)
ADRL R0, _start @ 加载相对地址
LDR R0, [PC, #offset] @ PC相对加载
应用场景:
答案:
函数实现:
@ 函数定义
my_function:
PUSH {LR} @ 保存返回地址
PUSH {R4-R7} @ 保存 callee-saved 寄存器
@ 函数体
MOV R0, #1 @ 返回值
POP {R4-R7} @ 恢复寄存器
POP {PC} @ 返回
@ 函数调用
BL my_function @ 调用函数
MOV R1, R0 @ 使用返回值
调用约定:
相关知识: [[01-Cortex-A7架构详解]] | [[03-时钟与电源管理]] | [[04-GPIO与中断系统]]
参考来源: 正点原子 I.MX6U嵌入式Linux驱动开发指南V2.0.1 最后更新: 2026-09-17