引言
本次点亮LED实验基于NXP的I.MX6ULL开发板,I.MX6ULL使用的是Cortex-A7 架构,通过汇编进行裸机开发。
一、I.MX6ULL GPIO详解
1、初始化IO口分为四个步骤:
(1)使能指定的GPIO的时钟。
(2)初始化GPIO,比如输出功能、上拉、速度等等。
(3)指定IO如需复用,设置IO的复用功能。
(4)设置GPIO输出高电平或低电平。
2、I.MX6ULL IO命名
STM32 中的 IO 都是 PA0-15、PB0-15 这样命名的,I.MX6ULL 的 IO 是怎么命名的呢?如下图可以看出,I.MX6ULL 的IO分为SNVS域和通用的,这两类IO的本质是一样的。同时,这些IO实际上是寄存器,通过配置寄存器可以设置不同属性。
3、I.MX6ULL IO复用
以IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器为例,介绍IO复用配置。
该寄存器地址为0X020E0068,是32位寄存器,但只用到了低5位,其中bit0-bit3(MUX_MODE)设置GPIO1_IO03的复用功能,GPIO1_IO03总共有9个复用功能,分别对应ALT0~ALT8,其中ALT5对应GPIO1_IO03。GPIO_IO03还可作为I2C_SDA、GPT1_COMPARE3等。
4、I.MX6ULL IO配置
以IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03寄存器为例,介绍IO电气属性配置。
该寄存器地址为0X020E02F4,是32位寄存器,但只用到了低17位。
寄存器 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00 的各个位的含义:
(1)HYS(bit16):用来使能迟滞比较器,当 IO 作为输入功能的时候有效,用于设置输入接收器的施密特触发器是否使能。
(2)PUS(bit15:14):用来设置上下拉电阻。
(3)PUE(bit13):当 IO 作为输入的时,设置 IO 使用上下拉还是状态保持器。
(4)PKE(bit12):使能或者禁止上下拉/状态保持器功能。
(5)ODE(bit11):当 IO 作为输出的时候,用来禁止或者使能开路输出。
(6)SPEDD(bit7:6):当 IO 用作输出的时候,用来设置 IO 速度。
(7)DSE(bit5:3):当 IO 用作输出的时候,用来设置 IO 的驱动能力。
(8)SRE(bit0):设置压摆率,当此位为 0 的时候是低压摆率,当为1的时候是高压摆率,这里的压摆率就是 IO 电平跳变所需要的时间。
5、I.MX6ULL GPIO配置
GPIO等于IO吗?当然不是!GPIO只是IO复用功能中的一种,比如GPIO01_IO03这个IO可以作为GPIO01_IO03外,还可以复用为I2C_SDA。LED点亮实验中,除了对IO进行配置外,还需要对GPIO进行配置。
当 IO 用作 GPIO 的时候需要设置的寄存器,一共有八个: DR、GDIR、PSR、ICR1、ICR2、EDGE_SEL、IMR 和 ISR。我们重点介绍下DR和GDIR两种寄存器。
(1)DR 寄存器是数据寄存器,结构图如下图所示:
一个 GPIO 组最大只有32个IO,因此 DR 寄存器中的每个位都对应一个 GPIO。当 GPIO 被配置为输出功能以后,向指定的位写入数据那么相应的 IO 就会输出相应的高低电平,比如要设置 GPIO1_IO03 输出高电平,那么就应该设置 GPIO1.DR=0x01<<3。
(2)GDIR 寄存器,这是方向寄存器,用来设置某个 GPIO 的工作方向的,即输入/输出,GDIR 寄存器结构如下图所示:
GDIR 寄存器也是32位的,此寄存器用来设置某个 IO 的工作方向,是输入还是输出。同样的,每个 IO 对应一个位,如果要设置 GPIO 为输入的话就设置相应的位为0,如果要设置为输出的话就设置为1。比如要设置 GPIO1_IO03为输出,那么GPIO1.GDIR=0x01<<3;
6、I.MX6ULL GPIO时钟使能
I.MX6ULL每个外设的时钟都可以独立的使能或禁止,这样可以关闭掉不使用的外设时钟,起到省电的目的。I.MX6ULL与STM32一样,需要使能GPIO对应的时钟。
CCM_CCGR0 是个 32 位寄存器,其中每 2 位控制一个外设的时钟,比如 bit31:30 控制着 GPIO2 的外设时钟,两个位就有 4 种操作方式,如表 8.1.6.1 所示:
为了方便开发,将 I.MX6U 的所有外设时钟都打开。
7、I.MX6ULL 硬件原理分析
从上图可以看出,LED0接GPIO3,GPIO3就是GPIO1_IO03。当GPIO1_IO03输出高电平时,LED不导通,因此LED不会点亮。当GPIO1_IO03输出低电平时,LED导通,因此LED点亮。
二、I.MX6ULL实验程序编写步骤
(1)使能GPIO1对应的时钟。
(2)设置IO的复用功能,使其复用为GPIO功能。
(3)设置IO的上下拉、速度等等。
(4)设置GPIO1_03为输出模式。
(5)控制GPIO1_03的输出电平。
三、汇编
.global _start /* 全局标号 */
/*
* 描述: _start函数,程序从此函数开始执行此函数完成时钟使能、
* GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
*/
_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
四、编译代码
1、arm-linux-gnueabihf-gcc 编译文件
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
上述命令就是将 led.s 编译为 led.o,其中“-g”选项是产生调试信息,GDB 能够使用这些调试信息进行代码调试。“-c”选项是编译源文件,但是不链接。“-o”选项是指定编译产生的文件名字,这里我们指定 led.s 编译完成以后的文件名字为 led.o。
2、arm-linux-gnueabihf-ld 链接文件
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里我们命名为 led.elf。
3、arm-linux-gnueabihf-objcopy 格式转换
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
上述命令中,“-O”选项指定以什么格式输出,后面的“binary”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息。
4、arm-linux-gnueabihf-objdump 反汇编
arm-linux-gnueabihf-objdump -D led.elf > led.dis
上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为 led.dis 文件。
5、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
创建好 Makefile 以后我们就只需要执行一次“make”命令即可完成编译,如果我们要清理工程的话执行“make clean”即可。
执行“make”命令后,该目录下生成文件如下图所示。
五、代码烧写
如何将我们前面编译出来的 led.bin 烧写到 SD 卡中呢?肯定有人会认为直接复制led.bin 到 SD 卡中不就行了,错!编译出来的可执行文件是怎么存放到 SD 中的,存放的位置是什么?这个 NXP 是有详细规定的!我们必须按照 NXP 的规定来将代码烧写到 SD 卡中,否则,代码是绝对运行不起来的。
imxdownload可以将编译出来的.bin文件烧写到SD卡中。
1、将imxdownload拷贝到工程根目录下
我们要将 imxdownload 拷贝到工程根目录下,也就是和 led.bin 处于同一个文件夹下,要不然烧写会失败的,拷贝完成以后如图 8.4.3.1 所示:
2、给予imxdownload可执行权限
chmod +x imxdownload
通过chmod命令给予imxdownload可执行权限。
3、向SD卡烧写代码
使用 imxdownload 向 SD 卡烧写 led.bin 文件,命令格式如下:
./imxdownload < .bin file > < SD Card >
其中.bin 就是要烧写的.bin 文件,SD Card 就是你要烧写的 SD 卡,比如我的电脑使用如下命令烧写 led.bin 到/dev/sdc 中,插入读卡器后,可以用ls /dev/sd*命令查看。
插入读卡器前后对比,可以看到SD卡路径是/dev/sdc。
./imxdownload led.bin /dev/sdc //不能烧写到/dev/sda 或 sda1 设备里面!那是系统磁盘
烧写完成后,在当前工程目录下生成load.imx文件,正是烧写到SD卡的文件,而不是led.bin。
六、代码验证
代码已经烧写到了 SD 卡中了,接下来就是将 SD 卡插到开发板的 SD 卡槽中,然后设置拨码开关为 SD 卡启动,拨码开关设置如下图所示:
设置好以后按一下开发板的复位键,如果代码运行正常的话 LED0就会被点亮,如下图所示,LED0成功被点亮。