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 ; 死循环