目录
实验准备
硬件原理图
我使用的是正点原子的开发板,原理图如下:
LED0原理图中,可以看出,LED0接在了开发板的GPIO_3上,GPIO_3这个IO输出低电平的时候,LED0就会导通点亮,GPIO_3输出高电平的时候,LED0不导通也就不亮。
IO配置
GPIO_3这个IO对应着im6ull参考手册里的GPIO1_IO03,有经验的朋友就会知道,我们需要配置IO的基本属性,比如做输入还是输出,是否需要设置上拉/下拉等。按照手册来配置我们的IO吧~。
这款开发板的IO配置主要涉及到两个寄存器,一个寄存器配置该IO被复用做哪个外设,是普通的GPIO还是其它功能,通常格式为SW_MUX_CTL_PAD* ;另一个寄存器配置IO 的具体属性,比如上下拉,速度等,通常格式为SW_PAD_CTL_PAD*,如下图所示:

左上角方框里还有8个需要设置的寄存器,分别是:
DR(数据寄存器)
GPIO做输出时,向指定的位写入数据,那么相应的IO就会输出对应的高低点平。GPIO做输入时,该寄存器就保存着相应IO的电平值。

GDIR(方向寄存器)
用来设置GPIO做输入还是输出。将对应的位置0,做输入;置1,做输出。

PSR(状态寄存器)
获取对应的GPIO的状态,也就是高低电平值。

ICR1,ICR2(中断控制寄存器)
ICR1配置低16个IO,ICR2配置高16个IO,用来配置中断的出发方式。比如高/低电平触发,上升沿/下降沿触发。

IMR(中断屏蔽寄存器)
用来控制GPIO的中断禁止和使能,使能则将对应的位置1。

ISR(中断状态寄存器)
某个GPIO的中断发生,对应位就会被置1,当我们处理完中断后,将对应位写1清零,也就是清除中断标志位。该寄存器常用来判断某个GPIO的中断是否产生。

EDGL_SEL(边沿选择寄存器)
用来设置边沿中断,会覆盖ICR1和ICR2的设置,将GPIO对应位置1,则表示双边延触发中断。

关于该款开发板的GPIO配置知识就是如上这些,根据这些知识,小伙伴们可以开始配置自己的GPIO啦。
时钟配置
每个外设工作起来都需要配置相应的时钟,GPIO也不例外。那么怎么使能我们的GPIO_3对应的时钟呢?
先来看看这款开发板的时钟树总览吧,图画的很好,这手艺不去织毛线真是可惜了。


对于初学者来说,要想一眼看破天机确实很困难,所以本讲不细说,且听下回分解。
查看imx6ull的参考手册,我们可以查找到外设时钟使能相关的寄存器,一共有7个,如下图:

我们点开CCM_CCGR1寄存器查看详情,可以看见该寄存器每2位控制一个外设时钟,我们需要的GPIO1_03时钟配置就属于bit27-26.

2个bit位就可以配置4种状态,一般情况下将这2位bit置为11,原文和翻译 如下图讲解:


总结一下,为了驱动我们的GPIO1_03,我们需要的步骤:
-
使能GPIO对应的时钟。
-
设置寄存器 IOMUXC_SW_MUX_CTL_PAD_XX_XX,设置 IO 的复用功能,使其复用为 GPIO 功能。
-
设置寄存器 IOMUXC_SW_PAD_CTL_PAD_XX_XX,设置 IO 的上下拉、速度等等。
-
将 IO 复用为了 GPIO 功能后,还需要配置 GPIO的8个寄存器,设置输入/输出、是否使用中断、默认输出电平等。
汇编知识
本篇文章的标题名为,汇编LED实验。简单贴一下接下来代码中,会用到的汇编指令:ldr和str

代码编写
首先,创建我们的工程。.vscode 文件夹里面存放 VSCode 的工程文件, led.s 就是我们新建的汇编文件。

按照IO配置里的总结,我们在led.s中编写相应的汇编代码。关键代码如下:
使能时钟
为了方便,要开启所有的外设时钟,因此 CCM_CCGR0~CCM_CCGR6 所有寄存器的 32 位都要置 1,也就是写入 0XFFFFFFFF。
cpp
/* 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]
配置IO复用
设置GPIO1_IO03的复用功能, GPIO1_IO03的复用寄存器地址为0X020E0068。给该寄存器的MUX_MODE 设 置 为 5 (0101),就 是 将GPIO1_IO03 设置为 GPIO。
cpp
/* 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]
配置IO属性
我们将GPIO1_03配置为:
*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 低转换率


这个寄存器怎么这么多专业名词,不懂怎么办?让我们大概来了解一下:

让我们看看正点原子的解释,官方最棒啦~


代码如下:
cpp
/* 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]

设置GPIO1_03做输出
根据手册我们可以查阅到GPIO1的8个配置寄存器,要配置GPIO1_03做输出我们就给GDIR(方向)寄存器对应位置1.

cpp
/* 4、设置GPIO1_IO03为输出 */
ldr r0, =0X0209C004 /*寄存器GPIO1_GDIR */
ldr r1, =0X0000008
str r1,[r0]
打开LED0
根据原理图,写0是导通,LED0亮。
cpp
/* 5、打开LED0
* 设置GPIO1_IO03输出低电平
*/
ldr r0, =0X0209C000 /*寄存器GPIO1_DR */
ldr r1, =0
str r1,[r0]
完整代码如下,方便朋友们复制:
cpp
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
编译代码
使用交叉编译器 arm-linux-gnueabihf-gcc 来编译,将 led.s 编译为 led.o。
"-g"选项是产生调试信息。
-c"选项是编译源文件,但是不链接。
"-o"选项是指定编译产生的文件名字。
cpp
arm-linux-gnueabihf-gcc -g -c led.s -o led.o

arm-linux-gnueabihf-ld 用来将众多的.o 文件链接到一个指定的链接位置。
-Ttext 就是指定链接地址。
"-o"选项指定链接生成的 elf 文件名。
cpp
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
上述命令执行完以后就会在工程目录下多一个 led.elf 文件,还需要将 led.elf 文件转换为.bin 文件。使用arm-linux-gnueabihf-objcopy 这个工具。
"-O"选项指定以什么格式输出,后面的" binary"表示以二进制格式输出,选项"-S"表示不要复制源文件中的重定位信息和符号信息,"-g"表示不复制源文件中的调试信息。
cpp
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令:
cpp
arm-linux-gnueabihf-objdump -D led.elf > led.dis
总结一下,为了编译led.s我们需要敲写4个命令,太麻烦了,所以需要引入makefile来帮忙。
cpp
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
在工程根目录下创建一个名为"Makefile"的文件,需要根据 Makefile 语法编写 ,内容如下:
cpp
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"。
接下来就是代码烧写和功能验证了,笔者不再赘述,读万卷书不如行万里路,看无数次不如亲自实践一次,行动起来吧~我们下篇见。