ARM 裸机开发学习知识体系
第一阶段:环境搭建与工具链认知
- 安装 gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf。
- 配置环境变量(修改 .bashrc 文件)。
- 验证安装(arm-linux-gnueabihf-gcc -v)。
- 在 Ubuntu 上配置 FTP 服务 (vsftpd)。
- 使用 Windows 下的 FileZilla Client 工具上传代码到 Ubuntu 开发机。
- 使用 imxdownload 工具将生成的 .bin 文件烧录到 SD 卡 (/dev/sdb)。
- 了解 i.MX6ULL-Mini 开发板的多种启动模式(SD卡、eMMC、NAND Flash、USB)。
- 通过拨码开关选择从 SD 卡启动。
- 编译器 (arm-linux-gnueabihf-gcc): 将源代码(.S, .c)编译成目标文件(.o)。
- 链接器 (arm-linux-gnueabihf-ld): 将一个或多个目标文件链接成可执行文件(.elf),并根据链接脚本分配地址。
- 格式转换器 (arm-linux-gnueabihf-objcopy): 将 .elf 文件转换为纯二进制格式(.bin),用于烧录。
- 反汇编器 (arm-linux-gnueabihf-objdump): 将 .elf 文件反汇编成人类可读的汇编代码,用于调试和分析。
- 理解 Makefile 的三要素:目标、依赖、规则。
- 编写规则来自动化执行编译、链接、格式转换、反汇编等步骤。
- 使用变量(如 CC, LD)简化和优化 Makefile。
- 定义伪目标(如 clean, load)来执行清理和烧录操作。
第二阶段:ARM 汇编语言基础
- AREA: 定义代码段或数据段。
- CODE32 / THUMB: 指定指令集(32位 ARM 或 16位 Thumb)。
- ENTRY: 定义程序入口点。
- END: 标记汇编源文件结束。
- .global _start: 声明全局符号 _start,作为程序入口。
- MOV: 数据传送指令。理解立即数(12位立即数的判断标准)、寄存器寻址、移位操作(LSL, LSR, ASR, ROR, RRX)。
- ADD/SUB: 加法/减法指令。
- MVN: 按位取反传送指令。
- BIC: 位清零指令(AND NOT)。
- ORR: 逻辑或指令(用于置位)。
- LDR: 从内存加载数据到寄存器。掌握多种寻址方式(立即数偏移、寄存器偏移、前/后索引等)。
- STR: 将寄存器数据存储到内存。
- CPSR 标志位:
- N (Negative): 结果为负。
- Z (Zero): 结果为零。
- C (Carry): 无符号运算产生进位或借位。
- V (oVerflow): 有符号运算溢出。
- CMP: 比较指令(实质是做减法但不保存结果,只更新 CPSR 标志位)。
- 利用条件码(如 EQ, NE, GT, LT 等)实现条件执行。
- B: 无条件或条件分支跳转。
- BL: 带返回的分支跳转(将下一条指令地址存入 LR)。
- BX: 间接跳转,并可切换指令集状态(ARM/Thumb)。
- 7 种基本模式: User, FIQ, IRQ, Supervisor (SVC), Abort, Undef, System。
- Cortex-A 特有模式: Monitor (安全扩展)。
- 模式切换: 通过修改 CPSR 寄存器的 M[4:0] 位域。
- MRS: 读取 CPSR 到通用寄存器。
- MSR: 将通用寄存器的值写入 CPSR。
- CPS 指令: 快速开关中断和切换模式(如 cpsid i, cpsie i, cps #mode)。
- 处理器复位后从固定地址(通常是 0x0)开始执行。
- 异常向量表包含各种异常(复位、未定义指令、SWI、预取中止、数据中止、IRQ、FIQ)的入口地址。
- 软中断 (SWI): 用于在用户模式下请求内核服务(系统调用)。
第三阶段:函数调用与栈管理
- PC 与 LR: BL 指令将返回地址存入 LR (R14),函数通过 BX LR 或 MOV PC, LR 返回。
- 寄存器传参: ARM AAPCS 规约规定,前 4 个参数通过 R0-R3 传递,返回值通过 R0 返回。
- 满栈 (Full Stack): 栈指针指向最后一个有效数据项。
- 空栈 (Empty Stack): 栈指针指向下一个要放入数据的位置。
- 增栈 (Ascending): 栈向高地址增长。
- 减栈 (Descending): 栈向低地址增长。
- ARM 采用: 满减栈 (Full Descending Stack)。
- 使用 LDR SP, =0x40001000 初始化栈顶指针(SP, R13)。
- STMFD (Store Multiple Full Descending): STMFD SP!, {寄存器列表}。将寄存器列表中的值压入栈,并更新 SP。
- LDMFD (Load Multiple Full Descending): LDMFD SP!, {寄存器列表}。从栈中弹出值到寄存器列表,并更新 SP。
- 主调函数职责: 在调用子函数前后,负责保存和恢复自己需要使用的寄存器(包括 LR)。
- PRES8 伪指令: 确保栈指针在函数调用时保持 8 字节对齐,以满足某些 ABI 要求,避免链接错误。
- 汇编调用 C 函数:
- 在汇编中使用 IMPORT c_func_name 声明。
- 按规约准备参数(R0-R3)。
- 使用 BL c_func_name 调用。
- 从 R0 获取返回值。
第四阶段:启动代码与 C 语言运行环境搭建
- 初始化异常向量表。
- 为各处理器模式(SVC, IRQ, FIQ, ABT, UND)设置独立的栈指针。
- 初始化 .bss 和 COMMON 段(清零)。
- (可选)关闭看门狗、初始化时钟、DDR 等。
- 设置处理器为 User 或 SVC 模式。
- 跳转到 C 语言的 main() 函数。
- 作用: 为链接器提供内存布局蓝图,精确控制各段(.text, .rodata, .data, .bss)在内存中的位置。
- 关键语法:
- SECTIONS { … }: 定义输出段。
- . = 0x87800000;: 设置当前链接地址(Location Counter)。
- *(.text): 收集所有输入文件的 .text 段。
- *(.rodata*): 使用通配符收集所有以 .rodata 开头的段(如 .rodata.str1.1)。
- __bss_start = .; … __bss_end = .;: 定义符号,标记 .bss 段的起始和结束地址,供启动代码用于清零。
- .text: 代码段,存放程序的机器指令。
- .rodata: 只读数据段。存放字符串常量、const 全局变量、跳转表等。运行时不可写。
- .data: 已初始化数据段。存放初始化为非零值的全局/静态变量。程序镜像中包含其初始值。
- .bss: 未初始化数据段。存放初始化为0或未显式初始化的静态/全局变量。程序镜像中不占空间,运行时由启动代码清零。
- COMMON: 未初始化的非静态全局变量。在链接时才确定大小和位置,最终通常与 .bss 合并。
第五阶段:i.MX6ULL 裸机外设驱动开发
- volatile 关键字: 告诉编译器不要优化对该变量的访问,因为其值可能被硬件异步修改。
- 直接地址映射: #define GPIO1_DR *((volatile unsigned int *)0x0209C000)。
- 结构体映射: 将外设寄存器组定义为结构体,提高代码可读性和可维护性。c
编辑
struct GPIO_t {
unsigned int DR;
unsigned int GDIR;
// … other registers
};
#define GPIO1 (*((struct GPIO_t *)0x0209C000))
- 主要利用 NXP 提供的头文件(如 MCIMX6Y2.h, fsl_common.h, fsl_iomuxc.h)。
- 使用 SDK 封装好的 API(如 IOMUXC_SetPinMux, IOMUXC_SetPinConfig)进行引脚复用和电气属性配置。
- 时钟使能: 配置 CCM (Clock Control Module) 寄存器,打开对应外设的时钟门控。
- 引脚复用 (IOMUX): 配置 IOMUXC_SW_MUX_CTL_PAD_xxx 寄存器,将引脚功能设置为 GPIO。
- 电气属性配置: 配置 IOMUXC_SW_PAD_CTL_PAD_xxx 寄存器,设置驱动能力、上下拉等。
- GPIO 方向设置: 配置 GPIOx_GDIR 寄存器,设置为输出模式。
- GPIO 数据操作: 通过 GPIOx_DR 寄存器进行置位(开灯)、清零(关灯)或异或(翻转)操作。
- 分层架构:
- project/: 存放 main.c, start.S 等主程序文件。
- imx6ull/: 存放 SoC 相关的官方头文件。
- bsp/: 存放板级外设驱动模块(如 led.c/h, beep.c/h)。
- 优点: 模块化、可重用、易于维护。
- 原理:通过 GPIO 控制 PNP 三极管(如 S8550)的基极,从而控制蜂鸣器的通断。高电平导通,蜂鸣器响。

