文章目录
- 前言
- 一、开发环境搭建:交叉编译器与烧录环境
- 1.1 Linaro交叉编译器安装
- 1.2 环境踩坑记录
- 二、ARM启动代码核心原理:从向量表到栈初始化
- 2.1 异常向量表:程序的入口索引
- 2.2 工作模式与栈:为什么要设两个栈
- 2.3 中断的先关后开逻辑
- 三、SoC引脚配置三步法:点亮LED的硬件逻辑
- 3.1 第一步:引脚复用配置(IOMUXC)
- 3.2 第二步:电气属性配置(PAD)
- 3.3 第三步:GPIO方向与电平控制
- 四、交叉编译工具链:四个核心工具的作用
- 五、汇编与C混合调用:AAPCS调用规范
- 5.1 核心规则
- 5.2 C调用汇编函数示例
- 5.3 汇编调用C函数
- 六、完整上电执行流程梳理
- 总结
前言
最近在学习ARM Cortex-A架构裸机开发,从交叉编译环境搭建,到异常向量表、启动流程、IOMUXC引脚配置,再到汇编与C语言混合调用规范,踩了不少环境的坑,也把底层原理串了一遍。本文就以最经典的LED闪烁实验为载体,完整梳理从环境搭建、启动代码原理、硬件引脚配置到编译调用的全链路知识点,既是个人学习复盘,也给入门的同学做个参考。
一、开发环境搭建:交叉编译器与烧录环境
裸机开发第一步就是搭建交叉编译环境,我们用的是Linaro GCC 4.9.4版本,在x86主机上编译能在ARM开发板上运行的程序。
1.1 Linaro交叉编译器安装
完整安装步骤如下:
# 1. 创建安装目录
sudo mkdir -p /usr/local/arm
2. 拷贝压缩包并解压
sudo cp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm/
cd /usr/local/arm
sudo tar -xvf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
3. 配置环境变量
vi ~/.bashrc
在文件末尾添加
export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin
4. 生效并验证
source ~/.bashrc
arm-linux-gnueabihf-gcc -v
输出gcc version 4.9.4 (Linaro GCC 4.9-2017.01)即为安装成功。
1.2 环境踩坑记录
本次学习踩了两个典型的环境坑,这里一并记录:
- 现象:SD卡读卡器在Windows主机正常识别,但虚拟机可移动设备里找不到,启动VMware USB Arbitration Service时报错“系统找不到指定的文件”。
- 解决:优先以管理员身份运行VMware安装包选择「修复」;修复无效则完整卸载重装,安装时必须以管理员身份运行,避免服务注册不全。
- 现象:连接USB读卡器时提示“无法连接到理想的主机控制器”。
- 解决:关闭虚拟机,在设置中将USB控制器从USB 3.1改为USB 2.0,重启后即可正常连接,大容量存储设备和USB 2.0兼容性更好。
二、ARM启动代码核心原理:从向量表到栈初始化
LED实验的启动代码是整个裸机开发的基础框架,覆盖了ARM架构最核心的异常机制、工作模式、栈管理。
2.1 异常向量表:程序的入口索引
ARM架构规定:发生异常/中断时,CPU会强制跳转到固定内存地址执行。这些固定地址按顺序组成异常向量表,必须放在程序最开头。
.global _start
_start:
ldr pc, =_reset_handler @ 复位异常
ldr pc, =_undef_handler @ 未定义指令异常
ldr pc, =_software_handler @ 软件中断异常
ldr pc, =_prefetch_handler @ 预取中止异常
ldr pc, =_data_handler @ 数据中止异常
nop @ 保留位
ldr pc, =_irq_handler @ IRQ普通中断
ldr pc, =_fiq_handler @ FIQ快速中断
关键细节:
- 向量表统一使用ldr pc, =标号的写法,这是地址加载伪指令,实现32位全地址空间的绝对跳转。相比b相对跳转(±32MB范围限制),没有跳转距离限制,是向量表的标准写法。
- 后面的异常处理函数默认写成死循环b .,属于调试占位写法——如果意外触发异常,程序会停在对应位置,方便定位跑飞原因。
2.2 工作模式与栈:为什么要设两个栈
ARM每个工作模式都有独立的SP栈指针寄存器,所以启动代码必须分别初始化栈。
cps #0x12 @ 切换到IRQ模式
ldr sp, =0x82000000 @ 设置IRQ模式栈指针
cps #0x1F @ 切换到SYS系统模式
ldr sp, =0x84000000 @ 设置主程序栈指针
核心原理:
2.3 中断的先关后开逻辑
启动代码有个经典操作:初始化开始先关中断,初始化完成再开中断。
cpsid i @ 关闭IRQ总中断
cpsid f @ 关闭FIQ总中断
… 初始化流程 …
cpsie i @ 打开IRQ总中断
为什么要这么设计?
- 初始化阶段栈指针、向量表、外设都未就绪,此时触发中断,CPU找不到处理函数、没有栈空间保存现场,直接跑飞死机。
- IRQ是普通外设中断(按键、串口、定时器等),FIQ是高优先级快速中断,对应CPSR寄存器的I位和F位两个总开关。
- 完整中断是三级开关:外设自身使能 → 中断控制器通道使能 → CPU总开关,三级全开才能正常响应中断。
三、SoC引脚配置三步法:点亮LED的硬件逻辑
点亮LED的核心是配置SoC的引脚,全部通过内存映射(MMIO)的外设寄存器完成,也就是直接读写固定地址的寄存器控制硬件。
3.1 第一步:引脚复用配置(IOMUXC)
SoC引脚数量有限,但内部外设众多,IOMUXC的作用就是让同一个物理引脚切换不同功能。
ldr r0, =0x020E0068 @ GPIO1_IO03引脚复用寄存器地址
ldr r1, =0x05
str r1, [r0] @ 配置为ALT5模式,即GPIO功能
- 寄存器地址0x020E0068由芯片手册规定,是IOMUXC基地址加引脚偏移得到,硬件固定不可修改。
- 不配置复用的话,引脚默认是其他外设功能,GPIO信号根本连不到外部引脚上,灯不会亮。
3.2 第二步:电气属性配置(PAD)
配置引脚的驱动强度、上下拉电阻、压摆率等电气参数,匹配硬件电路需求。
ldr r0, =0x020E02F4 @ PAD配置寄存器地址
ldr r1, =0x10B0
str r1, [r0] @ 配置普通驱动强度、上拉电阻、低速压摆率
合适的电气配置可以保证输出电流足够驱动LED,同时避免上电时电平不确定导致误亮。
3.3 第三步:GPIO方向与电平控制
最后配置GPIO为输出模式,通过数据寄存器控制引脚电平。
@ 设置输出方向
ldr r0, =0x0209C004 @ GPIO1方向寄存器
ldr r1, [r0]
orr r1, r1, #(1 << 3) @ 只修改bit3,其他位不变
str r1, [r0]
@ 点亮LED
ldr r0, =0x0209C000 @ GPIO1数据寄存器
ldr r1, [r0]
bic r1, r1, #(1 << 3) @ bit3清0,输出低电平
str r1, [r0]
两个关键细节:
四、交叉编译工具链:四个核心工具的作用
Linaro工具链不只有gcc编译器,还有四个核心组件,各自承担不同功能:
| 编译器 | arm-linux-gnueabihf-gcc | 将C语言/汇编源代码编译成二进制目标文件(.o) |
| 连接器 | arm-linux-gnueabihf-ld | 拼接多个目标文件和库,完成地址重定位,生成elf可执行文件 |
| 格式转换器 | arm-linux-gnueabihf-objcopy | 剥离elf文件的调试信息,转换成纯二进制bin镜像,用于烧录 |
| 反汇编器 | arm-linux-gnueabihf-objdump | 将二进制文件反向还原成汇编代码,用于调试和分析指令 |
五、汇编与C混合调用:AAPCS调用规范
裸机开发经常需要汇编和C混合编程,两者互相调用必须遵循ARM的AAPCS过程调用标准,参数和返回值有固定的传递规则。
5.1 核心规则
- 参数传递:前4个整型/指针参数,依次通过R0、R1、R2、R3传递;第5个及以后的参数通过栈传递。
- 返回值:32位返回值存放在R0寄存器中,64位返回值用R0存低32位、R1存高32位。
- 寄存器保护:R4~R11属于被调用者保护寄存器,函数内部使用前要压栈保存,返回前恢复。
5.2 C调用汇编函数示例
汇编侧导出函数,C侧声明原型即可直接调用:
@ 汇编文件:实现加法函数
.global asm_add
asm_add:
add r0, r0, r1 @ R0存第一个参数,R1存第二个参数,结果写回R0
bx lr
// C文件:声明并调用
int asm_add(int a, int b);
int result = asm_add(3, 5); // 编译器自动将3放入R0、5放入R1,返回值从R0读取
5.3 汇编调用C函数
汇编中直接用bl指令调用,调用前按规则把参数放入对应寄存器即可,返回值从R0读取。
六、完整上电执行流程梳理
最后把整个流程串一遍,建立完整的执行链路:
总结
看似简单的LED闪烁实验,其实覆盖了ARM裸机开发最核心的知识体系:异常向量机制、工作模式与栈管理、内存映射外设操作、交叉编译流程、混合调用规范。
裸机开发的核心本质就是「地址决定一切」:所有外设寄存器都有固定的物理地址,所有硬件操作本质都是对地址的读写。理解了加载存储架构和内存映射,再去学习UART、SPI、定时器等其他外设,都是同样的逻辑,只是寄存器地址和配置位不同而已。
后续会继续分享定时器中断、串口通信等更多裸机开发内容,欢迎一起交流学习。
网硕互联帮助中心



评论前必须登录!
注册