嵌入式Linux用汇编来完成LED点灯实验(正点原子I.MX6ULLMINI开发板)

I.MX6ULL IO初始化

  1. 使能时钟,CCGR0-CCGR6这七个寄存器控制这I.MX6ULL所有外设时钟的使能,为了简单,设置CCGR0-CCGR6这七个寄存器全部位0xFFFFFFFF,相当于使能所有外设时钟。
  2. IO复用,将寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的bit3~0设置为0101=5,这样GPIO1_IO03就复用为GPIO。
  3. 寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03是设置GPIO1_IO03的电气属性。包括压摆率、速度、驱动能力、开漏、上下拉等。
  4. 配置GPIO功能,设置输入输出。设置GPIO1_DR寄存器bit3为1,也就是设置为输出模式。设置GPIO1_DR寄存器的bit3,为1表示输出高电平,为0表示输出低电平。

为什么是GPIO1_IO03而不是IO04,05、06呢,请看开发板原理图

编译程序

  1. 使用arm-linux-gnueabihf-gcc,将.c.s文件变为.o
  2. 将所有的.o文件链接为elf格式的可执行文件
  3. 将elf文件转为bin文件
  4. 将elf文件转为汇编,反汇编

链接

链接就是将所有.o文件链接在一起,并且链接到指定的地方。本实验链接的时候要指定链接起始地址。链接起始地址就是代码运行的起始地址。

对于6ULL来说,链接起始地址应该指向RAM地址。RAM分为内部RAM和外部RAM,也就是 DDR。6ULL内部RAM地址范围0X900000~0X91FFFF。也可以放到外部DDR中,对于I.MX6U-ALPHA开发板,512MB字节DDR版本的核心板,DDR范围就是0X80000000~0X9FFFFFFF。对于256MB的DDR来说,那就是0X80000000~0X8FFFFFFF。

本系列视频,裸机代码的链接起始地址为0X87800000。要使用DDR,那么必须要初始化DDR,对于I.MX来说bin文件不能直接运行,需要添加一个头部,这个头部信息包含了DDR的初始化参数,I.MX系列SOC内部boot rom会从SD卡,EMMC等外置存储中读取头部信息,然后初始化DDR,并且将bin文件拷贝到指定的地方。

Bin的运行地址一定要和链接起始地址一致。位置无关代码除外。

烧写Bin文件

STM32烧写到内部FLASH。

6ULL支持SD卡、EMMC、NAND、nor、SPI flash等等启动。裸机例程选择烧写到SD卡里面。

在ubuntu下向SD卡烧写裸机bin文件。烧写不是将bin文件拷贝到SD卡中,而是将bin文件烧写到SD卡绝对地址上。而且对于I.MX6ULL而言,不能直接烧写bin文件,比如先在bin文件前面添加头部。完成这个工作,需要使用正点原子提供的imxdownload软件。

Imxdownload使用方法,确定要烧写的SD卡文件,我的是/dev/sdb。

给予imxdownload可执行权限:Chmod 777 imxdownload

烧写:./imxdownload led.bin /dev/sdf

Imxdownlaod会向led.bin添加一个头部,生成新的load.imx文件,这个load.imx文件就是最终烧写到SD卡里面去的。

代码分析

使能时钟

使能时钟,CCGR0-CCGR6这七个寄存器控制这I.MX6ULL所有外设时钟的使能,为了简单,设置CCGR0-CCGR6这七个寄存器全部位0xFFFFFFFF,相当于使能所有外设时钟。

ldr r0,=0x020C4068 @寄存器CCGR0的地址
ldr r1,=0xFFFFFFFF @赋予r1=0xFFFFFFFF
str r1,[r0] @将r1的值写入r0地址,也就是0x020C4068中地址存放的值为0xFFFFFFFF

加载地址和加载值的区别
加载地址:
ldr r0, =0x020C4068:这条指令的意思是将一个立即数(在这里是地址 0x020C4068)加载到寄存器 r0 中。在这种情况下,汇编器会将立即数 0x020C4068 解释为一个地址,并将这个地址的值加载到寄存器 r0 中。
加载值:
ldr r1, =0xFFFFFFFF:这条指令的意思是将立即数 0xFFFFFFFF 加载到寄存器 r1 中。在这种情况下,汇编器会将 0xFFFFFFFF 解释为一个立即数值,并将其加载到寄存器 r1 中。
为什么第一个是地址,第二个是值?
这是因为 ldr 指令在加载一个立即数时,汇编器会处理这个立即数并将其放到一个寄存器中。如果立即数表示的是一个内存地址,加载操作会将这个地址存储在寄存器中;如果立即数表示的是一个数值,加载操作会将这个数值存储在寄存器中。

示例:
ldr r0, =0x020C4068:
这里,0x020C4068 是一个内存地址。指令将这个地址本身存储到 r0 中。此时,r0 中的内容就是 0x020C4068。
ldr r1, =0xFFFFFFFF:
这里,0xFFFFFFFF 是一个数值。指令将这个数值存储到 r1 中。此时,r1 中的内容就是 0xFFFFFFFF。
总结
ldr r0, =0x020C4068 加载的是地址 0x020C4068,将这个地址存储到 r0 中。
ldr r1, =0xFFFFFFFF 加载的是数值 0xFFFFFFFF,将这个数值存储到 r1 中。

	ldr r0, =0X020C406C  	// 寄存器CCGR1的地址,以此类推,后面是CCGR2-CCGR6
	str r1, [r0] 

	ldr r0, =0X020C4070  	// CCGR2 
	str r1, [r0]
	
	ldr r0, =0X020C4074  	// CCGR3 
	str r1, [r0]
	
	ldr r0, =0X020C4078  	// CCGR4 
	str r1, [r0]
	
	ldr r0, =0X020C407C  	// CCGR5 
	str r1, [r0]
	
	ldr r0, =0X020C4080  	// CCGR6 
	str r1, [r0]

IO复用

IO复用,将寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的bit3~0设置为0101=5,这样GPIO1_IO03就复用为GPIO。

	//设置GPIO1_IO03复用为GPIO1_IO03 
	ldr r0, =0X020E0068		//将寄存器SW_MUX_GPIO1_IO03_BASE加载到r0中 
	ldr r1, =0X5			// 设置寄存器SW_MUX_GPIO1_IO03_BASE的MUX_MODE为5 
	str r1,[r0]

配置电气属性

寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03是设置GPIO1_IO03的电气属性。包括压摆率、速度、驱动能力、开漏、上下拉等。

    ldr r0, =0X020E02F4	//寄存器SW_PAD_GPIO1_IO03_BASE
    ldr r1, =0X10B0
    str r1,[r0]

0x10B0来源如下:

配置 GPIO1_IO03 的 IO 属性
bit 16:0 HYS 关闭
bit [15:14]: 00 默认下拉
bit [13]: 0 kepper 功能
bit [12]: 1 pull/keeper 使能
bit [11]: 0 关闭开路输出
bit [7:6]: 10 速度 100Mhz
bit [5:3]: 110 R0/6 驱动能力
bit [0]: 0 低转换率

配置GPIO功能,设置输入输出

配置GPIO功能,设置输入输出。设置GPIO1_DR寄存器bit3为1,也就是设置为输出模式。设置GPIO1_DR寄存器的bit3,为1表示输出高电平,为0表示输出低电平。

	//设置GPIO1_IO03为输出 
    ldr r0, =0X0209C004	//寄存器GPIO1_GDIR 
    ldr r1, =0X0000008		
    str r1,[r0]
	//打开LED0
	//设置GPIO1_IO03输出低电平
   ldr r0, =0X0209C000	//寄存器GPIO1_DR 
   ldr r1, =0		
   str r1,[r0]

	//loop死循环
loop:
	b loop 	

完整代码

.global _start  /* 全局标号 */


//GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
_start://_start函数,程序从此函数开始执行此函数完成时钟使能、
	//1、使能所有时钟
	ldr r0, =0X020C4068 	// CCGR0 
	ldr r1, =0XFFFFFFFF  
	str r1, [r0]		
	
	ldr r0, =0X020C406C  	// CCGR1
	str r1, [r0]

	ldr r0, =0X020C4070  	// CCGR2 
	str r1, [r0]
	
	ldr r0, =0X020C4074  	// CCGR3 
	str r1, [r0]
	
	ldr r0, =0X020C4078  	// CCGR4 
	str r1, [r0]
	
	ldr r0, =0X020C407C  	// CCGR5 
	str r1, [r0]
	
	ldr r0, =0X020C4080  	// CCGR6 
	str r1, [r0]
	

	//2、设置GPIO1_IO03复用为GPIO1_IO03 
	ldr r0, =0X020E0068		//将寄存器SW_MUX_GPIO1_IO03_BASE加载到r0中 
	ldr r1, =0X5			// 设置寄存器SW_MUX_GPIO1_IO03_BASE的MUX_MODE为5 
	str r1,[r0]

	//3、配置GPIO1_IO03的IO属性	
	//bit 16:0 HYS关闭
	//bit [15:14]: 00 默认下拉
    //bit [13]: 0 kepper功能
    //bit [12]: 1 pull/keeper使能
    //bit [11]: 0 关闭开路输出
    //bit [7:6]: 10 速度100Mhz
    //bit [5:3]: 110 R0/6驱动能力
    //bit [0]: 0 低转换率
    
    ldr r0, =0X020E02F4	//寄存器SW_PAD_GPIO1_IO03_BASE
    ldr r1, =0X10B0
    str r1,[r0]

	//4、设置GPIO1_IO03为输出 
    ldr r0, =0X0209C004	//寄存器GPIO1_GDIR 
    ldr r1, =0X0000008		
    str r1,[r0]

	//5、打开LED0
	//设置GPIO1_IO03输出低电平
	
   ldr r0, =0X0209C000	//寄存器GPIO1_DR 
   ldr r1, =0		
   str r1,[r0]

	//loop死循环
loop:
	b loop 				

Makefile

led.bin:leds.s//依赖于leds.s产生led.bin
	arm-linux-gnueabihf-gcc -g -c leds.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 leds.s -o led.o

详细解释:

arm-linux-gnueabihf-gcc

这是交叉编译器的名称,用于将代码编译为在 ARM 架构上运行的可执行文件。

arm:指代目标处理器架构,这里是 ARM 架构。
linux:指代目标操作系统,这里是 Linux。
gnueabihf:这是编译工具链的 ABI(应用二进制接口)标识。
gnu:指的是 GNU 工具链。
eabi:指的是嵌入式应用二进制接口(Embedded Application Binary Interface)。
hf:指的是硬浮点运算(hard float),表示编译器生成的代码将使用硬件浮点单元(FPU)进行浮点运算。
gcc
GNU 编译器集合(GNU Compiler Collection),用于编译 C、C++ 以及其他语言的源代码。

-g
编译选项,用于生成调试信息。这些调试信息可以在调试器(如 GDB)中使用,以便于调试程序。

-c
编译选项,指示编译器只进行编译,不进行链接。编译后生成目标文件(.o 文件)。

leds.s
输入文件,包含汇编语言代码。

-o led.o
输出选项,用于指定编译生成的目标文件的名称,这里是 led.o。

整体解释
这条命令使用交叉编译器 arm-linux-gnueabihf-gcc,将汇编文件 leds.s 编译为目标文件 led.o,并生成包含调试信息的文件。

arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf

arm-linux-gnueabihf-ld
这是用于 ARM 架构的 GNU 链接器,负责将目标文件(.o 文件)链接成可执行文件或其他目标格式文件。

-Ttext 0x87800000
这是链接选项,指定了可执行文件的代码段(text segment)的起始地址。

-Ttext:链接选项,用于指定代码段的起始地址。
0x87800000:指定的起始地址,表示代码段将从这个内存地址开始。
led.o
这是输入文件,包含已经编译好的目标文件(由汇编或 C 代码编译生成的 .o 文件)。

-o led.elf
这是输出选项,用于指定链接生成的可执行文件的名称,这里是 led.elf。

整体解释
这条命令使用 GNU 链接器 arm-linux-gnueabihf-ld,将目标文件 led.o 链接成一个可执行文件 led.elf,并将可执行文件的代码段起始地址设置为 0x87800000。

arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin

arm-linux-gnueabihf-objcopy
这是用于对象文件处理的工具,它可以在不同的对象文件格式之间进行转换。

-O binary
这是输出格式选项,用于指定输出文件的格式。

-O:指定输出文件格式。
binary:表示输出文件格式为纯二进制格式,即不包含任何格式信息或调试信息的裸二进制文件。
-S
这是一个选项,用于删除所有的符号表和重定位信息。它会使输出文件更加简洁,仅保留必要的指令和数据。

-g
这是一个选项,用于去除调试信息。如果目标文件中包含调试信息,使用这个选项可以将这些信息去掉,从而减小文件大小。

led.elf
这是输入文件,包含已经链接好的可执行文件(由目标文件链接生成的 .elf 文件)。

led.bin
这是输出文件,指定转换后的二进制文件的名称,这里是 led.bin。

整体解释
这条命令使用对象文件处理工具 arm-linux-gnueabihf-objcopy,将可执行文件 led.elf 转换成纯二进制文件 led.bin,并删除符号表、重定位信息和调试信息。

arm-linux-gnueabihf-objdump -D led.elf > led.dis

arm-linux-gnueabihf-objdump
这是用于显示对象文件信息的工具。它可以显示目标文件的各种信息,包括反汇编代码。

-D
这是一个选项,用于反汇编所有的代码段。它会将目标文件中的机器代码转换成人类可读的汇编语言指令。

led.elf
这是输入文件,包含已经链接好的可执行文件(由目标文件链接生成的 .elf 文件)。

> led.dis
这是输出重定向符,用于将命令的输出结果重定向到指定文件。

整体解释
这条命令使用对象文件信息显示工具 arm-linux-gnueabihf-objdump,将可执行文件 led.elf 中的机器代码反汇编成汇编语言指令,并将结果保存到文件 led.dis 中。

rm -rf *.o led.bin led.elf led.dis

rm
这是用于删除文件或目录的命令。

-rf
这是两个选项,分别是:

-r:递归删除目录及其内容。虽然在这条命令中并未指定目录,但这是一个常见的选项,用于确保删除所有指定的内容。
-f:强制删除文件,不提示确认。即使文件只读或不存在,也不会报错或提示。
*.o
这是一个通配符,表示删除当前目录下所有扩展名为 .o 的文件。这些文件是编译生成的目标文件。

led.bin
这是一个具体的文件名,表示删除名为 led.bin 的文件。这个文件是由 objcopy 工具生成的二进制文件。

led.elf
这是一个具体的文件名,表示删除名为 led.elf 的文件。这个文件是由 ld 工具生成的可执行文件。

led.dis
这是一个具体的文件名,表示删除名为 led.dis 的文件。这个文件是由 objdump 工具生成的反汇编文件。

整体解释
这条命令使用 rm 工具递归且强制地删除当前目录下所有的 .o 文件,以及 led.bin、led.elf 和 led.dis 文件。

你可以将上述内容保存为项目目录下的 Makefile,然后使用 make 命令编译你的代码,使用 make clean 命令清除生成的文件。这样就不用自己一条一条指令在控制台输入了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

rrrrr木生于

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值