今天我要分享一个基于I.MX6U处理器的裸机汇编LED控制实验。这个实验虽然简单,但涵盖了从汇编编程到编译链接的完整开发流程,特别适合想要学习ARM嵌入式开发的朋友们。通过这个实验,我们可以掌握如何使用汇编语言初始化处理器GPIO并控制LED灯的点亮与熄灭。
这个实验的核心在于理解ARM处理器的GPIO控制原理,以及如何通过汇编指令直接操作寄存器。相比使用C语言,汇编能让我们更贴近硬件,对处理器的运行机制有更深入的认识。实验环境使用的是Ubuntu系统,配合ARM交叉编译工具链,最终生成的可执行文件将烧写到SD卡中运行。
I.MX6U的GPIO控制流程与常见的STM32有些不同,主要分为四个步骤:
时钟使能:通过CCGR0~CCGR6这7个寄存器控制所有外设时钟。实验中我们简单地将它们全部设置为0xFFFFFFFF,使能所有外设时钟。
IO复用配置:I.MX6U的每个引脚都可以复用为多种功能。我们需要将GPIO1_IO03配置为GPIO模式,通过设置IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器的bit3~0为0101(5)来实现。
电气属性设置:通过IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03寄存器配置引脚的电气特性,包括压摆率、驱动能力、上下拉等。
GPIO功能配置:最后通过GPIO1_DR和GPIO1_GDIR寄存器设置GPIO方向和输出电平。
在嵌入式开发中,理解"存储地址"和"运行地址"的区别至关重要:
对于I.MX6U来说,内部boot rom程序会从SD卡读取可执行文件并拷贝到运行地址处执行。本实验将运行地址设置为0x87800000,这是DDR内存中的一个位置,也是后续Uboot运行的地址。
完整的汇编代码分为几个关键部分:
assembly复制.global _start /* 全局标号 */
_start:
/* 1. 使能所有时钟 */
ldr r0, =0X020C4068 /* CCGR0 */
ldr r1, =0XFFFFFFFF
str r1, [r0]
/* 2. 设置GPIO1_IO03复用为GPIO */
ldr r0, =0X020E0068
ldr r1, =0X5
str r1,[r0]
/* 3. 配置GPIO1_IO03的IO属性 */
ldr r0, =0X020E02F4
ldr r1, =0X10B0
str r1,[r0]
/* 4. 设置GPIO1_IO03为输出 */
ldr r0, =0X0209C004
ldr r1, =0X0000008
str r1,[r0]
/* 5. 打开LED0(输出低电平) */
ldr r0, =0X0209C000
ldr r1, =0
str r1,[r0]
loop:
b loop
汇编代码主要使用了三种指令:
LDR:将数据从内存加载到寄存器。例如ldr r0, =0X020C4068将地址0x020C4068加载到r0寄存器。
STR:将寄存器数据存储到内存。例如str r1, [r0]将r1的值存储到r0指向的内存地址。
B:分支指令,用于实现循环。b loop使程序无限循环在loop标签处。
提示:在ARM汇编中,LDR和STR是最常用的内存访问指令。LDR还可以用来加载32位立即数,因为MOV指令只能加载有限的立即数范围。
Makefile是编译过程的核心,它定义了从源代码到最终可执行文件的转换规则:
makefile复制led.bin: led.s
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis
clean:
rm -rf *.o led.bin led.elf led.dis
arm-linux-gnueabihf-gcc:
-g:生成调试信息-c:只编译不链接-o:指定输出文件名arm-linux-gnueabihf-ld:
-Ttext:设置代码段起始地址arm-linux-gnueabihf-objcopy:
-O binary:输出纯二进制格式-S:移除符号和重定位信息arm-linux-gnueabihf-objdump:
-D:反汇编所有段注意:交叉编译工具链的前缀"arm-linux-gnueabihf-"表示这是针对ARM架构的Linux工具链,使用硬浮点(hard-float)ABI。
编译汇编文件:
bash复制arm-linux-gnueabihf-gcc -g -c led.s -o led.o
这一步将汇编代码编译为目标文件,生成led.o。
链接目标文件:
bash复制arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
指定代码段起始地址为0x87800000,生成elf格式的可执行文件。
生成二进制文件:
bash复制arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
从elf文件提取纯二进制代码,准备烧写。
生成反汇编文件:
bash复制arm-linux-gnueabihf-objdump -D led.elf > led.dis
这个文件对于调试和分析非常有用。
I.MX6U的bin文件不能直接烧写到SD卡,需要添加一个头部信息,包含DDR初始化参数。我们使用imxdownload工具来完成这个工作:
bash复制chmod 777 imxdownload # 添加执行权限
./imxdownload led.bin /dev/sdf # 烧写到SD卡
imxdownload会生成一个load.imx文件,这才是实际烧写到SD卡的内容。
实操技巧:在Ubuntu下可以使用
lsblk命令查看SD卡对应的设备节点,通常是/dev/sdX(X为b,c,d等)。
工具链路径问题:
/opt/gcc-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc语法错误:
/* */或@符号链接地址错误:
LED不亮:
程序不运行:
反汇编分析:
bash复制arm-linux-gnueabihf-objdump -D led.elf > led.dis
通过反汇编文件可以确认代码是否正确链接到指定地址。
代码优化:
编译选项:
-O2启动速度:
虽然本实验使用汇编,但实际开发中通常会尽快切换到C语言环境。要实现这一点,需要:
可以扩展实现以下功能:
理解I.MX6U的完整启动流程:
在实际开发中,我发现在编写裸机程序时,良好的代码组织和详尽的注释特别重要。因为缺乏操作系统提供的调试工具,我们需要更多地依赖硬件调试器和日志输出。另外,理解处理器手册中的寄存器描述是关键,这需要耐心和细致的阅读。