|
@@ -0,0 +1,512 @@
|
|
|
|
|
+# 9 ARM汇编基础
|
|
|
|
|
+
|
|
|
|
|
+## 9.1 完整概念讲解
|
|
|
|
|
+
|
|
|
|
|
+### 9.1.1 为什么需要汇编
|
|
|
|
|
+
|
|
|
|
|
+Cortex-A 系列芯片上电后,SP 指针未初始化,C 语言运行环境尚未建立,无法直接执行 C 代码。必须先用汇编完成以下工作:
|
|
|
|
|
+
|
|
|
|
|
+1. 初始化 DDR 控制器(芯片内部 RAM 不开放给用户代码时)
|
|
|
|
|
+2. 设置 SP 指针(栈顶地址)
|
|
|
|
|
+3. 初始化其他必要外设
|
|
|
|
|
+
|
|
|
|
|
+当汇编把 C 环境设置好后,才跳转到 C 语言的 `main` 函数。这与 STM32 的启动文件 `startup_stm32f10x_hd.s` 原理相同,只是 STM32 的启动文件由 ST 写好,开发者无需修改。
|
|
|
|
|
+
|
|
|
|
|
+**参考文档**:
|
|
|
|
|
+
|
|
|
|
|
+- 《ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition.pdf》— A4 章讲解全部指令
|
|
|
|
|
+- 《ARM Cortex-A(armV7)编程手册 V4.0.pdf》— 编程模型与寄存器说明
|
|
|
|
|
+
|
|
|
|
|
+### 9.1.2 GNU 汇编语法
|
|
|
|
|
+
|
|
|
|
|
+MDK 和 IAR 的汇编语法不同,不能直接移植。本教程使用 GCC 交叉编译器,汇编代码必须符合 GNU 语法。
|
|
|
|
|
+
|
|
|
|
|
+**基本格式**:
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+label:instruction @comment
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- `label`:标号,表示地址位置,以 `:` 结尾
|
|
|
|
|
+- `instruction`:汇编指令或伪操作
|
|
|
|
|
+- `@`:注释符号(也可用 `/* */`)
|
|
|
|
|
+
|
|
|
|
|
+**大小写规则**:指令、伪操作、寄存器名可全大写或全小写,但不能混用。
|
|
|
|
|
+
|
|
|
|
|
+#### 段定义伪操作
|
|
|
|
|
+
|
|
|
|
|
+| 伪操作 | 含义 |
|
|
|
|
|
+| ---------- | -------------- |
|
|
|
|
|
+| `.text` | 代码段 |
|
|
|
|
|
+| `.data` | 已初始化数据段 |
|
|
|
|
|
+| `.bss` | 未初始化数据段 |
|
|
|
|
|
+| `.rodata` | 只读数据段 |
|
|
|
|
|
+| `.section` | 自定义段 |
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+.section .testsection @ 定义自定义段
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 常用伪操作
|
|
|
|
|
+
|
|
|
|
|
+| 伪操作 | 用法示例 | 含义 |
|
|
|
|
|
+| --------- | ------------------ | ------------------ |
|
|
|
|
|
+| `.byte` | `.byte 0x12` | 定义单字节数据 |
|
|
|
|
|
+| `.short` | `.short 0x1234` | 定义双字节数据 |
|
|
|
|
|
+| `.long` | `.long 0x12345678` | 定义 4 字节数据 |
|
|
|
|
|
+| `.equ` | `.equ num, 0x12` | 赋值(num = 0x12) |
|
|
|
|
|
+| `.align` | `.align 4` | 4 字节对齐 |
|
|
|
|
|
+| `.end` | `.end` | 源文件结束 |
|
|
|
|
|
+| `.global` | `.global _start` | 定义全局符号 |
|
|
|
|
|
+
|
|
|
|
|
+#### 函数格式
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+函数名:
|
|
|
|
|
+ 函数体
|
|
|
|
|
+ 返回语句 @ 非必须
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+示例——Cortex-A7 中断服务函数:
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+/* 未定义中断 */
|
|
|
|
|
+Undefined_Handler:
|
|
|
|
|
+ ldr r0, =Undefined_Handler
|
|
|
|
|
+ bx r0
|
|
|
|
|
+
|
|
|
|
|
+/* SVC 中断 */
|
|
|
|
|
+SVC_Handler:
|
|
|
|
|
+ ldr r0, =SVC_Handler
|
|
|
|
|
+ bx r0
|
|
|
|
|
+
|
|
|
|
|
+/* 预取终止中断 */
|
|
|
|
|
+PrefAbort_Handler:
|
|
|
|
|
+ ldr r0, =PrefAbort_Handler
|
|
|
|
|
+ bx r0
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 9.1.3 数据传输指令
|
|
|
|
|
+
|
|
|
|
|
+处理器内部数据传输的三种场景:
|
|
|
|
|
+
|
|
|
|
|
+| 场景 | 指令 |
|
|
|
|
|
+| ----------------------------------- | ----- |
|
|
|
|
|
+| 寄存器 → 寄存器 | `MOV` |
|
|
|
|
|
+| 特殊寄存器(CPSR/SPSR)→ 通用寄存器 | `MRS` |
|
|
|
|
|
+| 通用寄存器 → 特殊寄存器 | `MSR` |
|
|
|
|
|
+
|
|
|
|
|
+#### MOV 指令
|
|
|
|
|
+
|
|
|
|
|
+将数据从一个寄存器拷贝到另一个寄存器,或加载立即数到寄存器。
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+MOV R0, R1 @ R0 = R1
|
|
|
|
|
+MOV R0, #0x12 @ R0 = 0x12
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### MRS 指令
|
|
|
|
|
+
|
|
|
|
|
+读取特殊寄存器(CPSR / SPSR)的值到通用寄存器。**读特殊寄存器只能用 MRS**。
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+MRS R0, CPSR @ R0 = CPSR
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### MSR 指令
|
|
|
|
|
+
|
|
|
|
|
+将通用寄存器的值写入特殊寄存器。**写特殊寄存器只能用 MSR**。
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+MSR CPSR, R0 @ CPSR = R0
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 9.1.4 存储加载指令
|
|
|
|
|
+
|
|
|
|
|
+ARM 不能直接访问存储器(包括寄存器映射的 RAM),需要通过 LDR/STR 指令间接访问。
|
|
|
|
|
+
|
|
|
|
|
+| 指令 | 描述 |
|
|
|
|
|
+| ------------------------ | ------------------------------------ |
|
|
|
|
|
+| `LDR Rd, [Rn, #offset]` | 从存储器 `Rn+offset` 读取数据到 `Rd` |
|
|
|
|
|
+| `STR Rd, [Rn, #offset]` | 将 `Rd` 的值写入存储器 `Rn+offset` |
|
|
|
|
|
+| `LDRB Rd, [Rn, #offset]` | 按字节读取 |
|
|
|
|
|
+| `STRB Rd, [Rn, #offset]` | 按字节写入 |
|
|
|
|
|
+| `LDRH Rd, [Rn, #offset]` | 按半字(16 位)读取 |
|
|
|
|
|
+| `STRH Rd, [Rn, #offset]` | 按半字写入 |
|
|
|
|
|
+
|
|
|
|
|
+#### LDR 指令详解
|
|
|
|
|
+
|
|
|
|
|
+LDR 有两种用法:
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+@ 用法1:从存储器加载数据到寄存器
|
|
|
|
|
+LDR R0, =0x0209C004 @ 将地址 0x0209C004 加载到 R0(立即数用 =)
|
|
|
|
|
+LDR R1, [R0] @ 从地址 0x0209C004 读取数据到 R1
|
|
|
|
|
+
|
|
|
|
|
+@ 用法2:加载立即数到寄存器
|
|
|
|
|
+LDR R0, =0x12345678 @ R0 = 0x12345678
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**关键区别**:加载立即数用 `=`,加载地址值也用 `=`;加载寄存器中存储的地址指向的数据用 `[]`。
|
|
|
|
|
+
|
|
|
|
|
+#### STR 指令详解
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+@ 配置 GPIO1_GDIR 寄存器(地址 0x0209C004)的值为 0x20000002
|
|
|
|
|
+LDR R0, =0x0209C004 @ R0 = 寄存器地址
|
|
|
|
|
+LDR R1, =0x20000002 @ R1 = 要写入的值
|
|
|
|
|
+STR R1, [R0] @ 将 R1 写入 R0 指向的地址
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 9.1.5 压栈出栈指令
|
|
|
|
|
+
|
|
|
|
|
+函数调用时需要保存/恢复寄存器状态(现场保护/恢复现场),使用 PUSH 和 POP 指令。
|
|
|
|
|
+
|
|
|
|
|
+| 指令 | 描述 |
|
|
|
|
|
+| ----------------------- | -------------------- |
|
|
|
|
|
+| `PUSH <reg list>` | 将寄存器列表压入栈中 |
|
|
|
|
|
+| `POP <reg list>` | 从栈中恢复寄存器列表 |
|
|
|
|
|
+| `STMFD SP!, <reg list>` | PUSH 的另一种写法 |
|
|
|
|
|
+| `LDMFD SP!, <reg list>` | POP 的另一种写法 |
|
|
|
|
|
+
|
|
|
|
|
+#### 压栈示例
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+@ 将 R0~R3 和 R12 压栈
|
|
|
|
|
+PUSH {R0~R3, R12}
|
|
|
|
|
+
|
|
|
|
|
+@ 将 LR 压栈
|
|
|
|
|
+PUSH {LR}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+堆栈变化示意(SP 初始值 0x80000000,向下增长):
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+高地址
|
|
|
|
|
+0x80000000 ┌─────────┐
|
|
|
|
|
+ │ (空) │ ← 压栈前 SP
|
|
|
|
|
+0x7FFFFFEC ├─────────┤
|
|
|
|
|
+ │ R12 │
|
|
|
|
|
+ │ R3 │
|
|
|
|
|
+ │ R2 │
|
|
|
|
|
+ │ R1 │
|
|
|
|
|
+ │ R0 │ ← PUSH {R0~R3,R12} 后 SP
|
|
|
|
|
+0x7FFFFFE8 ├─────────┤
|
|
|
|
|
+ │ LR │ ← PUSH {LR} 后 SP
|
|
|
|
|
+低地址
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 出栈示例
|
|
|
|
|
+
|
|
|
|
|
+出栈顺序必须与压栈相反:
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+POP {LR} @ 先恢复 LR
|
|
|
|
|
+POP {R0~R3, R12} @ 再恢复 R0~R3, R12
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### STMFD / LDMFD 写法
|
|
|
|
|
+
|
|
|
|
|
+`PUSH` 和 `POP` 是 `STMFD` / `LDMFD` 的简化写法:
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+STMFD SP!, {R0~R3, R12} @ 等价于 PUSH {R0~R3, R12}
|
|
|
|
|
+STMFD SP!, {LR}
|
|
|
|
|
+
|
|
|
|
|
+LDMFD SP!, {LR} @ 等价于 POP {LR}
|
|
|
|
|
+LDMFD SP!, {R0~R3, R12}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**FD = Full Descending**(满递减):ARM 使用 FD 类型堆栈,SP 指向最后一个入栈值,堆栈由高地址向低地址增长。`STM` / `LDM` 是多存储/多加载指令,可一次操作多个连续数据。
|
|
|
|
|
+
|
|
|
|
|
+### 9.1.6 跳转指令
|
|
|
|
|
+
|
|
|
|
|
+| 指令 | 描述 |
|
|
|
|
|
+| ------------ | ----------------------------------------------------- |
|
|
|
|
|
+| `B <label>` | 跳转到 label(范围 ±2KB,用 `B.W` 可扩大到 32 位) |
|
|
|
|
|
+| `BX <Rm>` | 间接跳转到 `Rm` 中的地址,并切换指令集 |
|
|
|
|
|
+| `BL <label>` | 跳转到 label,并将返回地址保存到 LR |
|
|
|
|
|
+| `BLX <Rm>` | 跳转到 `Rm` 指定地址,保存返回地址到 LR,并切换指令集 |
|
|
|
|
|
+
|
|
|
|
|
+#### B 指令——不保存返回地址
|
|
|
|
|
+
|
|
|
|
|
+用于不需要返回的跳转,例如汇编初始化完成后跳转到 C 的 `main` 函数:
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+_start:
|
|
|
|
|
+ ldr sp, =0x80200000 @ 设置栈指针
|
|
|
|
|
+ b main @ 跳转到 main,不再返回
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### BL 指令——保存返回地址
|
|
|
|
|
+
|
|
|
|
|
+用于函数调用,跳转前自动将当前 PC 值保存到 LR(R14):
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+push {r0, r1} @ 保存 r0, r1
|
|
|
|
|
+cps #0x13 @ 进入 SVC 模式
|
|
|
|
|
+bl system_irqhandler @ 调用 C 语言中断处理函数
|
|
|
|
|
+cps #0x12 @ 进入 IRQ 模式
|
|
|
|
|
+pop {r0, r1}
|
|
|
|
|
+str r0, [r1, #0x10] @ 写 EOIR,中断处理完成
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**选择原则**:需要返回原处用 `BL`,跳转后不返回用 `B`。
|
|
|
|
|
+
|
|
|
|
|
+### 9.1.7 算术运算指令
|
|
|
|
|
+
|
|
|
|
|
+| 指令 | 公式 | 说明 |
|
|
|
|
|
+| -------------------- | ------------------- | ------------ |
|
|
|
|
|
+| `ADD Rd, Rn, Rm` | `Rd = Rn + Rm` | 加法 |
|
|
|
|
|
+| `ADD Rd, Rn, #immed` | `Rd = Rn + #immed` | 立即数加法 |
|
|
|
|
|
+| `ADC Rd, Rn, Rm` | `Rd = Rn + Rm + C` | 带进位加法 |
|
|
|
|
|
+| `SUB Rd, Rn, Rm` | `Rd = Rn - Rm` | 减法 |
|
|
|
|
|
+| `SUB Rd, Rn, #immed` | `Rd = Rn - #immed` | 立即数减法 |
|
|
|
|
|
+| `SBC Rd, Rn, Rm` | `Rd = Rn - Rm - !C` | 带借位减法 |
|
|
|
|
|
+| `MUL Rd, Rn, Rm` | `Rd = Rn * Rm` | 乘法(32位) |
|
|
|
|
|
+| `UDIV Rd, Rn, Rm` | `Rd = Rn / Rm` | 无符号除法 |
|
|
|
|
|
+| `SDIV Rd, Rn, Rm` | `Rd = Rn / Rm` | 有符号除法 |
|
|
|
|
|
+
|
|
|
|
|
+嵌入式开发中最常用的是加减指令,乘除基本用不到。
|
|
|
|
|
+
|
|
|
|
|
+### 9.1.8 逻辑运算指令
|
|
|
|
|
+
|
|
|
|
|
+| 指令 | 公式 | 说明 |
|
|
|
|
|
+| -------------------- | --------------------- | ------------ |
|
|
|
|
|
+| `AND Rd, Rn, Rm` | `Rd = Rn & Rm` | 按位与 |
|
|
|
|
|
+| `AND Rd, Rn, #immed` | `Rd = Rn & #immed` | 立即数与 |
|
|
|
|
|
+| `ORR Rd, Rn, Rm` | `Rd = Rn | Rm` | 按位或 |
|
|
|
|
|
+| `ORR Rd, Rn, #immed` | `Rd = Rn | #immed` | 立即数或 |
|
|
|
|
|
+| `BIC Rd, Rn, Rm` | `Rd = Rn & (~Rm)` | 位清除 |
|
|
|
|
|
+| `BIC Rd, Rn, #immed` | `Rd = Rn & (~#immed)` | 立即数位清除 |
|
|
|
|
|
+| `ORN Rd, Rn, #immed` | `Rd = Rn | (~#immed)` | 按位或非 |
|
|
|
|
|
+| `EOR Rd, Rn, Rm` | `Rd = Rn ^ Rm` | 按位异或 |
|
|
|
|
|
+| `EOR Rd, Rn, #immed` | `Rd = Rn ^ #immed` | 立即数异或 |
|
|
|
|
|
+
|
|
|
|
|
+逻辑运算指令常用于配置 I.MX6UL 外设寄存器时的位操作。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 9.2 核心API/语法
|
|
|
|
|
+
|
|
|
|
|
+### 寄存器说明
|
|
|
|
|
+
|
|
|
|
|
+Cortex-A7 有 16 个 32 位通用寄存器(R0-R15)和 2 个特殊状态寄存器:
|
|
|
|
|
+
|
|
|
|
|
+| 寄存器 | 别名 | 用途 |
|
|
|
|
|
+| ------ | ---- | -------------------------------------------------- |
|
|
|
|
|
+| R0-R3 | | 参数传递 / 返回值(AAPCS 调用约定) |
|
|
|
|
|
+| R4-R10 | | 通用变量,被调用者保存 |
|
|
|
|
|
+| R11 | FP | 帧指针(可选) |
|
|
|
|
|
+| R12 | IP | 过程间调用暂存 |
|
|
|
|
|
+| R13 | SP | 栈指针,指向当前栈顶 |
|
|
|
|
|
+| R14 | LR | 链接寄存器,保存子程序返回地址 |
|
|
|
|
|
+| R15 | PC | 程序计数器,当前正在执行的指令地址 |
|
|
|
|
|
+| CPSR | | 当前程序状态寄存器(标志位、中断使能、处理器模式) |
|
|
|
|
|
+| SPSR | | 保存的程序状态寄存器(异常返回时恢复 CPSR) |
|
|
|
|
|
+
|
|
|
|
|
+### CPSR 各位含义
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+31 30 29 28 ... 7 6 5 4 3 2 1 0
|
|
|
|
|
+ N Z C V I F T M4 M3 M2 M1 M0
|
|
|
|
|
+ └──处理器模式──┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+| 位 | 名称 | 含义 |
|
|
|
|
|
+| ------ | ----- | ------------------------------------- |
|
|
|
|
|
+| N | 负数 | 运算结果为负置 1 |
|
|
|
|
|
+| Z | 零 | 运算结果为零置 1 |
|
|
|
|
|
+| C | 进位 | 加法进位 / 减法借位 |
|
|
|
|
|
+| V | 溢出 | 有符号运算溢出置 1 |
|
|
|
|
|
+| I | IRQ | 1 = 禁止 IRQ 中断 |
|
|
|
|
|
+| F | FIQ | 1 = 禁止 FIQ 中断 |
|
|
|
|
|
+| T | Thumb | 0 = ARM 状态,1 = Thumb 状态 |
|
|
|
|
|
+| M[4:0] | 模式 | 处理器模式(SVC=10011, IRQ=10010 等) |
|
|
|
|
|
+
|
|
|
|
|
+### 指令速查表
|
|
|
|
|
+
|
|
|
|
|
+| 类别 | 指令 | 语法 |
|
|
|
|
|
+| -------- | -------------------------- | ----------------------- |
|
|
|
|
|
+| 数据传输 | MOV / MRS / MSR | `MOV Rd, Operand2` |
|
|
|
|
|
+| 存储加载 | LDR / STR / LDRB / STRB | `LDR Rd, [Rn, #offset]` |
|
|
|
|
|
+| 压栈出栈 | PUSH / POP / STMFD / LDMFD | `PUSH {reg list}` |
|
|
|
|
|
+| 跳转 | B / BL / BX / BLX | `BL label` |
|
|
|
|
|
+| 算术 | ADD / ADC / SUB / SBC | `ADD Rd, Rn, Rm` |
|
|
|
|
|
+| 乘除 | MUL / UDIV / SDIV | `MUL Rd, Rn, Rm` |
|
|
|
|
|
+| 逻辑 | AND / ORR / BIC / EOR | `AND Rd, Rn, Rm` |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 9.3 代码示例
|
|
|
|
|
+
|
|
|
|
|
+### 完整的 Cortex-A7 启动汇编框架
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+@ Cortex-A7 启动汇编示例(适用于 I.MX6ULL)
|
|
|
|
|
+@ 文件:start.S
|
|
|
|
|
+
|
|
|
|
|
+.global _start
|
|
|
|
|
+
|
|
|
|
|
+_start:
|
|
|
|
|
+ @ ============================================
|
|
|
|
|
+ @ 第一阶段:汇编初始化
|
|
|
|
|
+ @ ============================================
|
|
|
|
|
+
|
|
|
|
|
+ @ 1. 关闭 IRQ 和 FIQ
|
|
|
|
|
+ mrs r0, cpsr
|
|
|
|
|
+ orr r0, r0, #0xc0 @ 设置 I=1, F=1,禁止中断
|
|
|
|
|
+ msr cpsr, r0
|
|
|
|
|
+
|
|
|
|
|
+ @ 2. 设置 SVC 模式
|
|
|
|
|
+ mrs r0, cpsr
|
|
|
|
|
+ bic r0, r0, #0x1f @ 清除 M[4:0] 位
|
|
|
|
|
+ orr r0, r0, #0x13 @ 设置为 SVC 模式(10011)
|
|
|
|
|
+ msr cpsr, r0
|
|
|
|
|
+
|
|
|
|
|
+ @ 3. 设置栈指针(SVC 模式栈)
|
|
|
|
|
+ ldr sp, =0x80200000 @ DDR 起始地址 + 2MB 作为栈顶
|
|
|
|
|
+
|
|
|
|
|
+ @ 4. 清零 .bss 段
|
|
|
|
|
+ ldr r0, =__bss_start @ .bss 起始地址(链接脚本定义)
|
|
|
|
|
+ ldr r1, =__bss_end @ .bss 结束地址
|
|
|
|
|
+ mov r2, #0
|
|
|
|
|
+bss_loop:
|
|
|
|
|
+ cmp r0, r1 @ 比较当前地址与结束地址
|
|
|
|
|
+ bge bss_done @ 如果 >= 结束地址,跳转完成
|
|
|
|
|
+ str r2, [r0] @ 写零
|
|
|
|
|
+ add r0, r0, #4 @ 地址 +4
|
|
|
|
|
+ b bss_loop @ 继续循环
|
|
|
|
|
+bss_done:
|
|
|
|
|
+
|
|
|
|
|
+ @ 5. 跳转到 C 语言 main 函数
|
|
|
|
|
+ bl main @ 调用 main,保存返回地址到 LR
|
|
|
|
|
+
|
|
|
|
|
+ @ main 返回后(理论上不会到这里)
|
|
|
|
|
+hang:
|
|
|
|
|
+ b hang @ 死循环
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 中断向量表与跳转
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+@ 中断向量表(放在代码段最前面)
|
|
|
|
|
+.section .text.startup, "ax"
|
|
|
|
|
+
|
|
|
|
|
+.global _vector_table
|
|
|
|
|
+_vector_table:
|
|
|
|
|
+ ldr pc, =_start @ 复位
|
|
|
|
|
+ ldr pc, =Undefined_Handler
|
|
|
|
|
+ ldr pc, =SWI_Handler
|
|
|
|
|
+ ldr pc, =PrefAbort_Handler
|
|
|
|
|
+ ldr pc, =DataAbort_Handler
|
|
|
|
|
+ nop @ 保留
|
|
|
|
|
+ ldr pc, =IRQ_Handler
|
|
|
|
|
+ ldr pc, =FIQ_Handler
|
|
|
|
|
+
|
|
|
|
|
+@ IRQ 中断服务函数示例
|
|
|
|
|
+IRQ_Handler:
|
|
|
|
|
+ push {r0, r1} @ 保存 r0, r1
|
|
|
|
|
+ cps #0x13 @ 切换到 SVC 模式
|
|
|
|
|
+ bl system_irqhandler @ 调用 C 语言中断处理函数
|
|
|
|
|
+ cps #0x12 @ 切换回 IRQ 模式
|
|
|
|
|
+ pop {r0, r1}
|
|
|
|
|
+ str r0, [r1, #0x10] @ 写 EOIR 清中断
|
|
|
|
|
+ subs pc, lr, #4 @ 返回到中断前的下一条指令
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 函数调用与现场保护
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+@ 汇编函数示例:调用 C 函数并保存寄存器
|
|
|
|
|
+my_function:
|
|
|
|
|
+ push {r4-r11, lr} @ 保存 callee-saved 寄存器和返回地址
|
|
|
|
|
+
|
|
|
|
|
+ @ 调用 C 函数
|
|
|
|
|
+ mov r0, #10 @ 第一个参数
|
|
|
|
|
+ mov r1, #20 @ 第二个参数
|
|
|
|
|
+ bl c_add_func @ 调用 C 函数,返回值在 r0
|
|
|
|
|
+
|
|
|
|
|
+ pop {r4-r11, pc} @ 恢复寄存器并返回
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 9.4 注意事项与易错点
|
|
|
|
|
+
|
|
|
|
|
+### = 与 # 的区别
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+LDR R0, =0x12345678 @ 加载立即数到 R0(伪指令,编译器生成文字池)
|
|
|
|
|
+LDR R0, [R1] @ 从 R1 指向的地址读取数据
|
|
|
|
|
+MOV R0, #0x12 @ 加载小立即数(8 位以内)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`=` 用于加载任意 32 位立即数(编译器自动处理),`#` 用于直接立即数(有限范围)。
|
|
|
|
|
+
|
|
|
|
|
+### PUSH/POP 顺序必须匹配
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+@ 错误!出栈顺序与压栈相反
|
|
|
|
|
+PUSH {r0, r1, r2}
|
|
|
|
|
+POP {r2, r1, r0} @ 正确但不推荐
|
|
|
|
|
+
|
|
|
|
|
+@ 推荐写法:POP 按原顺序写,硬件自动按正确顺序恢复
|
|
|
|
|
+PUSH {r0, r1, r2}
|
|
|
|
|
+POP {r0, r1, r2} @ 推荐!
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### B vs BL 的选择
|
|
|
|
|
+
|
|
|
|
|
+| 场景 | 使用 | 原因 |
|
|
|
|
|
+| ------------------- | ---- | -------------------- |
|
|
|
|
|
+| 初始化完成后跳 main | `B` | 不需要返回汇编 |
|
|
|
|
|
+| 调用 C 函数 | `BL` | 需要返回继续执行 |
|
|
|
|
|
+| 中断处理调用 C 函数 | `BL` | 必须返回完成中断恢复 |
|
|
|
|
|
+
|
|
|
|
|
+### 函数返回
|
|
|
|
|
+
|
|
|
|
|
+- `bx lr`:返回到 LR 保存的地址
|
|
|
|
|
+- `pop {pc}`:从栈中恢复 PC(等价于返回)
|
|
|
|
|
+- `mov pc, lr`:简单返回(不切换指令集)
|
|
|
|
|
+
|
|
|
|
|
+### 指令大小写
|
|
|
|
|
+
|
|
|
|
|
+```asm
|
|
|
|
|
+@ 以下两种写法等价
|
|
|
|
|
+MOV R0, R1
|
|
|
|
|
+mov r0, r1
|
|
|
|
|
+
|
|
|
|
|
+@ 但不能混用
|
|
|
|
|
+Mov r0, R1 @ 错误!
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 9.5 面试要点
|
|
|
|
|
+
|
|
|
|
|
+### 常见问题
|
|
|
|
|
+
|
|
|
|
|
+**Q:为什么 Cortex-A 芯片上电后必须先执行汇编?**
|
|
|
|
|
+
|
|
|
|
|
+A:上电时 SP 指针未初始化,C 语言的函数调用依赖栈(出栈入栈),没有栈 C 语言无法运行。汇编不依赖栈,可以先初始化 DDR、设置 SP,建立 C 运行环境后再跳转到 C 代码。
|
|
|
|
|
+
|
|
|
|
|
+**Q:MOV 和 LDR 加载立即数有什么区别?**
|
|
|
|
|
+
|
|
|
|
|
+A:`MOV` 只能加载 8 位立即数(通过循环右移偶数位得到的值),`LDR` 伪指令可以加载任意 32 位立即数(编译器会将值放在代码段附近的文字池中,用 PC 相对寻址加载)。
|
|
|
|
|
+
|
|
|
|
|
+**Q:B 和 BL 指令的区别?**
|
|
|
|
|
+
|
|
|
|
|
+A:`B` 只做跳转,不保存返回地址;`BL` 跳转前自动将当前 PC+4(下一条指令地址)保存到 LR 寄存器,支持子程序调用后返回。
|
|
|
|
|
+
|
|
|
|
|
+**Q:PUSH/POP 与 STMFD/LDMFD 的关系?**
|
|
|
|
|
+
|
|
|
|
|
+A:`PUSH` 等价于 `STMFD SP!`,`POP` 等价于 `LDMFD SP!`。FD 表示 Full Descending(满递减堆栈),ARM 处理器默认使用这种堆栈模型。
|
|
|
|
|
+
|
|
|
|
|
+**Q:什么是现场保护?为什么需要它?**
|
|
|
|
|
+
|
|
|
|
|
+A:函数调用或中断发生时,需要先保存当前寄存器值(R0-R15),称为现场保护;函数返回或中断处理完成后,再恢复寄存器值,称为恢复现场。这是保证程序正确执行的基础。
|
|
|
|
|
+
|
|
|
|
|
+**Q:CPSR 寄存器的作用?**
|
|
|
|
|
+
|
|
|
|
|
+A:CPSR(Current Program Status Register)保存当前处理器的状态信息,包括:条件标志位(N/Z/C/V)、中断禁止位(I/F)、处理器模式位(M[4:0])、Thumb 状态位(T)等。通过 MRS/MSR 指令读写。
|