ARM(5)-IMX6ULL 裸机开发入门:从启动到点亮第一盏 LED 灯
一、开发板介绍:IMX6ULL-Mini 的硬件基础
正点原子的 IMX6ULL-Mini 开发板 是入门 ARM Cortex-A7 裸机开发的经典平台,核心硬件与结构如下:
1. 核心板(六层板设计)
- CPU:NXP i.MX6ULL Cortex-A7 单核处理器,工业级主频 528MHz、商业级 800MHz,采用 BGA 封装,平衡性能与功耗。
- 内存:512MB DDR3L RAM,为程序运行提供高速数据存取能力。
- 存储:8GB eMMC,支持 SD 卡、NAND、eMMC 等多启动模式,灵活适配不同开发场景。
- 显示扩展:预留 4.3 寸 800×480 分辨率屏幕接口,便于后续图形界面开发。
2. 底板(以 LED 模块为例)
底板负责外设扩展,其中LED 模块是最基础的 “Hello World” 级外设:
- 用户 LED:1 颗红色 LED 为 “用户可控灯”(后续通过代码控制其亮灭)。
- 电源指示灯:1 颗蓝色 LED 为电源状态指示(上电即常亮)。
- 限流保护:LED 串联 510Ω 限流电阻,防止电流过大烧毁灯珠,体现硬件 “保护性设计”。
二、启动代码:ARM 处理器的 “开机第一步”
裸机开发中,启动代码(Start Code) 是系统上电后执行的第一段程序,负责初始化处理器核心状态(如工作模式、栈、中断)。以下是基于 ARM 汇编的核心逻辑:
1. 异常向量表:处理 ARM 的 8 类核心异常
ARM 处理器规定,不同 “异常”(如复位、中断、指令错误)会跳转到固定内存地址执行处理程序,这些地址组成异常向量表。代码通过 ldr pc, =处理函数
指定各异常的入口:
.global _start__start:; 0x00:复位异常(系统上电/复位时触发,优先级最高)ldr pc, =_start_handler ; 0x04:未定义指令异常(执行处理器不识别的指令时触发)ldr pc, =_undefined_handler; 0x08:管理模式异常(通常由SWI指令触发,用于系统调用)ldr pc, =_supervisor_handler; 0x0C:预取指中止异常(指令读取失败时触发)ldr pc, =_prefetch_handler ; 0x10:数据中止异常(数据读写失败时触发)ldr pc, =_data_abort_handler; 0x14:保留异常(未使用,预留)ldr pc, =_not_use_handler; 0x18:IRQ中断(普通外部中断,如按键)ldr pc, =_irq_handler ; 0x1C:FIQ中断(快速中断,如定时器,响应优先级更高)ldr pc, =_fiq_handler
除复位异常外,其他异常的处理函数暂为 “死循环”(b 自身
)—— 这是最基础的 “防跑飞” 设计,后续可根据需求扩展实际逻辑。
2. 复位处理:初始化处理器状态
_start_handler
是系统上电后第一个执行的函数,负责初始化 CPU 的工作模式、栈、中断状态:
_start_handler:; 关闭IRQ中断:防止初始化过程被外部中断打断cpsid i ; 步骤1:切换到IRQ模式,初始化IRQ栈cps #0x12 ; 0x12是IRQ模式的编码(二进制`10010`)ldr sp, =0x82000000 ; 设置IRQ模式的栈指针(需确保地址在有效内存区间); 步骤2:切换到系统模式,初始化系统栈cps #0x1F ; 0x1F是系统模式的编码(二进制`11111`,特权模式,用于主逻辑)ldr sp, =0x84000000 ; 设置系统模式的栈指针(与IRQ栈地址分离,避免冲突); 打开---再次关闭IRQ中断,确保初始化完成后中断状态cpsid i finish:b finish ; 初始化完成后,进入死循环(后续可扩展应用逻辑)
cps
指令:用于切换 ARM 处理器的工作模式(如 IRQ 模式、系统模式)。ldr sp, =地址
:为当前模式设置栈指针(SP)—— 栈是函数调用、中断处理的 “临时数据容器”,必须提前初始化。
三、点亮 LED:外设寄存器的 “精准控制”
要控制底板上的红色 LED,需通过寄存器配置完成 “引脚复用→电气特性→GPIO 方向→数据输出” 的流程。
前期准备:查阅技术文档
开发外设前需明确硬件连接和寄存器配置,核心参考文档(开发板购买处可提供)
- 《IMX6ULL_MINI_V2.2 (Mini 底板原理图).pdf》:确认 LED 连接的 GPIO 引脚(如 GPIO1_IO03)
- 《IMX6ULL 参考手册.pdf》:查询 GPIO 相关寄存器的地址和配置方式
1. 硬件映射:LED 与 GPIO 的关联
从底板原理图可知:红色用户 LED 连接到 GPIO1_IO03 引脚,因此需配置该引脚的功能与输出状态。
2. 寄存器配置流程
ARM 芯片的外设通过 “内存映射” 的寄存器控制,需按顺序配置以下寄存器(以 GPIO1_IO03 为例):
(1)引脚复用配置:选择 GPIO 功能
i.MX6ULL 的引脚可复用为多种功能(如 GPIO、I2C、UART 等),需通过
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
寄存器选择 “GPIO 模式”。该寄存器的低 4 位决定复用功能,设置为
0101
(二进制)时,引脚被配置为 GPIO 功能。
(2)电气特性配置:优化引脚驱动
通过
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
寄存器配置引脚的电气特性(如上下拉电阻、迟滞、驱动能力等)。需根据 LED 的驱动需求(如电流、电平匹配)调整,确保引脚稳定输出。
(3)GPIO 方向配置:设为输出模式
GPIO 引脚的方向由
GPIOx_GDIR
(GPIO 方向寄存器)控制。对于 GPIO1 组,寄存器为GPIO1_GDIR
。将
GPIO1_GDIR
的第 3 位(对应 GPIO1_IO03)设为1
,表示 “输出模式”(0
为输入模式)。
(4)GPIO 数据输出:控制 LED 亮灭
GPIO 数据由
GPIOx_DR
(GPIO 数据寄存器)控制。对于 GPIO1 组,寄存器为GPIO1_DR
。
- 若硬件为 “低电平点亮”:置
GPIO1_DR
的第 3 位为0
,LED 亮;置为1
,LED 灭。- 若硬件为 “高电平点亮”:逻辑相反(需结合原理图电平逻辑确认)。
3.****** 汇编代码实现 *********
围绕ARM 汇编启动代码(异常向量表、工作模式配置)与LED 驱动逻辑展开,结合 Makefile 工程构建流程,梳理嵌入式 ARM 开发的核心环节,适用于 I.MX6ULL 等 ARM Cortex-A 系列开发板入门实践。
A、核心文件结构
实验包含 2 类关键文件:
start.S
:ARM 汇编源代码(异常向量表、工作模式初始化、LED 控制逻辑)Makefile
:编译、链接、烧录脚本(指定工具链、生成可执行文件、下载到 SD 卡)
B、ARM 汇编代码解析(start.S)
代码分为 4 大模块:异常向量表、工作模式与栈初始化、LED 控制函数、主循环,遵循 ARM Cortex-A 汇编语法(AT&T 或 ARM 语法,本文为 ARM 语法)。
(一)1. 异常向量表(ARM 架构核心)
异常向量表是内存固定地址区域(本文链接地址0x87800000
,对应 I.MX6ULL 启动地址),存储 8 类异常的处理入口,地址偏移固定(每 4 字节 1 个入口,对应 32 位指令)。
地址偏移(相对链接地址) | 异常类型 | 触发场景 | 处理函数 | 功能说明 |
---|---|---|---|---|
0x00 | 复位异常(Reset) | 系统上电 / 复位按钮按下 | _start_handler | 优先级最高,是程序第一个执行的入口 |
0x04 | 未定义指令异常 | 执行处理器不识别的机器码指令 | _undefined_handler | 本文暂用 “忙等” 实现(无实际处理逻辑) |
0x08 | 管理模式异常(SVC) | 执行SWI 指令(用户态→内核态切换) | _supervisor_handler | 暂用 “忙等” 实现 |
0x0C | 预取指中止异常 | 读取指令时地址无效 / 权限不足(如访问 ROM 越界) | _prefetch_handler | 暂用 “忙等” 实现 |
0x10 | 数据中止异常 | 读写数据时地址无效 / 权限不足(如访问 RAM 越界) | _data_abort_handler | 暂用 “忙等” 实现 |
0x14 | 保留异常 | 未定义用途(ARM 架构预留扩展) | _not_use_handler | 暂用 “忙等” 实现 |
0x18 | IRQ 中断 | 普通外部中断(如按键、传感器、串口) | _irq_handler | 暂用 “忙等” 实现(后续可扩展中断服务函数) |
0x1C | FIQ 中断 | 快速中断(如定时器、高速外设) | _fiq_handler | 暂用 “忙等” 实现(响应速度比 IRQ 快) |
关键语法说明:
.global _start
:定义全局符号_start
,作为链接器的入口(告诉链接器程序从这里开始执行)。ldr pc,=label
:将label
的地址加载到程序计数器pc
,实现 “跳转到异常处理函数”(ARM 汇编中pc
指向当前执行指令的下两条,此伪指令可正确加载目标地址)。b label
:无条件分支跳转(“忙等” 逻辑,即异常触发后循环等待,避免程序跑飞)。
(二)2. 系统初始化(_start_handler)
复位异常触发后,进入_start_handler
,完成工作模式切换、栈指针设置、中断关闭(防止初始化被干扰),为后续 LED 控制做准备。
_start_handler:cpsid i // 关闭IRQ中断(禁止外部中断干扰初始化,i=IRQ)cps #0x12 // 切换到IRQ模式(模式码0x12:ARM Cortex-A中IRQ模式的cpsr配置值)ldr sp, =0x82000000 // 设置IRQ模式栈指针(需在I.MX6ULL的RAM范围内,如0x80000000~0x90000000)cps #0x1F // 切换到SYS模式(系统模式,特权模式,用于后续函数调用)ldr sp, =0x84000000 // 设置SYS模式栈指针(与IRQ栈地址不重叠,避免栈冲突)// cpsie i //打开 IRQ 中断(ie=enable IRQ),初始化完成后可按需开启。bl led_init // 调用LED初始化函数(bl=分支并链接,将返回地址存入lr寄存器)b finish // 跳转到主循环(不再返回)
核心知识点:
工作模式码(Cortex-A 系列):
0x12
:IRQ 模式(中断模式,处理外部中断)0x1F
:SYS 模式(系统模式,特权模式,支持访问所有硬件资源,适合主程序运行)- 切换模式用
cps #模式码
(CPSR 控制指令,直接修改程序状态寄存器)。栈指针(sp)设置:
- 不同工作模式需独立栈空间(避免模式切换时栈数据覆盖)。
- 栈地址需在开发板的 RAM 范围内(I.MX6ULL 的 RAM 基地址为
0x80000000
,大小通常为 256MB/512MB)。中断控制:
cpsid i
:关闭 IRQ 中断(id
=disable IRQ)。cpsie i
:打开 IRQ 中断(ie
=enable IRQ),初始化完成后可按需开启。
(三)3. LED 控制函数
通过操作 I.MX6ULL 的GPIO 寄存器,实现 LED 的初始化、点亮、熄灭,核心是 “配置 GPIO 功能→设置 GPIO 方向→操作 GPIO 数据”。
(1)LED 初始化(led_init)
配置 GPIO 的复用功能(将引脚设为 GPIO 模式)、电气特性(上下拉、驱动能力)、方向(输出模式)。
led_init:// 1. IO复用配置:将引脚复用为GPIO(I.MX6ULL的复用寄存器地址0x020E0068)ldr r0, =0x020E0068 // 加载复用寄存器地址到r0ldr r1, =0x05 // 配置值0x05:将对应引脚复用为GPIO(需结合硬件原理图,如LED接GPIO1_IO03)str r1, [r0] // 将r1的值写入r0指向的寄存器(完成复用配置)// 2. 引脚电气特性配置:上下拉、驱动能力、 slew rate(地址0x020E02F4)ldr r0, =0x020E02F4 // 加载电气特性寄存器地址ldr r1, =0x10B0 // 配置值0x10B0:如20K上拉、驱动能力等级、慢 slew rate(需参考芯片手册)str r1, [r0] // 写入配置// 3. GPIO方向配置:设为输出模式(GPIO1方向寄存器地址0x0209C004)ldr r0, =0x0209C004 // 加载GPIO1方向寄存器地址ldr r1, [r0] // 读取当前配置orr r1, r1, #(1 << 3)// 第3位置1:GPIO1_IO03设为输出(1=输出,0=输入)str r1, [r0] // 写入配置bx lr // 返回调用处(bx=分支并交换指令集,lr存返回地址)
(2)LED 点亮(led_on)
操作 GPIO数据寄存器,将对应引脚置低电平(假设 LED 为 “低电平点亮”,需结合硬件原理图)。
led_on:ldr r0, =0x0209C000 // 加载GPIO1数据寄存器地址(数据寄存器控制引脚电平)ldr r1, [r0] // 读取当前电平状态bic r1, r1, #(1 << 3)// 第3位清0:GPIO1_IO03输出低电平(LED点亮)str r1, [r0] // 写入数据寄存器bx lr // 返回
(3)LED 熄灭(led_off)
将 GPIO 对应引脚置高电平,实现 LED 熄灭。
led_off:ldr r0, =0x0209C000 // 加载GPIO1数据寄存器地址ldr r1, [r0] // 读取当前电平状态orr r1, r1, #(1 << 3)// 第3位置1:GPIO1_IO03输出高电平(LED熄灭)str r1, [r0] // 写入数据寄存器bx lr // 返回
(4)延时函数(led_delay)
通过循环计数实现简单延时(软件延时,精度较低,适合入门)。
led_delay:ldr r0, =0x7FFFF // 加载延时计数初值(值越大,延时越长)
loop:sub r0, r0, #1 // r0 = r0 - 1(计数减1)cmp r0, #0 // 比较r0与0(设置cpsr的标志位)bgt loop // 若r0 > 0,跳回loop继续循环(bgt=大于则分支)bx lr // 延时结束,返回
关键寄存器说明(I.MX6ULL):
寄存器功能 | 地址 | 关键配置位 | 作用 |
---|---|---|---|
IO 复用寄存器 | 0x020E0068 | 低 4 位(如 0x05=GPIO 模式) | 决定引脚功能(是 GPIO、UART 还是 SPI) |
电气特性寄存器 | 0x020E02F4 | 上下拉、驱动能力位 | 确保引脚电气性能稳定(如避免电平漂移) |
GPIO 方向寄存器 | 0x0209C004 | 每 1 位对应 1 个引脚(1 = 输出) | 配置 GPIO 为输入 / 输出模式 |
GPIO 数据寄存器 | 0x0209C000 | 每 1 位对应 1 个引脚电平 | 控制引脚输出高 / 低电平 |
(四)4. 主循环(finish)
初始化完成后,进入无限循环,实现 “LED 点亮→延时→熄灭→延时” 的往复逻辑。
finish:bl led_on // 点亮LEDbl led_delay // 延时(保持点亮状态)bl led_off // 熄灭LEDbl led_delay // 延时(保持熄灭状态)b finish // 循环执行
C、Makefile 工程构建
Makefile 用于自动化编译、链接、生成可执行文件(start.bin
),并提供烧录脚本,核心是指定ARM 交叉编译工具链(适用于 PC 编译嵌入式代码)。
(一)1. Makefile 代码解析
C、核心知识点总结
- ARM 异常处理:异常向量表是入口,复位异常是程序启动点,其他异常需后续扩展实际处理逻辑(如 IRQ 中断服务函数)。
- 工作模式与栈:不同模式独立栈空间,SYS 模式适合主程序,IRQ 模式用于中断处理。
- GPIO 控制流程:复用配置→电气特性→方向设置→数据操作,需结合芯片手册和硬件原理图。
- 交叉编译流程:汇编→编译(.o)→链接(.elf)→格式转换(.bin)→烧录,Makefile 自动化管理流程。
****************************************************
四、编译与烧写:让代码在开发板运行
裸机程序需经过 “编译→链接→格式转换→烧写” 流程,才能在开发板上运行。
1. 交叉编译工具链
开发板 CPU 为 ARM 架构,需在 x86 的 Ubuntu 上使用交叉编译工具链 arm-linux-gnueabihf-*
(包含gcc
、ld
、objcopy
等工具)。
2. 编译步骤(以start.S
为例)
(1)汇编:将汇编代码转为目标文件
-c
:只编译不链接。-o start.o
:输出目标文件。-g
:包含调试信息(可选)。
(2)链接:将目标文件链接到指定地址
i.MX6ULL 的启动地址通常为 0x87800000
(需与芯片手册、Bootloader 匹配),因此链接时指定入口地址:
arm-linux-gnueabihf-ld -Ttext 0x87800000 start.o -o start.elf
-Ttext 地址
:指定代码段起始地址。start.elf
:输出可链接的 ELF 格式文件。
(3)格式转换:生成二进制镜像
开发板运行需要纯二进制文件,因此用objcopy
转换格式:
arm-linux-gnueabihf-objcopy -O binary -S -g start.elf start.bin
-O binary
:输出二进制格式。-S
:去除符号表。-g
:去除调试信息。start.bin
:最终可烧写的二进制文件。
(4)反汇编(可选):查看汇编与机器码对应
为调试或学习,可将 ELF 文件反汇编为可读的汇编代码:
arm-linux-gnueabihf-objdump -D start.elf > start.dis
3. 烧写至 SD 卡
i.MX6ULL 支持从 SD 卡启动,需将start.bin
烧写到 SD 卡的特定位置,借助正点原子的imxdownload
工具实现:
(1)连接 SD 卡到 Ubuntu
将 SD 卡插入 USB 读卡器,连接到 Ubuntu 后,通过 ls /dev/sd*
确认 SD 卡设备名(如/dev/sdb
)。
(2)赋予烧写工具权限
将imxdownload
拷贝到工程目录,执行:
chmod +777 imxdownload
(3)烧写二进制文件
./imxdownload start.bin /dev/sdb
- 若烧写速度异常(如上 MB/s),需重新拔插读卡器或重启 Ubuntu,确保 SD 卡被正确识别。
4. 开发板测试
(1)设置启动模式
通过开发板的拨码开关选择 “SD 卡启动”(参考底板原理图的 BOOT 模块说明)。
(2)启动开发板
插入烧写好的 SD 卡,给开发板上电,观察红色 LED 是否按代码逻辑亮灭,验证程序是否运行成功。
5. 命令说明
- 执行
make
:自动编译生成start.bin
。- 执行
make clean
:删除所有编译生成的文件,清理工程。- 执行
make load
:自动调用imxdownload
,将程序烧写到 SD 卡。- 板子需配置启动方式
1、在LED实验中,在对Soc引脚配置时都做了哪些工作?
(1)引脚复用配置:选择 GPIO 功能
操作寄存器:IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
将引脚的低 4 位设置为 0101,将引脚复用为 GPIO 功能
(2)电气特性配置:优化引脚驱动
操作寄存器:IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
设置引脚的电气参数
(3)GPIO 方向配置:设为输出模式
GPIO 引脚的方向由
GPIOx_GDIR
(GPIO 方向寄存器)控制。对于 GPIO1 组,寄存器为GPIO1_GDIR
。将
GPIO1_GDIR
的第 3 位(对应 GPIO1_IO03)设为1
,表示 “输出模式”(0
为输入模式)。
2、什么是编译器、连接器、格式转换器和反汇编器?
1.编译器是将源代码转换为目标代码(机器语言指令)的工具
arm-linux-gnueabihf-gcc -c -g start.S -o start.o
-c:只编译不链接,生成目标文件(start.o)
-g:保留调试信息,方便后续调试
-o start.o:指定输出的目标文件名
2.链接器将多个目标文件(start.o)组合成一个完整的可执行文件(.elf文件),并分配内存地址
arm-linux-gnueabihf-ld -Ttext 0x87800000 start.o -o start.elf
-Ttext 0x87800000:指定程序代码段(text 段)的起始地址(IMX6ULL 开发板的常用加载地址)
-o start.elf:指定输出的可执行文件名
3.格式转换器将一种文件格式(ELF 格式的start.elf)转换为另一种格式(二进制格式的start.bin)
arm-linux-gnueabihf-objcopy -O binary -S -g start.elf start.bin
-O binary:指定输出格式为纯二进制
-S:去除符号表和重定位信息
-g:去除调试信息
4.反汇编器将二进制可执行文件(start.elf)(类似于a.out) 反编译为可读的汇编代码(start.dis)
arm-linux-gnueabihf-objdump -D start.elf > start.dis
-D:对整个文件进行反汇编(包括所有段)
> start.dis:将反汇编结果输出到文本文件