imx6ull-裸机学习实验1——汇编LED灯实验

目录

实验准备

硬件原理图

IO配置

DR(数据寄存器)

GDIR(方向寄存器)

PSR(状态寄存器)

ICR1,ICR2(中断控制寄存器)

IMR(中断屏蔽寄存器)

ISR(中断状态寄存器)

EDGL_SEL(边沿选择寄存器)

时钟配置

汇编知识

代码编写

使能时钟

配置IO复用

​编辑

配置IO属性

设置GPIO1_03做输出

打开LED0

编译代码


实验准备

硬件原理图

我使用的是正点原子的开发板,原理图如下:

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,我们需要的步骤:

  1. 使能GPIO对应的时钟。

  2. 设置寄存器 IOMUXC_SW_MUX_CTL_PAD_XX_XX,设置 IO 的复用功能,使其复用为 GPIO 功能。

  3. 设置寄存器 IOMUXC_SW_PAD_CTL_PAD_XX_XX,设置 IO 的上下拉、速度等等。

  4. 将 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"。

接下来就是代码烧写和功能验证了,笔者不再赘述,读万卷书不如行万里路,看无数次不如亲自实践一次,行动起来吧~我们下篇见。

相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩3 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言