浏览代码

ingest: 提取正点原子ARM汇编基础章节,整理为完整知识页

OpenCode 2 周之前
父节点
当前提交
52950a7cd6

+ 512 - 0
X-Knowledge-Base/raw/Joplin/Linux+C+C++技术体系梳理/3. 编译调试与驱动预留/9. ARM汇编基础.md

@@ -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 指令读写。