【嵌入式ARM入门】IMX6ULL 汇编启动代码 + LED点灯实战
开发板:正点原子IMX6ULL-mini,Cortex-A7 ARMv7,裸机开发
今日学习:ARM交叉编译工具链完整编译流程、汇编启动代码编写、栈设置、IOMUXC引脚复用、GPIO寄存器配置,最终实现LED点亮
一、学习概述
今天学习IMX6ULL裸机开发,使用ARM汇编编写启动代码,从交叉编译工具链4大编译阶段入手,理解预处理、编译、汇编、链接完整流程;掌握IOMUXC引脚复用原理,配置GPIO寄存器,汇编中设置不同模式栈,编写启动汇编代码,最终编译链接生成bin文件,完成裸机LED点灯。
二、核心知识点
2.1 ARM交叉编译工具链全流程
交叉编译:在x86 PC上编译生成能在ARM平台运行的程序,整套工具链arm-linux-gnueabihf-,分为4个阶段:
| 阶段 | 工具命令 | 输入文件 | 输出文件 | 功能说明 |
|---|---|---|---|---|
| 预处理 | arm-linux-gnueabihf-cpp main.c -o main.i |
main.c | main.i | 处理#include、#define宏、注释,只做文本替换,独立阶段 |
| 编译 | arm-linux-gnueabihf-gcc -S main.i -o main.s |
main.i / start.S | main.s | C语言翻译成ARM汇编代码,独立阶段 |
| 汇编 | arm-linux-gnueabihf-as main.s -o main.o |
main.s / start.S | main.o / start.o | 汇编语言转为目标文件(.o),二进制机器码+段信息 |
| 链接 | arm-linux-gnueabihf-ld main.o start.o -o program.elf |
多个.o目标文件 | led.elf | 合并所有目标文件,按lds链接脚本分配内存地址,生成elf可执行文件 |
工具链辅助组件
arm-linux-gnueabihf-objcopy:elf转bin,objcopy -O binary led.elf led.bin,bin只有纯二进制指令,无elf头信息,裸机烧录用binarm-linux-gnueabihf-objdump:反汇编,objdump -D led.elf > led.dis,查看elf对应的汇编代码,调试分析arm-linux-gnueabihf-gdb:GNU调试器,用于ARM代码单步调试
2.2 IMX6ULL 内存与ARM栈设置
IMX6ULL Cortex-A7,ARMv7架构,ARM有多种运行模式,每种模式拥有独立栈指针SP:
svc(管理模式,裸机常用)、irq中断模式、sys系统模式,不同模式需要单独设置SP栈地址- IRAM内部RAM地址范围:
0x80000000 ~ 0xA0000000(512M) - 栈从高地址向低地址增长:
- SVC栈:
0x82000000 - IRQ栈:
0x87800000
- SVC栈:
裸机汇编启动第一步:切换处理器模式,给每种模式设置SP栈,没有栈,C语言无法运行(C函数需要栈保存局部变量、返回地址)
2.3 IOMUXC 引脚复用(核心重点)
IMX6ULL的PAD引脚是单刀多复用开关,一个物理PAD引脚可以复用为:GPIO、UART_TX/RXD、Timer等多种外设功能。
- IOMUXC寄存器组两大作用:
- 复用功能选择:选择引脚映射到哪个外设(GPIO / UART / Timer)
- 电气特性配置:上下拉、速率、驱动能力、压摆率
- 点灯流程:配置IOMUXC,把PAD引脚复用为GPIO功能 → 配置GPIO方向寄存器(输出) → 配置GPIO数据寄存器,输出高低电平,控制LED亮灭
2.4 汇编启动代码核心逻辑
裸机上电,芯片首先执行汇编启动文件start.S,C语言不能直接运行,需要汇编做初始化:
- 关闭看门狗(防止上电自动复位)
- 切换ARM处理器工作模式,分别设置SVC、IRQ等模式栈指针SP
- 初始化时钟(可选,本例简单点灯可省略)
- 配置IOMUXC引脚复用,设置GPIO为输出
- 操作GPIO数据寄存器输出电平,点亮LED
- 跳转到C语言main函数(如果有C代码)
本次实战:纯汇编实现LED点灯,直接操作寄存器地址,控制引脚电平。
三、完整汇编示例 start.S(IMX6ULL LED)
asm
.global _start
_start:
/* 1. 设置SVC模式栈 */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x13 @ SVC模式
msr cpsr, r0
ldr sp, =0x82000000 @ SVC栈地址
/* 2. 设置IRQ模式栈 */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x12 @ IRQ模式
msr cpsr, r0
ldr sp, =0x87800000 @ IRQ栈地址
/* 3. IOMUXC + GPIO寄存器配置,LED引脚初始化 */
ldr r0, =0x20E0068 @ IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
ldr r1, =0x5 @ 设置复用为GPIO1_IO03
str r1, [r0]
ldr r0, =0x20E0254 @ IOMUXC_PAD 电气属性
ldr r1, =0x10B0
str r1, [r0]
ldr r0, =0x209C000 @ GPIO1方向寄存器,设为输出
ldr r1, =(1<<3)
str r1, [r0]
/* 4. 点亮LED,输出低电平(LED低电平点亮) */
ldr r0, =0x209C004 @ GPIO1数据寄存器
ldr r1, =0
str r1, [r0]
loop:
b loop @ 死循环,防止跑飞
编译命令
bash
# 汇编:start.S 编译成目标文件
arm-linux-gnueabihf-as start.S -o start.o
# 链接:使用ld + 链接脚本,生成elf
arm-linux-gnueabihf-ld start.o -o led.elf -T imx6ull.lds
# 转bin,裸机烧录文件
arm-linux-gnueabihf-objcopy -O binary led.elf led.bin
# 反汇编查看代码
arm-linux-gnueabihf-objdump -D led.elf > led.dis
四、学习重点
- 裸机上电先跑汇编:C语言依赖栈,必须汇编提前设置SP,才能调用C代码。
- 交叉编译四阶段区分:预处理只文本替换;编译生成汇编;汇编生成机器码.o;链接合并段、分配地址。
- IOMUXC是IMX6ULL核心:引脚不是默认GPIO,必须配置复用寄存器,否则引脚不工作。
- ARM不同模式拥有独立栈,SVC模式是裸机开发最常用模式。
- elf和bin区别:elf带段、符号、调试信息;bin只有原始机器指令,是裸机烧写到SD卡的文件。
五、易错点(踩坑总结)
- ❌ 忘记设置栈直接调用C代码:程序直接崩溃,C函数运行必须栈。
- ❌ 混淆IOMUX复用寄存器地址,地址写错,引脚功能不生效,LED不亮。
- ❌ 混淆bin与elf,直接烧录elf文件到SD卡,裸机无法识别,必须objcopy转bin。
- ❌ 栈地址设置错误:栈是从高地址向下生长,SP必须指向合法IRAM区间,不能越界。
- ❌ 电平逻辑搞错:开发板LED是低电平点亮,写1是熄灭,写0点亮,容易写反。
- ❌ 链接时缺少lds链接脚本,内存地址分配错误,程序运行地址错乱。
- ❌ 交叉编译工具选错:用x86 gcc编译,生成x86机器码,ARM芯片无法识别。
六、学习小结
今天完成IMX6ULL裸机汇编启动代码,打通了源码 -> 交叉编译工具链 -> elf -> bin -> 烧录开发板 -> LED点亮完整链路。理解ARMv7处理器模式、栈的作用、SOC引脚复用机制。裸机开发核心就是直接操作寄存器,没有操作系统,所有硬件初始化都需要手动写汇编/ C代码,为后面嵌入式Linux驱动打下基础。