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"。

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

相关推荐
cat_with_cat12 分钟前
Linux网络:UDP socket创建流程与简单通信
linux·网络·udp
2401_8616152841 分钟前
跨平台的ARM 和 x86 Docker 镜像:汇编语言实验环境搭建
linux·汇编·ubuntu·docker·容器
hz355721 小时前
IO多路复用--epoll
linux
努力学习的小廉1 小时前
深入了解linux系统—— System V之消息队列和信号量
android·linux·开发语言
华无丽言1 小时前
如何简单实现发版不影响客户使用?nginx负载
linux·nginx
自由游戏开发者1 小时前
用U盘启动制作centos系统最常见报错,系统卡住无法继续问题(手把手)
linux·运维·centos
Ronin3052 小时前
【Linux系统】vim编辑器 | 编译器gcc/g++ | make/Makefile
linux·运维·服务器·ubuntu·编辑器·vim
Bruce_Liuxiaowei2 小时前
Netstat高级分析工具:Windows与Linux双系统兼容的精准筛查利器
linux·运维·网络·windows·安全
yt948322 小时前
C#实现CAN通讯接口
java·linux·前端
linux修理工2 小时前
n1 armbian 安装桌面环境并启用xrdp远程登录
linux·服务器·数据库