I.MX6ULL IO初始化
- 使能时钟,CCGR0-CCGR6这七个寄存器控制这I.MX6ULL所有外设时钟的使能,为了简单,设置CCGR0-CCGR6这七个寄存器全部位0xFFFFFFFF,相当于使能所有外设时钟。
- IO复用,将寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的bit3~0设置为0101=5,这样GPIO1_IO03就复用为GPIO。
- 寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03是设置GPIO1_IO03的电气属性。包括压摆率、速度、驱动能力、开漏、上下拉等。
- 配置GPIO功能,设置输入输出。设置GPIO1_DR寄存器bit3为1,也就是设置为输出模式。设置GPIO1_DR寄存器的bit3,为1表示输出高电平,为0表示输出低电平。
为什么是GPIO1_IO03而不是IO04,05、06呢,请看开发板原理图
编译程序
- 使用arm-linux-gnueabihf-gcc,将.c.s文件变为.o
- 将所有的.o文件链接为elf格式的可执行文件
- 将elf文件转为bin文件
- 将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 命令清除生成的文件。这样就不用自己一条一条指令在控制台输入了。