【ARM裸机开发】IMX6ULL平台点亮一个LED灯

一、IMX6ULL平台GPIO初始化及复用

1.1.IMX6ULL的GPIO命名

IMX6ULL 的 IO 分为两类:SNVS 域的和通用的,这两类 IO 本质上都是一样的。
如“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00”的就是 GPIO 命名,命名形式就是“IOMUXC_SW_MUC_CTL_PAD_XX_XX”,后面的“XX_XX”就是 GPIO 命名。IMX6ULL 的 GPIO 并不像 STM32 一样以 PA0~15 这样命名,他是根据某个 IO 所拥有的功能来命名的。比如我们一看到GPIO1_IO01 就知道这个肯定能做 GPIO,看到 UART1_TX_DATA 肯定就知道这个 IO 肯定能做为 UART1 的发送引脚。
STM32 的很多 IO 是可以复用为其它功能的,那么 IMX6ULL 的其它 IO 也 是 可 以 复 用 为 GPIO 功能。同样的,GPIO1_IO00~GPIO_IO09 也是可以复用为其它外设引脚的,接下来就是 IMX6ULL IO 复用。

1.2.IMX6ULL的GPIO复用

以“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00”这个 IO 为例,《IMX6ULL 参考手册》中第1568页,如图所示:
在这里插入图片描述
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00寄存器
从图中可以看到有个名为:IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00 的寄存器,寄存器地址为 0X020E005C,这个寄存器是 32 位的,但是只用到了最低 5 位,其中bit0~bit3(MUX_MODE)就是设置 GPIO1_IO00 的复用功能的。GPIO1_IO00 一共可以复用为 9 种功能 IO,分别对应 ALT0~ALT8,其中 ALT5 就是作为 GPIO1_IO00。GPIO1_IO00 还可以作为 I2C2_SCL、GPT1_CAPTURE1、ANATOP_OTG1_ID 等。这个就是 I.MX6U 的 IO 复用,我们学习 STM32 的时候 STM32 的 GPIO 也是可以复用的。
再来看一个“IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA”这个 IO,对应《IMX6ULL 参考手册》1578页 ,这个 IO 对应的复用如下图所示:
IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA寄存器
同样的,从图中可以看出,UART1_TX_DATA 可以复用为 8 种不同功能的 IO,分为ALT0~ALT5 和 ALT8、ATL9,其中 ALT5 表示 UART1_TX_DATA 可以复用为 GPIO1_IO16。
由此可见,IMX6ULL的 GPIO 不止 GPIO1_IO00~GPIO1_IO09 这 10 个,其它的 IO 都可以复用为 GPIO 来使用。IMX6ULL的 GPIO 一共有 5 组:GPIO1、GPIO2、GPIO3、GPIO4 和 GPIO5,其中 GPIO1 有 32 个 IO,GPIO2 有 22 个 IO,GPIO3 有 29 个 IO、GPIO4 有 29 个 IO,GPIO5 最少,只有 12 个 IO,这样一共有 124 个 GPIO。
如果只想看每个 IO 能复用什么外设的话可以直接查阅《IMX6ULL 参考手册》的第 4 章“Chapter 4 External Signals and Pin Multiplexing”。如果我们要编写代码,设置某个 IO 的复用功能的话就需要查阅第 32 章“Chapter 32: IOMUX Controller(IOMUXC)”,第 32 章详细的列出了所有 IO 对应的复用配置寄存器。

1.3.IMX6ULL的GPIO寄存器配置

在《IMX6ULL 参考手册》的第30章中,每一个 IO 会出现两次,它们的名字差别很小,不仔细看就看不出来,比如 GPIO1_IO00 有如下两个书签:

IOMUXC_SW_MUXCTL_PAD_GPIO1_IO00
IOMUXC_SW
PAD_CTL_PAD_GPIO1_IO00

那么 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00 是做什么的呢?找到《IMX6ULL 参考手册》对应的 1787 页

OMUXC_SW_PAD_CTL_PAD_GPIO1_IO00寄存器
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00 也是个寄存器,寄存器地址为 0X020E02E8。这也是个 32 位寄存器,但是只用到了其中的低 17 位。
HYS(bit16):用来使能迟滞比较器,当 IO 作为输入功能的时候有效,用于设置输入接收器的施密特触发器是否使能。如果需要对输入波形进行整形的话可以使能此位。此位为 0 的时候禁止迟滞比较器,为 1 的时候使能迟滞比较器。
**PUS(bit15:14):**用来设置上下拉电阻的,一共有四种选项可以选择,如下表所示:

位设置含义
00100K 下拉
0147K 上拉
10100K 上拉
1122K 上拉

**PUE(bit13):**当 IO 作为输入的时候,这个位用来设置 IO 使用上下拉还是状态保持器。当为 0 的时候使用状态保持器,当为 1 的时候使用上下拉。状态保持器在IO 作为输入的时候才有用,顾名思义,就是当外部电路断电以后此 IO 口可以保持住以前的状态。
**PKE(bit12):**此位用来使能或者禁止上下拉/状态保持器功能,为0 时禁止上下拉/状态保持器,为 1 时使能上下拉和状态保持器。
**ODE(bit11):**当 IO 作为输出的时候,此位用来禁止或者使能开路输出,此位为 0 的时候禁止开路输出,当此位为 1 的时候就使能开路输出功能。
**SPEED(bit7:6):**当 IO 用作输出的时候,此位用来设置 IO 速度,设置如下表所示:

位设置速度
00低速 50M
01中速 100M
10中速 100M
11最大速度 200M

**DSE(bit5:3):**当 IO 用作输出的时候用来设置 IO 的驱动能力,总共有 8 个可选选项,如下表所示:

位设置速度
000输出驱动关闭
001R0(3.3V 下 R0 是 260Ω,1.8V 下 R0 是 150Ω,接 DDR 的时候是 240Ω)
010R0/2
011R0/3
100R0/4
101R0/5
110R0/6
111R0/7

**SRE(bit0):**设置压摆率,当此位为 0 的时候是低压摆率,当为 1 的时候是高压摆率。这里的压摆率就是 IO 电平跳变所需要的时间,比如从 0 到 1 需要多少时间,时间越小波形就越陡,说明压摆率越高;反之,时间越多波形就越缓,压摆率就越低。如果你的产品要过 EMC 的话那就可以使用小的压摆率,因为波形缓和,如果你当前所使用的 IO 做高速通信的话就可以使用高压摆率。
IOMUXC_SW_MUX_CTL_PAD_XX_XX 和 IOMUXC_SW_PAD_CTL_PAD_XX_XX 这两
种寄存器都是配置 IO 的。如果我们要用 GPIO1_IO00 来点个灯、作为按键输入啥的就是使用其 GPIO(通用输入输出)的功能。将其复用为 GPIO 以后还需要对其 GPIO 的功能进行配置,关于 IMX6ULL 的 GPIO 请参考《IMX6ULL 参考手册》的第 26 章“Chapter 26 General Purpose Input/Ouput(GPIO)”
IOMUXC 框 图 里 面 就 有 SW_MUX_CTL_PAD_ 和SW_PAD_CTL_PAD_两种寄存器。这两种寄存器前面说了用来设置 IO 的复用功能和 IO 属性配置。左上角部分的 GPIO 框图就是,当 IO 用作 GPIO 的时候需要设置的寄存器,一共有八个:DR、GDIR、PSR、ICR1、ICR2、EDGE_SEL、IMR 和 ISR。前面我们说了 I.MX6U 一共有GPIO1~GPIO5 共五组 GPIO,每组 GPIO 都有这 8 个寄存器。
IOMUXC框图_GPIO结构图
DR(数据寄存器):当 GPIO 被配置为输出功能以后,向指定的位写入数据那么相应的 IO 就会输出相应的高低电平,比如要设置 GPIO1_IO00 输出高电平,那么就应该设置 GPIO1.DR=1。当 GPIO被配置为输入模式以后,此寄存器就保存着对应 IO 的电平值,每个位对对应一个 GPIO,例如,当 GPIO1_IO00 这个引脚接地的话,那么 GPIO1.DR 的 bit0 就是 0。
GDIR(方向寄存器):用来设置某个 GPIO 的工作方向的,即输入/输出。同样的,每个 IO 对应一个位,如果要设置 GPIO 为输入的话就设置相应的位为 0,如果要设置为输出的话就设置为 1。比如要设置 GPIO1_IO00 为输入,那么 GPIO1.GDIR的 bit0 就是 0。
PSR(状态寄存器):同样的 PSR 寄存器也是一个 GPIO 对应一个位,读取相应的位即可获取对应的 GPIO 的状态,也就是 GPIO 的高低电平值。功能和输入状态下的 DR 寄存器一样。
ICR1和ICR2(中断控制寄存器):ICR1用于配置低16个GPIO,ICR2 用于配置高 16 个 GPIO。ICR1 用于 IO0~15 的配置, ICR2 用于 IO16~31 的配置。ICR1 寄存器中一个 GPIO 用两个位,这两个位用来配置中断的触发方式。

位设置触发方式
00低电平触发
01高电平触发
10上升沿触发
11下降沿触发

以GPIO1_IO15为例,如果要设置GPIO1_IO15为上升沿触发中断,那么GPIO1.ICR1=2<<30,如果要设置 GPIO1 的 IO16~31 的话就需要设置 ICR2 寄存器了。
IMR(中断屏蔽寄存器):IMR 寄存器也是一个 GPIO 对应一个位,IMR 寄存器用来控制 GPIO 的中断禁止和使能,如果使能某个 GPIO 的中断,那么设置相应的位为 1 即可,反之,如果要禁止中断,那么就设置相应的位为 0 即可。
ISR(中断状态寄存器):ISR 寄存器也是 32 位寄存器,一个 GPIO 对应一个位,只要某个 GPIO 的中断发生,那么ISR 中相应的位就会被置 1。相当于中断标志位
EDGE_SEL(边沿选择寄存器):EDGE_SEL 寄存器用来设置边沿中断,这个寄存器会覆盖 ICR1 和 ICR2 的设置,同样是一个 GPIO 对应一个位。如果相应的位被置 1,那么就相当与设置了对应的 GPIO 是上升沿和下降沿(双边沿)触发。

1.4.IMX6ULL的GPIO时钟使能

IMX6ULL的系统时钟参考《IMX6ULL 参考手册》的第 18 章“Chapter 18: Clock Controller Module(CCM)”,这一章主要讲解 I.MX6U 的时钟系统,很复杂。我们只看一下 CCM 里 面 的 外 设 时 钟 使 能 寄 存 器 。 CMM 有CCM_CCGR0~CCM_CCGR6 这 7 个寄存器,这 7 个寄存器控制着 I.MX6U 的所有外设时钟开关。
以 CCM_CCGR0 为例来看一下如何禁止或使能一个外设的时钟。CCM_CCGR0 是个 32 位寄存器,其中每 2 位控制一个外设的时钟,比如 bit31:30 控制着GPIO2 的外设时钟,两个位就有 4 种操作方式,如下表所示:

位设置时钟控制
00所有模式下都关闭外设时钟
01只有在运行模式下打开外设时钟,等待模式和停止模式下均关闭外设时钟
10未使用
11除了停止模式以外,其他所有模式下时钟都打开

CCM寄存器
位描述
如果我们要打开 GPIO2 的外设时钟,那么只需要设置CCM_CCGR0 的 bit31 和 bit30 都为 1 即可,也就是 CCM_CCGR0=3 << 30。反之,如果要关闭GPIO2 的 外 设 时 钟 , 那 就 设 置 CCM_CCGR0 的 bit31 和 bit30 都为 0 即可。
总结一下,要将 IMX6ULL 的 IO 作为 GPIO 使用,我们需要一下几步:
①、使能 GPIO 对应的时钟。
②、设置寄存器 IOMUXC_SW_MUX_CTL_PAD_XX_XX,设置 IO 的复用功能,使其复用为 GPIO 功能。
③、设置寄存器 IOMUXC_SW_PAD_CTL_PAD_XX_XX,设置 IO 的上下拉、速度等等。
④、第②步已经将 IO 复用为了 GPIO 功能,所以需要配置 GPIO,设置输入/输出、是否使用中断、默认输出电平等。

二、使用汇编语言编搭建C语言启动环境

在开始部分用汇编来初始化一下 C 语言环境,比如初始化 DDR、设置堆栈指针 SP 等等,当这些工作都做完以后就可以进入 C 语言环境,也就是运行 C 语言代码,一般都是进入 main 函数。所以我们有两部分文件要做:
①、汇编文件
汇编文件只是用来完成 C 语言环境搭建。
②、C 语言文件
C 语言文件就是完成我们的业务层代码的,其实就是我们实际例程要完成的功能。

.global _start       /* 全局标号 */
 /* 
* 描述: _start 函数,程序从此函数开始执行,此函数主要功能是设置 C 
* 运行环境
*/
 _start: 
 /* 进入 SVC 模式 */
 mrs r0, cpsr
bic r0, r0, #0x1f     /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4*/
orr r0, r0, #0x13     /* r0 或上 0x13,表示使用 SVC 模式*/
msr cpsr, r0          /* 将 r0 的数据写入到 cpsr_c 中*/

ldr sp, =0X80200000  /* 设置栈指针             */
b main               /* 跳转到 main 函数       */

第 1 行定义了一个全局标号_start。
第 6 行标号_start 开始的地方,相当于是一个_start 函数。
第 8-11 行设置处理器进入 SVC 模式。处理器模式的设置是通过修改 CPSR(程序状态)寄存器来完成的,M[4:0](CPSR 的 bit[4:0])就是设置处理器运行模式的(M[4:0]就要等于 0x13)。9~11 行代码就是先使用指令MRS将CPSR寄存器的值读取到 R0中,然后修改R0中的值,设置R0的 bit[4:0]为0x13,然后再使用指令MSR将修改后的 R0 重新写入到 CPSR 中。
第 13 行通过 ldr 指令设置 SVC 模式下的 SP 指针=0X80200000,因为IMX6ULL 开发板上的DDR3地址范围是0X80000000~0XA0000000(512MB) 或者0X80000000~0X90000000(256MB),不管是 512MB 版本还是 256MB 版本的,其 DDR3 起始地址都是 0X80000000。由于 Cortex-A7 的堆栈是向下增长的,所以将 SP 指针设置为 0X80200000,因此 SVC 模式的栈大小 0X80200000-0X80000000=0X200000=2MB,2MB 的栈空间已经很大了,如果做裸机开发的话绰绰有余。
第 14 行就是跳转到 main 函数,main 函数就是 C 语言代码了。

三、点亮LED灯

C 语言环境搭建完成后,就需要编写相关的C语言代码,C语言代码中的main.h文件主要用于初始化各种寄存器,定义寄存器地址,main.c代码用于实现相关功能。

3.1.main.h文件中寄存器地址定义

时钟 GPIO1_IO03 相关寄存器地址定义:

#ifndef __MAIN_H
#define __MAIN_H

/* 
 * CCM相关寄存器地址 
 */
#define CCM_CCGR0 			*((volatile unsigned int *)0X020C4068)
#define CCM_CCGR1 			*((volatile unsigned int *)0X020C406C)

#define CCM_CCGR2 			*((volatile unsigned int *)0X020C4070)
#define CCM_CCGR3 			*((volatile unsigned int *)0X020C4074)
#define CCM_CCGR4 			*((volatile unsigned int *)0X020C4078)
#define CCM_CCGR5 			*((volatile unsigned int *)0X020C407C)
#define CCM_CCGR6 			*((volatile unsigned int *)0X020C4080)

/* 
 * IOMUX相关寄存器地址 
 */
#define SW_MUX_GPIO1_IO03 	*((volatile unsigned int *)0X020E0068)
#define SW_PAD_GPIO1_IO03 	*((volatile unsigned int *)0X020E02F4)

/* 
 * GPIO1相关寄存器地址 
 */
#define GPIO1_DR 			*((volatile unsigned int *)0X0209C000)
#define GPIO1_GDIR 			*((volatile unsigned int *)0X0209C004)
#define GPIO1_PSR 			*((volatile unsigned int *)0X0209C008)
#define GPIO1_ICR1 			*((volatile unsigned int *)0X0209C00C)
#define GPIO1_ICR2 			*((volatile unsigned int *)0X0209C010)
#define GPIO1_IMR 			*((volatile unsigned int *)0X0209C014)
#define GPIO1_ISR 			*((volatile unsigned int *)0X0209C018)
#define GPIO1_EDGE_SEL 		*((volatile unsigned int *)0X0209C01C)

#endif

参考《IMX6ULL 参考手册》的第 18 章“Chapter 18: Clock Controller Module(CCM)”,在参考手册的658页中定义了CCM中各类定时器的地址,如下图所示:
在这里插入图片描述
在这里插入图片描述
在 main.h 中我们以宏定义的形式定义了要使用到的所有寄存器,后面的数字就是其地址,比如 CCM_CCGR0 寄存器的地址就是 0X020C4068。

3.2.main.c文件

#include "main.h"

/*
 * @description	: 使能I.MX6U所有外设时钟
 * @param 		: 无
 * @return 		: 无
 */
void clk_enable(void)
{
	CCM_CCGR0 = 0xffffffff;
	CCM_CCGR1 = 0xffffffff;
	CCM_CCGR2 = 0xffffffff;
	CCM_CCGR3 = 0xffffffff;
	CCM_CCGR4 = 0xffffffff;
	CCM_CCGR5 = 0xffffffff;
	CCM_CCGR6 = 0xffffffff;
}

/*
 * @description	: 初始化LED对应的GPIO
 * @param 		: 无
 * @return 		: 无
 */
void led_init(void)
{
	/* 1、初始化IO复用 */
	SW_MUX_GPIO1_IO03 = 0x5;	/* 复用为GPIO1_IO03 */

	/* 2、、配置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 低转换率
     */
	SW_PAD_GPIO1_IO03 = 0X10B0;		

	/* 3、初始化GPIO */
	GPIO1_GDIR = 0X0000008;	/* GPIO1_IO03设置为输出 */

	/* 4、设置GPIO1_IO03输出低电平,打开LED0 */
	GPIO1_DR = 0X0;
}

/*
 * @description	: 打开LED灯
 * @param 		: 无
 * @return 		: 无
 */
void led_on(void)
{
	/* 
	 * 将GPIO1_DR的bit3清零	 
	 */
	GPIO1_DR &= ~(1<<3); 
}

/*
 * @description	: 关闭LED灯
 * @param 		: 无
 * @return 		: 无
 */
void led_off(void)
{
	/*    
	 * 将GPIO1_DR的bit3置1
	 */
	GPIO1_DR |= (1<<3);
}

/*
 * @description	: 短时间延时函数
 * @param - n	: 要延时循环次数(空操作循环次数,模式延时)
 * @return 		: 无
 */
void delay_short(volatile unsigned int n)
{
	while(n--){}
}

/*
 * @description	: 延时函数,在396Mhz的主频下
 * 			  	  延时时间大约为1ms
 * @param - n	: 要延时的ms数
 * @return 		: 无
 */
void delay(volatile unsigned int n)
{
	while(n--)
	{
		delay_short(0x7ff);
	}
}

/*
 * @description	: mian函数
 * @param 	    : 无
 * @return 		: 无
 */
int main(void)
{
	clk_enable();		/* 使能所有的时钟		 	*/
	led_init();			/* 初始化led 			*/

	while(1)			/* 死循环 				*/
	{	
		led_off();		/* 关闭LED   			*/
		delay(500);		/* 延时大约500ms 		*/

		led_on();		/* 打开LED		 	*/
		delay(500);		/* 延时大约500ms 		*/
	}

	return 0;
}

delay_short()函数是靠空循环来实现延时的,delay()是对 delay_short() 的 简单封装,在 IMX6ULL工作在396MHz(Boot ROM 设置 的 396MHz) 的主频的时候delay_short(0x7ff)基本能够实现大约 1ms 的延时。

四、总结

本文主要介绍了IMX6ULL平台如何初始化一个GPIO,并介绍了如何编写汇编代码搭建C语言环境,简单介绍了一种LED灯的点亮方式。

参考资料:
《IMX6ULL 参考手册》官方参考手册
《正点原子-I.MX6U 嵌入式 Linux 驱动开发指南 V1.81 》开发教程
开发板平台:正点原子 I.MX6U ALPHA

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿强12138

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值