学习笔记——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      ; 死循环
相关推荐
froginwe111 小时前
Ruby Dir 类和方法
开发语言
Jay Chou why did1 小时前
uboot—1.概述
linux
weixin_307779131 小时前
在AWS上构建类Manus的生产级AI Agent服务
运维·人工智能·云计算·aws·agi
番茄灭世神1 小时前
基于VScode搭建GD32开发环境
arm开发·vscode·单片机·cmake·gd32
星火开发设计2 小时前
表达式与语句:C++ 程序的执行逻辑基础
java·开发语言·c++·学习·知识·表达式
m0_748254662 小时前
CSS 编辑器
前端·css·编辑器
纵有疾風起2 小时前
【Linux 系统开发】基础开发工具详解:软件包管理器、编辑器。编译器开发实战
linux·服务器·开发语言·经验分享·bash·shell
Amumu121382 小时前
React扩展(二)
前端·javascript·react.js
2501_944525762 小时前
Flutter for OpenHarmony数独游戏App实战:笔记功能
笔记·flutter·游戏