ARM Cortex-A 裸机开发深度解析笔记
一、ARM架构:不仅是CPU,而是一个完整的生态系统
1. 系统分层与裸机开发本质
典型ARM系统软件/硬件分层结构:
应用层(APP) → 系统调用(SYS) → 内核 → 硬件(SoC)
裸机开发的核心:
-
去除内核层:程序直接在SoC硬件上运行
-
需要手动管理:寄存器、栈、外设等所有硬件资源
-
完全控制权:无操作系统开销,执行效率最高
SoC组成:
-
CPU核心:Cortex-A处理器核心
-
外设模块:GPIO、UART、PWM、定时器等
-
集成优势:高集成度,适合嵌入式产品量产
2. 关键处理单元对比(明确开发定位)
| 名称 | 全称 | 核心特性 | 应用场景 |
|---|---|---|---|
| CPU | 中央处理器 | 强大的通用计算能力 | 所有嵌入式系统的核心场景 |
| MCU | 微控制器 | 丰富外设、低功耗 | 单片机开发(Cortex-M系列) |
| MPU | 微处理器 | 高性能、需外接外设 | 裸机/Linux开发(Cortex-A系列) |
| SoC | 片上系统 | CPU+外设集成 | 量产嵌入式产品 |
| DSP | 数字信号处理器 | 专注信号处理 | 音视频处理 |
本文重点:Cortex-A系列
-
平衡性能和可扩展性
-
既是裸机开发基础,也是Linux开发基石
-
适合需要较高性能的嵌入式应用
3. RISC架构:ARM指令集的核心优势
RISC(精简指令集) vs CISC(复杂指令集)对比:
| 特性 | RISC (ARM) | CISC (x86) |
|---|---|---|
| 指令长度 | 固定(ARM状态32位) | 可变长度 |
| 执行效率 | 高,单周期执行多数指令 | 相对较低 |
| 内存访问 | 只有LDR/STR访问内存 | 多种内存访问方式 |
| 寄存器 | 丰富的通用寄存器 | 寄存器数量有限 |
| 功耗 | 低功耗设计 | 功耗较高 |
ARM RISC核心特性:
-
固定指令长度:ARM状态下统一为32位,解码简单高效
-
加载/存储架构:
-
只有LDR/STR指令可以访问内存
-
其他指令只操作寄存器
-
减少内存访问频率,提高效率
-
-
丰富寄存器:16个通用寄存器(r0-r15)
-
低功耗设计:适合移动设备和工业控制
二、ARM寄存器系统:程序执行的"基础"
1. 通用寄存器(r0-r15)
| 寄存器 | 核心作用 | 关键注意事项 |
|---|---|---|
| r0-r3 | 函数参数传递、返回值存储 | 可被调用函数修改,无需保护 |
| r4-r12 | 通用数据存储 | 调用函数必须保护(push/pop) |
| r13 (SP) | 栈指针 | 指向当前栈顶,裸机中必须手动初始化 |
| r14 (LR) | 链接寄存器 | 存储函数返回地址,嵌套调用需保护 |
| r15 (PC) | 程序计数器 | 指向当前取指地址,不能手动修改 |
寄存器使用规则:
-
调用者保存:r0-r3可由被调函数任意修改
-
被调者保存:r4-r12必须由被调函数保存恢复
-
特殊寄存器:SP、LR、PC有特殊用途
2. 状态寄存器(CPSR/SPSR)
CPSR(当前程序状态寄存器):
-
所有模式共享,存储核心状态信息
-
条件标志位:
-
N(负标志):结果为负时置1
-
Z(零标志):结果为零时置1
-
C(进位标志):加法进位或减法借位时置1
-
V(溢出标志):有符号溢出时置1
-
-
中断禁止位:
-
I位:IRQ中断使能控制(1=禁止)
-
F位:FIQ中断使能控制(1=禁止)
-
-
模式位:M[4:0],指定CPU当前工作模式
SPSR(保存的程序状态寄存器):
-
仅特权模式可用
-
异常发生时备份CPSR
-
异常返回时恢复CPSR
三、核心汇编指令:从数据操作到流程控制
1. 数据传送与移位指令(MOV/LDR)
核心功能:数据在寄存器间、寄存器与内存间移动
; 1. 立即数/寄存器传送(MOV)
mov r1, #0x08 ; r1 = 8(立即数传送,必须符合12位规则)
mov r3, r1 ; r3 = r1(寄存器复制)
mov r4, r1, lsl #2 ; r1左移2位(×4)存入r4(r4=32)
mov r4, r4, lsr #2 ; r4右移2位(÷4)存入r4(r4=8)
mov r4, r1, ror #4 ; r1循环右移4位
; 2. 非法立即数加载(LDR伪指令)
ldr r0, =0x1FB0 ; 0x1FB0违反12位规则,使用LDR加载大立即数
关键规则:
-
12位立即数规则:立即数必须能被扩展为"偶数位循环右移"的形式
-
移位范围:0-31位
-
移位类型:
-
LSL:逻辑左移,低位补0
-
LSR:逻辑右移,高位补0
-
ROR:循环右移,移出的位补到高位
-
2. 算术逻辑指令(ADD/SUB/CMP)
核心功能:数据运算和比较,S后缀更新CPSR标志
; 1. 算术运算(ADD/SUB)
mov r0, #0xA0 ; r0 = 160
mov r1, #0x08 ; r1 = 8
add r5, r0, #1 ; r5 = 160 + 1 = 161(立即数运算)
add r6, r0, r1 ; r6 = 160 + 8 = 168(寄存器运算)
add r6, r0, r1, lsl #2 ; r6 = 160 + (8×4) = 192(带移位运算)
sub r3, r0, r1 ; r3 = 160 - 8 = 152
adds r3, r0, r1 ; 带S后缀,更新CPSR标志位(N/Z/C/V)
; 2. 比较指令(CMP)→ 实质是"减法但不保存结果,只更新CPSR"
mov r0, #200
mov r1, #100
cmp r0, r1 ; 相当于 subs r0, r1(不改变r0,只更新标志)
movge r3, r0 ; 如果 r0≥r1(N=V),则 r3 = r0
movlt r3, r1 ; 如果 r0<r1(N≠V),则 r3 = r1
核心规则:
-
非法操作 :不能直接对两个立即数运算(如
add r0, #3, #2) -
编译优化:常数运算在编译时优化
-
条件执行:条件指令依赖CMP或带S后缀的算术指令
3. 位操作指令(BIC/ORR)
核心功能:精确控制寄存器特定位,硬件外设配置的关键
; 1. 清除指定位(BIC:Rd = Rn & ~Operand2)
mov r0, #0xFFFFFFFF ; r0 = 全1
bic r1, r0, #(1 << 15) ; 清除第15位(r1 = 0xFF7FFFFF)
bic r1, r0, r2, lsl #15 ; 使用寄存器移位进行灵活清除
; 2. 设置指定位(ORR:Rd = Rn | Operand2)
orr r4, r1, #(1 << 15) ; 设置第15位(r4 = 0xFFFFFFFF)
应用场景:
-
GPIO引脚方向配置
-
外设寄存器位控制
-
修改目标位而不影响其他位
四、程序流程控制:循环与分支指令
循环三要素:
-
终止条件:循环何时结束
-
迭代步长:每次循环如何变化
-
循环体:重复执行的代码
1. while循环(先检查,后执行)
特点:循环次数不确定时使用
mov r0, #1 ; 循环变量 i = 1
mov r1, #0 ; 累加和 sum = 0
loop_label
cmp r0, #1000 ; 检查 i ≤ 1000?
bgt loop_finish ; 如果 i > 1000,退出循环
add r1, r1, r0 ; sum += i(循环体)
add r0, r0, #1 ; i++(迭代步长)
b loop_label ; 跳回循环开始
loop_finish
b loop_finish ; 无限循环暂停(裸机程序无退出机制)
2. do-while循环(先执行,后检查)
特点:至少执行一次循环体
mov r0, #1 ; 循环变量 i = 1
mov r1, #0 ; 累加和 sum = 0
do_loop
add r1, r1, r0 ; sum += i(先执行循环体)
add r0, r0, #1 ; i++
cmp r0, #1000 ; 检查 i ≤ 1000?
ble do_loop ; 如果条件满足,继续循环
loop_finish
b loop_finish
3. 跳转指令对比
| 指令 | 核心功能 | 应用场景 | 关键区别 |
|---|---|---|---|
| B | 无条件跳转 | 循环、一般分支 | 不保存返回地址 |
| BL | 带返回地址的跳转 | 函数调用 | 自动保存返回地址到LR |
| BX | 带状态切换的跳转 | 函数返回、状态切换 | 支持ARM/Thumb模式切换 |
特殊用法 :bx lr = 函数返回
五、函数调用与栈机制:裸机开发的核心保障
1. ARM栈模型:满递减栈(FD)
核心规则:
-
满栈:入栈前SP指向有效数据,入栈时先减4再存数据
-
递减栈:栈指针从高地址向低地址增长
-
初始化 :
ldr sp, =0x40001000(非法立即数需用LDR加载)
栈配置示例:
-
栈基地址:0x40001000
-
栈大小:0x1000(4KB)
-
栈顶随着入栈逐渐减小
2. 完整函数调用流程(含嵌套调用)
(1) 基本函数定义与返回
; 无参数无返回值函数
asm_fun0
mov r0, #10 ; 内部逻辑:r0=10, r1=20
mov r1, #20
bx lr ; 函数返回(LR存放返回地址)
(2) 嵌套函数调用(需要LR保护)
; 函数1:返回r0和r1的最大值,嵌套调用asm_fun0
asm_twoNumMax
cmp r0, r1 ; 比较输入参数r0和r1
movge r3, r0 ; 将较大值存入r3
movlt r3, r1
stmfd sp!, {lr} ; 将LR压栈(嵌套调用需要保护LR)
bl asm_fun0 ; 调用asm_fun0(LR自动更新为PC+4)
ldmfd sp!, {lr} ; 恢复LR
bx lr ; 函数返回
; 主函数:初始化栈指针,调用嵌套函数
asm_main
ldr sp, =0x40001000 ; 初始化栈指针(满递减栈)
mov r0, #50 ; 参数1:a=50
mov r1, #20 ; 参数2:b=20
stmfd sp!, {r0-r12, lr} ; 保存主函数上下文
bl asm_twoNumMax ; 调用嵌套函数
ldmfd sp!, {r0-r12, lr} ; 恢复主函数上下文
finish
b finish ; 无限循环暂停
3. 函数调用核心规则
| 规则项 | 具体内容 |
|---|---|
| 参数传递 | 前4个参数通过r0-r3传递,超过4个使用栈传递 |
| 返回值 | 通过r0返回 |
| 上下文保存 | r4-r12和LR在嵌套调用中必须压栈保护 |
| 栈对齐 | 调用C函数时需8字节对齐,使用preserve8伪指令 |
| 函数返回 | 使用bx lr,嵌套函数需先恢复LR |
调用约定总结:
-
调用者责任:保存r0-r3(如果需要)
-
被调者责任:保存r4-r12和LR(如果修改)
-
栈平衡:函数返回前必须恢复栈指针
六、裸机开发陷阱指南(关键实践提醒)
1. 立即数合法性陷阱
问题:直接使用非法立即数
; 错误:0x1FB0违反12位规则
mov r0, #0x1FB0 ; 编译错误!
; 正确:使用LDR伪指令
ldr r0, =0x1FB0 ; 编译器会自动处理
12位立即数规则详解:
-
立即数必须能被表示为:
8位数值 × 2^(2×n),其中n=0-15 -
即:高24位全0,低8位为有效数据
-
编译器会将非法立即数转为LDR指令加载
2. 栈初始化陷阱
; 错误:立即数可能非法
mov sp, #0x40001000 ; 可能编译错误
; 正确:使用LDR伪指令
ldr sp, =0x40001000 ; 安全可靠
3. 条件指令依赖陷阱
; 错误:条件指令前无标志更新
mov r0, #100
mov r1, #50
movge r2, r0 ; 错误!CPSR标志未更新
; 正确:使用CMP或带S后缀的指令
mov r0, #100
mov r1, #50
cmp r0, r1 ; 更新CPSR标志
movge r2, r0 ; 正确!基于CMP结果
4. 函数返回约定
; 常规函数返回
bx lr ; 标准返回方式
; 嵌套函数必须保护LR
nested_function
stmfd sp!, {lr} ; 保存LR
... ; 函数体
ldmfd sp!, {lr} ; 恢复LR
bx lr ; 返回
5. 裸机程序终止
无操作系统时的处理方式:
-
无限循环 :
b finish(最简单) -
低功耗模式:进入WFI/WFE状态
-
等待中断:进入中断等待状态
七、C与汇编混合调用(实践扩展)
1. 汇编调用C函数
汇编端代码:
; 1. 导入C函数(Keil环境)
import c_add
; 2. 栈对齐伪指令(避免编译错误)
preserve8
; 3. 调用流程
asm_call_c
ldr sp, =0x40001000 ; 初始化栈
stmfd sp!, {r0-r12, lr} ; 保存上下文
mov r0, #1 ; C参数1:a=1
mov r1, #2 ; C参数2:b=2
bl c_add ; 调用C函数
ldmfd sp!, {r0-r12, lr} ; 恢复上下文
bx lr
C端函数:
// C函数定义
int c_add(int a, int b) {
return a + b;
}
2. C调用汇编函数
汇编端代码:
; 导出汇编函数
export asm_fun1
asm_fun1
add r0, r0, r1 ; 实现a+b,结果存r0
bx lr ; 返回
C端代码:
// 声明汇编函数
extern int asm_fun1(int a, int b);
// 调用汇编函数
int main(void) {
int res = asm_fun1(10, 20); // res = 30
while(1); // 无限循环
return 0;
}
3. 混合调用关键规则
| 规则项 | 汇编调用C | C调用汇编 |
|---|---|---|
| 参数传递 | r0-r3传递前4个参数 | r0-r3接收前4个参数 |
| 返回值 | r0接收返回值 | r0返回结果 |
| 栈对齐 | 需要8字节对齐 | 编译器自动处理 |
| 寄存器保护 | 保护r4-r12和LR | 保护必要寄存器 |
| 函数声明 | 使用import伪指令 | 使用export伪指令 |
八、总结:裸机开发核心要点
1. 架构层面
-
理解RISC优势:固定指令、加载/存储架构、丰富寄存器
-
明确SoC组成:CPU核心 + 外设模块的集成系统
-
定位开发层次:去除OS层,直接控制硬件
2. 指令层面
-
掌握核心指令:MOV/LDR、ADD/SUB/CMP、BIC/ORR
-
理解条件执行:基于CPSR标志的条件分支
-
熟练位操作:硬件寄存器配置的关键
3. 流程控制层面
-
实现循环结构:while、do-while循环
-
掌握函数调用:参数传递、返回值、上下文保护
-
理解栈机制:满递减栈的工作方式
4. 实践层面
-
避免常见陷阱:立即数、栈初始化、条件依赖
-
实现混合编程:C与汇编相互调用
-
设计程序结构:裸机环境下的程序组织
5. 核心思维转变
从应用开发 到系统开发的思维转变:
-
直接控制硬件:无操作系统抽象层
-
手动资源管理:寄存器、栈、内存全手动管理
-
极致性能优化:无OS开销,执行效率最高
-
完全系统控制:对整个硬件系统有完全控制权
裸机开发的真正价值:
-
深入理解硬件:从底层理解计算机工作原理
-
系统级调试能力:能够调试最底层的硬件问题
-
性能极致优化:无OS开销,发挥硬件最大性能
-
特殊应用支持:实时性要求极高或资源极度受限的场景