学习笔记——ARM Cortex-A 裸机开发实战指南

ARM Cortex-A 裸机开发实战指南

一、汇编指令系统精解

1. 数据移动指令全解析

复制代码
; ============ MOV指令的完整形态 ============
mov r0, #0xA0           ; 基础形式:立即数→寄存器
mov r1, r0              ; 寄存器→寄存器拷贝

; 移位变种(第二操作数移位)
mov r2, r0, lsl #2      ; 逻辑左移:r2 = r0 << 2
mov r2, r0, lsr #3      ; 逻辑右移:r2 = r0 >> 3
mov r2, r0, asr #2      ; 算术右移:保持符号位
mov r2, r0, ror #4      ; 循环右移
mov r2, r0, rrx         ; 带扩展右移(C标志参与)

; 寄存器控制移位量
mov r3, #8
mov r2, r0, lsl r3      ; 移位量由寄存器指定

; ============ 非法立即数加载技巧 ============
; 错误:mov r0, #0x1FB0    ; ❌ 0x1FB0不是合法立即数
; 正确:
ldr r0, =0x1FB0          ; ✅ 伪指令,汇编器自动处理

; ============ 内存加载指令 ============
ldr r0, [r1]            ; 从内存加载:r0 = *r1
ldr r0, [r1, #4]        ; r0 = *(r1 + 4)
ldr r0, [r1, #4]!       ; r0 = *(r1 + 4); r1 = r1 + 4
ldr r0, [r1], #4        ; r0 = *r1; r1 = r1 + 4

2. 算术运算的条件标志影响

复制代码
; ============ 条件标志位(NZCV)产生机制 ============
; 情况1:正数溢出为负数
mov r0, #0x7FFFFFFF      ; 最大正有符号数
mov r1, #1
adds r2, r0, r1          ; r2 = 0x80000000
; N=1 (结果最高位为1,表示为负数)
; Z=0 (结果非零)
; C=0 (无进位,从无符号角度看没超过32位)
; V=1 (有符号溢出:正+正=负)

; 情况2:无符号溢出
mov r0, #0xFFFFFFFF      ; 最大无符号数
adds r2, r0, r1          ; r2 = 0x00000000
; N=0 (结果最高位为0)
; Z=1 (结果为0)
; C=1 (有进位,无符号数溢出)
; V=0 (无有符号溢出)

; 情况3:有借位减法
mov r0, #100
mov r1, #200
subs r2, r0, r1          ; r2 = -100 (0xFFFFFF9C)
; N=1 (结果为负)
; Z=0 (非零)
; C=0 (有借位)
; V=0 (无溢出)

; ============ 条件执行实战 ============
mov r0, #200
mov r1, #100

; 最大值算法(无分支)
cmp r0, r1              ; 比较,设置NZCV
movgt r2, r0            ; 仅当r0>r1时执行
movle r2, r1            ; 仅当r0≤r1时执行

; 绝对值计算
mov r0, #-50
cmp r0, #0              ; 判断正负
rsblt r0, r0, #0        ; 若为负,r0 = 0 - r0

3. 位操作指令详解

复制代码
; ============ BIC(位清除)指令 ============
; 语法:BIC Rd, Rn, Operand2
; 功能:Rd = Rn & ~Operand2

mov r0, #0xFFFFFFFF     ; r0 = 全1
bic r1, r0, #0xFF       ; 清除低8位:r1 = 0xFFFFFF00
bic r1, r0, #(1 << 31)  ; 清除最高位:r1 = 0x7FFFFFFF

; 清除多个位
bic r1, r0, #0x0F0F0F0F ; 清除每字节的低4位

; ============ ORR(位设置)指令 ============
; 语法:ORR Rd, Rn, Operand2
; 功能:Rd = Rn | Operand2

mov r0, #0              ; r0 = 0
orr r1, r0, #0xFF       ; 设置低8位:r1 = 0x000000FF
orr r1, r1, #(1 << 31)  ; 设置最高位:r1 = 0x800000FF

; ============ EOR(位取反)指令 ============
; 语法:EOR Rd, Rn, Operand2
; 功能:Rd = Rn ^ Operand2

mov r0, #0xAAAAAAAA     ; r0 = 1010 1010...
eor r1, r0, #0xFFFFFFFF ; 按位取反:r1 = 0x55555555

; ============ MVN(移动取反)指令 ============
; 语法:MVN Rd, Operand2
; 功能:Rd = ~Operand2

mvn r0, #0              ; r0 = 0xFFFFFFFF
mvn r1, #0xFF           ; r1 = 0xFFFFFF00

二、程序流程控制实战

1. 循环结构实现

复制代码
; ============ while循环(先判断后执行)============
; C代码:while(i <= 1000) { sum += i; i++; }
mov r0, #1              ; i = 1
mov r1, #0              ; sum = 0

while_loop:
    cmp r0, #1000       ; 判断 i <= 1000?
    bgt while_end       ; 如果 i > 1000,跳出
    
    add r1, r1, r0      ; sum += i
    add r0, r0, #1      ; i++
    
    b while_loop        ; 继续循环

while_end:
    ; 循环结束,r1 = 1到1000的和

; ============ do-while循环(先执行后判断)============
; C代码:do { sum += i; i++; } while(i <= 1000);
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         ; 如果满足条件,继续循环

; ============ for循环(标准格式)============
; C代码:for(i=0; i<10; i++) { sum += i; }
mov r0, #0              ; i = 0
mov r1, #0              ; sum = 0
mov r2, #10             ; 循环次数

for_loop:
    cmp r0, r2          ; 判断 i < 10?
    bge for_end
    
    add r1, r1, r0      ; sum += i
    add r0, r0, #1      ; i++
    
    b for_loop

for_end:

2. 分支与跳转指令

复制代码
; ============ 三种跳转指令对比 ============
; 1. B指令 - 简单跳转
    b label1            ; 无条件跳转到label1
    
; 2. BL指令 - 带链接的跳转(函数调用)
    bl my_function      ; 调用函数,LR = PC + 4
    
; 3. BX指令 - 带状态切换的跳转
    bx lr               ; 返回到调用者(LR -> PC)
    bx r0               ; 跳转到r0指定的地址

; ============ 条件跳转实战 ============
mov r0, #100
mov r1, #200

; if-else结构
cmp r0, r1
bgt if_greater          ; 如果 r0 > r1
    ; else分支
    mov r2, r1          ; r2 = r1
    b if_end
if_greater:
    ; if分支
    mov r2, r0          ; r2 = r0
if_end:

; switch-case结构(基于值跳转)
mov r0, #2              ; 测试值
cmp r0, #1
beq case_1
cmp r0, #2
beq case_2
cmp r0, #3
beq case_3
b default_case

case_1:
    ; 处理case 1
    b switch_end
case_2:
    ; 处理case 2
    b switch_end
case_3:
    ; 处理case 3
    b switch_end
default_case:
    ; 默认处理
switch_end:

三、函数调用与栈操作

1. 基本函数调用

复制代码
; ============ 简单函数(无参数无返回值)============
simple_function:
    ; 函数体
    mov r0, #10
    mov r1, #20
    add r2, r0, r1
    bx lr               ; 返回

; 调用
    bl simple_function

; ============ 带参数和返回值的函数 ============
; C原型:int add(int a, int b)
add_function:
    add r0, r0, r1      ; r0 = a + b (返回值)
    bx lr               ; 返回

; 调用(参数通过r0,r1传递)
    mov r0, #100        ; 第一个参数
    mov r1, #200        ; 第二个参数
    bl add_function     ; 调用,返回值在r0

; ============ 多个参数的函数 ============
; C原型:int func(int a, int b, int c, int d)
multi_param_function:
    add r0, r0, r1      ; r0 = a + b
    add r0, r0, r2      ; r0 += c
    add r0, r0, r3      ; r0 += d
    bx lr

; 调用(前4个参数用r0-r3)
    mov r0, #1
    mov r1, #2
    mov r2, #3
    mov r3, #4
    bl multi_param_function

2. 栈操作与现场保护

复制代码
; ============ 栈初始化 ============
; 错误:mov sp, #0x40001000  ; ❌ 非法立即数
; 正确:
    ldr sp, =0x40001000     ; ✅ 设置栈底

; ============ 入栈保护现场 ============
; STMDB = Store Multiple Decrement Before
; 等价于 STMFD(满递减栈)
protect_context:
    stmfd sp!, {r0-r12, lr} ; 保存所有寄存器和返回地址
    ; 或者有选择地保存
    stmfd sp!, {r4-r8, lr}  ; 只保存需要保护的寄存器

; ============ 出栈恢复现场 ============
; LDMIA = Load Multiple Increment After
; 等价于 LDMFD
restore_context:
    ldmfd sp!, {r0-r12, pc} ; 恢复寄存器并返回
    ; 注意:这里用pc而不是lr,直接恢复PC跳转

; ============ 嵌套函数调用完整示例 ============
outer_function:
    ; 1. 保护现场
    stmfd sp!, {r4-r6, lr}
    
    ; 2. 函数体
    mov r4, #100
    mov r5, #200
    add r6, r4, r5
    
    ; 3. 调用内部函数
    mov r0, r4            ; 参数传递
    mov r1, r5
    bl inner_function     ; 调用
    
    ; 4. 恢复现场并返回
    ldmfd sp!, {r4-r6, pc}

inner_function:
    ; 1. 保护现场(如果需要)
    stmfd sp!, {r7, lr}
    
    ; 2. 函数体
    add r0, r0, r1        ; 计算
    
    ; 3. 恢复现场并返回
    ldmfd sp!, {r7, pc}

3. 复杂函数参数传递

复制代码
; ============ 超过4个参数的函数 ============
; C原型:int func(int a, int b, int c, int d, int e, int f)
complex_function:
    ; 参数布局:
    ; r0 = a, r1 = b, r2 = c, r3 = d
    ; 栈中:sp = e, sp+4 = f
    
    ; 1. 获取栈中参数
    ldr r4, [sp]          ; r4 = e
    ldr r5, [sp, #4]      ; r5 = f
    
    ; 2. 计算
    add r0, r0, r1        ; r0 = a + b
    add r0, r0, r2        ; r0 += c
    add r0, r0, r3        ; r0 += d
    add r0, r0, r4        ; r0 += e
    add r0, r0, r5        ; r0 += f
    
    ; 3. 返回
    bx lr

; 调用超过4个参数的函数
call_complex:
    ; 前4个参数通过寄存器
    mov r0, #1            ; a = 1
    mov r1, #2            ; b = 2
    mov r2, #3            ; c = 3
    mov r3, #4            ; d = 4
    
    ; 第5、6个参数通过栈
    mov r4, #5
    mov r5, #6
    stmfd sp!, {r4, r5}   ; 压栈参数e和f
    
    bl complex_function    ; 调用
    
    ; 清理栈空间
    add sp, sp, #8        ; 弹出参数

四、C与汇编混合编程

1. 汇编调用C函数

复制代码
; ============ 汇编文件 ============
    area reset, code, readonly
    code32
    entry
    
    ; 声明外部C函数
    import c_add
    import c_multiply
    
    preserve8             ; 8字节栈对齐
    
asm_main:
    ; 1. 初始化栈
    ldr sp, =0x40001000
    
    ; 2. 调用C函数(参数≤4个)
    mov r0, #10           ; 第一个参数
    mov r1, #20           ; 第二个参数
    bl c_add              ; 调用,返回值在r0
    
    ; 3. 调用另一个C函数
    mov r0, #5
    mov r1, #6
    bl c_multiply
    
    ; 4. 死循环
    b .
    
    end

; ============ C文件 ============
// main.c
int c_add(int a, int b) {
    return a + b;
}

int c_multiply(int a, int b) {
    return a * b;
}

2. C调用汇编函数

复制代码
; ============ 汇编文件 ============
    area code, code, readonly
    code32
    
    ; 导出函数供C调用
    export asm_max
    export asm_min
    
asm_max:
    ; 找出两个数中的最大值
    ; 原型:int asm_max(int a, int b)
    cmp r0, r1
    movgt r0, r0          ; 如果r0>r1,保持r0
    movle r0, r1          ; 否则r0=r1
    bx lr

asm_min:
    ; 找出两个数中的最小值
    ; 原型:int asm_min(int a, int b)
    cmp r0, r1
    movlt r0, r0          ; 如果r0<r1,保持r0
    movge r0, r1          ; 否则r0=r1
    bx lr
    
    end

; ============ C文件 ============
// main.c
extern int asm_max(int a, int b);
extern int asm_min(int a, int b);

int main(void) {
    int a = 100, b = 200;
    
    int max = asm_max(a, b);  // 调用汇编函数
    int min = asm_min(a, b);
    
    while(1);
    return 0;
}

五、工作模式切换实战

1. 模式切换代码

复制代码
; ============ CPSR操作指令 ============
; MRS - 从状态寄存器读入通用寄存器
; MSR - 从通用寄存器写入状态寄存器

; 切换到IRQ模式
switch_to_irq:
    mrs r0, cpsr             ; 读取当前CPSR
    
    ; 清除模式位[4:0]
    bic r0, r0, #0x1F
    
    ; 设置IRQ模式(0x12)
    orr r0, r0, #0x12
    
    ; 禁止IRQ中断(可选)
    orr r0, r0, #(1 << 7)
    
    ; 写回CPSR
    msr cpsr_c, r0
    
    ; 设置IRQ模式栈指针
    ldr sp, =irq_stack_top
    bx lr

; 切换回User模式
switch_to_user:
    mrs r0, cpsr
    
    ; 清除模式位
    bic r0, r0, #0x1F
    
    ; 设置User模式(0x10)
    orr r0, r0, #0x10
    
    ; 开启IRQ中断
    bic r0, r0, #(1 << 7)
    
    ; 写回CPSR
    msr cpsr_c, r0
    
    ; 设置User模式栈指针
    ldr sp, =user_stack_top
    bx lr

; ============ 为每个模式设置独立的栈 ============
init_stacks:
    ; 设置各个模式的栈指针
    
    ; 1. 进入IRQ模式设置栈
    mrs r0, cpsr
    bic r1, r0, #0x1F
    orr r1, r1, #0x12        ; IRQ模式
    msr cpsr_c, r1
    ldr sp, =0x40002000      ; IRQ栈
    
    ; 2. 进入SVC模式设置栈
    mrs r0, cpsr
    bic r1, r0, #0x1F
    orr r1, r1, #0x13        ; SVC模式
    msr cpsr_c, r1
    ldr sp, =0x40003000      ; SVC栈
    
    ; 3. 回到原来的模式
    msr cpsr_c, r0
    bx lr

六、完整启动代码示例

复制代码
; ============ 完整启动代码 ============
    area reset, code, readonly
    code32
    entry
    preserve8
    
    ; 异常向量表
vectors:
    ldr pc, =reset_handler      ; 复位
    ldr pc, =undef_handler      ; 未定义指令
    ldr pc, =swi_handler        ; 软件中断
    ldr pc, =prefetch_handler   ; 预取中止
    ldr pc, =data_handler       ; 数据中止
    nop                         ; 保留
    ldr pc, =irq_handler        ; IRQ中断
    ldr pc, =fiq_handler        ; FIQ中断

reset_handler:
    ; 1. 设置各个模式的栈指针
    bl init_stacks
    
    ; 2. 初始化BSS段(清零)
    ldr r0, =__bss_start
    ldr r1, =__bss_end
    mov r2, #0
    
bss_clear:
    cmp r0, r1
    strlt r2, [r0], #4
    blt bss_clear
    
    ; 3. 拷贝数据段
    ldr r0, =__data_load        ; ROM中的数据
    ldr r1, =__data_start       ; RAM中的目标
    ldr r2, =__data_end
    
data_copy:
    cmp r1, r2
    ldrlt r3, [r0], #4
    strlt r3, [r1], #4
    blt data_copy
    
    ; 4. 设置中断向量表
    ldr r0, =0x00000000         ; 向量表基址
    ldr r1, =vectors
    mov r2, #8                  ; 8个异常向量
    
vector_copy:
    cmp r2, #0
    ldr r3, [r1], #4
    str r3, [r0], #4
    sub r2, r2, #1
    bne vector_copy
    
    ; 5. 初始化中断控制器(省略)
    
    ; 6. 开启中断
    mrs r0, cpsr
    bic r0, r0, #(1 << 7)       ; 清除I位,开启IRQ
    msr cpsr_c, r0
    
    ; 7. 跳转到C主函数
    import main
    bl main
    
    ; 8. 如果main返回,则进入死循环
    b .

; 异常处理函数(简化版)
undef_handler:
    b undef_handler

swi_handler:
    b swi_handler

prefetch_handler:
    b prefetch_handler

data_handler:
    b data_handler

irq_handler:
    ; 1. 保护现场
    sub lr, lr, #4              ; 调整返回地址
    stmfd sp!, {r0-r12, lr}
    
    ; 2. 调用C中断处理函数
    import irq_handler_c
    bl irq_handler_c
    
    ; 3. 恢复现场
    ldmfd sp!, {r0-r12, pc}^    ; ^表示恢复CPSR

fiq_handler:
    b fiq_handler

; 栈初始化函数
init_stacks:
    ; 保存当前模式
    mrs r0, cpsr
    mov r4, r0
    
    ; 设置IRQ模式栈
    bic r1, r0, #0x1F
    orr r1, r1, #0x12
    msr cpsr_c, r1
    ldr sp, =irq_stack_top
    
    ; 设置SVC模式栈
    bic r1, r0, #0x1F
    orr r1, r1, #0x13
    msr cpsr_c, r1
    ldr sp, =svc_stack_top
    
    ; 设置ABT模式栈
    bic r1, r0, #0x1F
    orr r1, r1, #0x17
    msr cpsr_c, r1
    ldr sp, =abt_stack_top
    
    ; 设置UND模式栈
    bic r1, r0, #0x1F
    orr r1, r1, #0x1B
    msr cpsr_c, r1
    ldr sp, =und_stack_top
    
    ; 返回原始模式
    msr cpsr_c, r4
    ldr sp, =usr_stack_top
    
    bx lr

; 栈区域定义
    area stack, data, readwrite
irq_stack_base    space 256
irq_stack_top
svc_stack_base    space 1024
svc_stack_top
abt_stack_base    space 256
abt_stack_top
und_stack_base    space 256
und_stack_top
usr_stack_base    space 4096
usr_stack_top

    end

七、调试技巧与常见问题

1. 常见错误与解决方案

复制代码
; ============ 问题1:非法立即数 ============
; 错误:
    mov r0, #0x1234       ; ❌ 可能不是合法立即数
; 解决:
    ldr r0, =0x1234       ; ✅ 使用LDR伪指令

; ============ 问题2:栈未对齐 ============
; 错误:
    bl c_function         ; ❌ 可能导致对齐错误
; 解决:
    preserve8             ; ✅ 添加栈对齐伪指令
    ; 或者手动对齐
    bic sp, sp, #7        ; 8字节对齐

; ============ 问题3:寄存器未保护 ============
; 错误:
nested_function:
    mov r4, #100          ; 使用了r4
    bl another_function   ; another_function可能修改r4
    add r0, r4, #1        ; ❌ r4可能已被修改
; 解决:
nested_function:
    stmfd sp!, {r4, lr}   ; ✅ 保护r4和lr
    mov r4, #100
    bl another_function
    add r0, r4, #1
    ldmfd sp!, {r4, pc}   ; 恢复并返回

; ============ 问题4:死循环优化 ============
; 裸机程序不能退出,需要死循环
finish:
    b finish              ; 简单死循环
    ; 或者
    wfi                   ; 等待中断(省电)
    b finish

2. 调试宏定义

复制代码
; ============ 调试辅助宏 ============
    ; 设置断点(软件断点)
    macro $bkpt
    bkpt #0
    mend
    
    ; 无限循环(用于调试)
    macro $debug_loop $label
$label
    b $label
    mend
    
    ; 寄存器打印(通过串口)
    macro $print_reg $reg
    ; 假设串口地址在r5
    str $reg, [r5]
    mend
    
    ; 使用示例
    mov r0, #100
    $print_reg r0         ; 打印r0的值
    $bkpt                 ; 断点
    $debug_loop loop      ; 死循环
相关推荐
ID_180079054731 分钟前
如何使用 Python 调用小红书笔记评论 API 时进行并发控制?
开发语言·笔记·python
馨谙2 分钟前
Docker常用命令
运维·docker·容器
MacroZheng3 分钟前
全面升级!看看人家的后台管理系统,确实清新优雅!
前端·vue.js·typescript
齐潇宇4 分钟前
Tomcat服务
linux·运维·网络·http·tomcat·web应用
自不量力的A同学4 分钟前
MateClaw v1.0.418 发布
笔记
虎头金猫6 分钟前
GodoOS是一款轻量级云端办公系统,整合Word、Excel、PPT等常用工具,支持Docker 一键部署,随时随地远程办公
运维·服务器·网络·程序人生·docker·容器·职场和发展
Mintopia6 分钟前
一套简单但有效的"代码可读性"提升法:不用重构也能清爽
前端
lsx2024067 分钟前
PHP Error处理指南
开发语言
沐雪轻挽萤10 分钟前
4. C++17新特性-内联变量 (Inline Variables)
开发语言·c++
木下~learning11 分钟前
嵌入式Linux 小项目:RK3399 基于 MPlayer 实现视频播放器(从环境搭建到完整播放列表)
linux·运维·嵌入式硬件·音视频