第2期ARM裸机篇:【6】 汇编LED驱动实验1_汇编LED代码编写

简介

本章开始编写本教程第一个裸机例程——经典的点灯试验,这也是我们嵌入式 Linux 学习的第一步。万里长征第一步,祝愿大家学习愉快!

目标

了解如何使用汇编语言来初始化 I.MX6U 外设寄存器、了解 I.MX6UL 最基本的 IO 输出功能。

阅读基础

熟悉计算机。

环境说明

  • windows10

参考资料

  • 原子文档:I.MX6ULL开发指南 第八章
  • NXP官方文档:I.MX6ULL参考手册、数据手册

I.MX6U GPIO 详解

STM32 GPIO 回顾

我们一般拿到一款全新的芯片,第一个要做的事情的就是驱动其 GPIO,控制其 GPIO 输出高低电平,我们学习 I.MX6U 也一样的,先来学习一下 I.MX6U 的 GPIO。

在学习 I.MX6U的 GPIO 之前,我们先来回顾一下 STM32 的 GPIO 初始化(如果没有学过 STM32 就不用回顾了),我们以最常见的 STM32F103 为例来看一下 STM32 的 GPIO 初始化,示例代码如下:

示例代码 STM32 GPIO 初始化
void LED_Init(void)
{
    
    
	GPIO_InitTypeDef GPIO_InitStructure; 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能 PB 端口时钟
 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; 		 //PB5 端口配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度
    GPIO_Init(GPIOB, &GPIO_InitStructure); 		//根据设定参数初始化 GPIOB.5
 
	GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高
}

上述代码就是使用库函数来初始化 STM32 的一个 IO 为输出功能,可以看出上述初始化代码中重点要做的事情有一下几个:

  1. 使能指定 GPIO 的时钟。
  2. 初始化 GPIO,比如输出功能、上拉、速度等等。
  3. STM32 有的 IO 可以作为其它外设引脚,也就是 IO 复用,如果要将 IO 作为其它外设引脚使用的话就需要设置 IO 的复用功能。
  4. 最后设置 GPIO 输出高电平或者低电平。
  • STM32 的 GPIO 初始化就是以上四步,那么会不会也适用于 I.MX6U 的呢?
  • I.MX6U 的GPIO 是不是也需要开启相应的时钟?
  • 是不是也可以设置复用功能?
  • 是不是也可以设置输出或输入、上下拉、速度等等这些?

我们现在都不知道,只有去看 I.MX6U 的数据手册和参考手册才能知道,I.MX6U 的数据手册和参考手册有I.MX6UL 和 I.MX6ULL 两种,这两种型号基本是一样的,我们以 I.MX6ULL 为例来讲解。

I.MX6ULL 的数据手册有三种,分别对应:车规级、工业级和商用级。从我们写代码的角度看,这三份数据手册一模一样的,做硬件的在选型的时候才需要注意一下,我们就用商用级的手册。

带着上面四个疑问打开这两份手册,然后就是“啃”手册

扫描二维码关注公众号,回复: 13246857 查看本文章

I.MX6U IO 命名

STM32 中的 IO 都是 PA0~15、PB0~15 这样命名的,I.MX6U 的 IO 是怎么命名的呢?

打开I.MX6ULL 参考手册的第 32 章“Chapter 32: IOMUX Controller(IOMUXC)”,第 32 章的书签如图所示:

BlogImage-20210909092220

从上图可以看出,I.MX6ULL 的 IO 分为两类:SNVS 域的和通用的,这两类 IO 本质上都是一样的,我们就有下面的常用 IO 为例,讲解一下 I.MX6ULL 的 IO 命名方式。图中的形如“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00”的就是 GPIO 命名,命名形式就是“IOMUXC_SW_MUC_CTL_PAD_XX_XX”,后面的“XX_XX”就是 GPIO 命名,比如:GPIO1_IO01、UART1_TX_DATA、JTAG_MOD 等等。

I.MX6ULL 的 GPIO 并不像 STM32一样以 PA0~15 这样命名,他是根据某个 IO 所拥有的功能来命名的。比如我们一看到GPIO1_IO01 就知道这个肯定能做 GPIO,看到 UART1_TX_DATA 肯定就知道这个 IO 肯定能做为 UART1 的发送引脚。

Chapter 32: IOMUX Controller(IOMUXC)”这一章列出了 I.MX6ULL 的所有 IO,如果你找遍 32 章的书签,你会发现貌似 GPIO 只有GPIO1_IO00~GPIO1_IO09,难道I.MX6ULL 的 GPIO 只有这 10 个?

显然不是的, 我们知道 STM32 的很多 IO 是可以复用为其它 功 能 的 , 那 么 I.MX6ULL 的其它 IO 也 是 可 以 复 用 为 GPIO 功能。同样的,GPIO1_IO00~GPIO_IO09 也是可以复用为其它外设引脚的,接下来就是 I.MX6ULL IO 复用。

I.MX6U IO 复用

以“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00”这个 IO 为例,打开参考手册的1568页,如图所示:

BlogImage-20210909092938

从图 可以看到有个名为: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,这个 IO 对应的复用如图 所示:

BlogImage-20210909093806

同样的,从图可以看出,UART1_TX_DATA 可以复用为 8 种不同功能的 IO,分为ALT0~ALT5 和 ALT8、ATL9,其中 ALT5 表示 UART1_TX_DATA 可以复用为 GPIO1_IO16

由此可见,I**.MX6U 的 GPIO 不止 GPIO1_IO00~GPIO1_IO09 这 10 个,其它的 IO 都可以复用为 GPIO 来使用**。

I.MX6U 的 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 对应的复用配置寄存器。

至此我们就解决了前面的第 3 个疑问,那就是 I.MX6U 的 IO 是有复用功能的,和 STM32一样,如果某个 IO 要作为某个外设引脚使用的话,是需要配置复用寄存器的

I.MX6U IO 配置

细心的读者 应该会发现在《 I.MX6UL 参考手册》32 章“Chapter 32: IOMUX Controller(IOMUXC)”的书签中,每一个 IO 会出现两次,它们的名字差别很小,不仔细看就看不出来,比如 GPIO1_IO00 有如下两个书签:

  • IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00
  • IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00

上面两个都是跟 GPIO_IO00 有关的寄存器,名字上的区别就是红色部分,一个是“MUX”,一个是“PAD”。IOMUX_SW_MUX_CTL_PAD_GPIO1_IO00 我们前面已经说了,是用来配置GPIO1_IO00 复用功能的,那么 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00 是做什么的呢?找到这个书签对应的 1787页,如图所示:

BlogImage-20210909095116

从图中可以看出,IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00 也是个寄存器,寄存器地址为 0X020E02E8。这也是个 32 位寄存器,但是只用到了其中的低 17 位,在看这写位的具体含义之前,先来看一下图所示的 GPIO 功能图:

BlogImage-20210909095302

我们对照着图来详细看一下寄存器 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00 的各个位的含义:

HYS(bit16)

HYS(bit16):对应图中HYS,用来使能迟滞比较器,当 IO 作为输入功能的时候有
效,用于设置输入接收器的施密特触发器是否使能。如果需要对输入波形进行整形的话可以使
能此位此位为 0 的时候禁止迟滞比较器,为 1 的时候使能迟滞比较器

PUS(bit15:14)

PUS(bit15:14):对应图中的PUS用来设置上下拉电阻的,一共有四种选项可以选择,如表所示:

BlogImage-20210909095648

PUE(bit13)

PUE(bit13):图没有给出来,当 IO 作为输入的时候,这个位用来设置 IO 使用上下拉还是状态保持器当为 0 的时候使用状态保持器,当为 1 的时候使用上下拉状态保持器在IO 作为输入的时候才有用,顾名思义,就是当外部电路断电以后此 IO 口可以保持住以前的状态

PKE(bit12)

PKE(bit12):对应图 中的PKE,此位用来使能或者禁止上下拉/状态保持器功能为0 时禁止上下拉/状态保持器,为 1 时使能上下拉和状态保持器

ODE(bit11)

ODE(bit11):对应图中的 ODE当 IO 作为输出的时候,此位用来禁止或者使能开路输出此位为 0 的时候禁止开路输出,当此位为 1 的时候就使能开路输出功能

SPEED(bit7:6)

SPEED(bit7:6):对应图中的 SPEED当 IO 用作输出的时候,此位用来设置 IO 速度,设置如表 所示:

BlogImage-20210909100351

DSE(bit5:3)

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

BlogImage-20210909100447

SRE(bit0)

SRE(bit0):对应图中的SRE设置压摆率当此位为 0 的时候是低压摆率,当为 1的时候是高压摆率这里的压摆率就是 IO 电平跳变所需要的时间,比如从 0 到 1 需要多少时间,时间越小波形就越陡,说明压摆率越高;反之,时间越多波形就越缓,压摆率就越低

如果你的产品要过 EMC 的话那就可以使用小的压摆率,因为波形缓和,如果你当前所使用的 IO做高速通信的话就可以使用高压摆率。

总结

通过上面的介绍,可以看出寄存器 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00 是用来配置GPIO1_IO00 的,包括速度设置、驱动能力设置、压摆率设置等等

至此我们就解决了前面的第 2 疑问,那就是 I.MX6U 的 IO 是可以设置速度的、而且比 STM32 的设置要更多。但是我们没有看到如何设置 IO 为输入还是输出?IO 的默认电平如何设置等等,所以我们接着继续看。

I.MX6U GPIO 配置

IOMUXC_SW_MUX_CTL_PAD_XX_XXIOMUXC_SW_PAD_CTL_PAD_XX_XX 这两种寄存器都是配置IO 的,注意是 IO!不是 GPIO,GPIO 是一个 IO 众多复用功能中的一种

比如 GPIO1_IO00 这个 IO可以复用为:I2C2_SCL、GPT1_CAPTURE1、ANATOP_OTG1_ID、ENET1_REF_CLK 、MQS_RIGHT 、 GPIO1_IO00 、ENET1_1588_EVENT0_IN 、SRC_SYSTEM_RESET 和WDOG3_WDOG_B 这 9 个功能,GPIO1_IO00 是其中的一种,我们想要把 GPIO1_IO00 用作哪个外设就复用为哪个外设功能即可。如果我们要用 GPIO1_IO00 来点个灯、作为按键输入啥的就是使用其 GPIO(通用输入输出)的功能。

将其复用为 GPIO 以后还需要对其 GPIO 的功能进行配置,关于 I.MX6U 的 GPIO 请参《IMX6UL 参考手册》的第 28章Chapter 28 General Purpose Input/Ouput(GPIO)”,GPIO 结构如图所示:

BlogImage-20210909101304

在 图的 左 下 角 的 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 个寄存器。我们来看一下这 8 个寄存器都是什么含义。

DR 寄存器

首先来看一下 DR 寄存器,此寄存器是数据寄存器,结构图如图所示:

BlogImage-20210909102147

此寄存器是 32 位的,一个 GPIO 组最大只有 32 个 IO,因此 DR 寄存器中的每个位都对应一个 GPIO

当 GPIO 被配置为输出功能以后,向指定的位写入数据那么相应的 IO 就会输出相应的高低电平,比如要设置 GPIO1_IO00 输出高电平,那么就应该设置 GPIO1.DR=1。

当 GPIO被配置为输入模式以后,此寄存器就保存着对应 IO 的电平值,每个位对对应一个GPIO,例如,当 GPIO1_IO00 这个引脚接地的话,那么 GPIO1.DR 的 bit0 就是 0。

GDIR 寄存器

看完 DR 寄存器,接着看 GDIR 寄存器,这是方向寄存器,用来设置某个 GPIO 的工作方
向的,即输入/输出,GDIR 寄存器结构如图所示:

BlogImage-20210909103016

GDIR 寄存器也是 32 位的,此寄存器用来设置某个 IO 的工作方向,是输入还是输出。

同样的,每个 IO 对应一个位,如果要设置 GPIO 为输入的话就设置相应的位为 0,如果要设置为 输出的话就设置为 1。比如要设置 GPIO1_IO00 为输入,那么 GPIO1.GDIR=0;

PSR 寄存器

接下来看 PSR 寄存器,这是 GPIO 状态寄存器,如图所示

BlogImage-20210909103255

同样的 PSR 寄存器也是一个 GPIO 对应一个位,读取相应的位即可获取对应的 GPIO 的状态,也就是 GPIO 的高低电平值。功能和输入状态下的 DR 寄存器一样。

ICR1和ICR2寄存器

接下来看ICR1和ICR2这两个寄存器,都是中断控制寄存器,ICR1用于配置低16个GPIO,ICR2 用于配置高 16 个 GPIO,ICR1 寄存器如图所示:

BlogImage-20210909103547

ICR1 用于 IO0~15 的配置, ICR2 用于 IO16~31 的配置。ICR1 寄存器中一个 GPIO 用两个位这两个位用来配置中断的触发方式,和 STM32 的中断很类似,可配置的选线如表所示:

BlogImage-20210909103817

以GPIO1_IO15为例,如果要设置GPIO1_IO15为上升沿触发中断,那么GPIO1.ICR1=2<<30,如果要设置 GPIO1 的 IO16~31 的话就需要设置 ICR2 寄存器了。

IMR 寄存器

接下来看 IMR 寄存器,这是中断屏蔽寄存器,如图所示:

BlogImage-20210909104108

IMR 寄存器也是一个 GPIO 对应一个位,IMR 寄存器用来控制 GPIO 的中断禁止和使能如果使能某个 GPIO 的中断,那么设置相应的位为 1 即可,反之,如果要禁止中断,那么就设置相应的位为 0 即可。例如,要使能 GPIO1_IO00 的中断,那么就可以设置 GPIO1.MIR=1 即可。

ISR寄存器

接下来看寄存器 ISR,ISR 是中断状态寄存器,寄存器如图所示:

BlogImage-20210909104441

ISR 寄存器也是 32 位寄存器,一个 GPIO 对应一个位,只要某个 GPIO 的中断发生,那么ISR 中相应的位就会被置 1。所以,我们可以通过读取 ISR 寄存器来判断 GPIO 中断是否发生,相当于 ISR 中的这些位就是中断标志位。当我们处理完中断以后,必须清除中断标志位,清除方法就是向 ISR 中相应的位写 1,也就是写 1 清零

EDGE_SEL 寄存器

最后来看一下 EDGE_SEL 寄存器,这是边沿选择寄存器,寄存器如图所示:

BlogImage-20210909104615

EDGE_SEL 寄存器用来设置边沿中断,这个寄存器会覆盖 ICR1 和 ICR2 的设置,同样是一个 GPIO 对应一个位

如果相应的位被置 1,那么就相当与设置了对应的 GPIO 是上升沿和下降沿(双边沿)触发。例如,我们设置 GPIO1.EDGE_SEL=1,那么就表示 GPIO1_IO01 是双边沿触发中断,无论 GFPIO1_CR1的设置为多少,都是双边沿触发。

关于 GPIO 的寄存器就讲解到这里,因为 GPIO 是最常用的功能,我们详细的讲解了 GPIO的 8个寄存器。至此我们就解决了前面的第 3 个和第 4 个疑问,那就是 I.MX6U 的 IO 是需要配置和输出的、是可以设置输出高低电平,也可以读取 GPIO 对应的电平

I.MX6U GPIO 时钟使能

还有最后一个疑问,那就是 I.MX6U 的 GPIO 是否需要使能时钟?

STM32 的每个外设都有一个外设时钟,GPIO 也不例外,要使用某个外设,必须要先使能对应的时钟。I.MX6U 其实也一样的,每个外设的时钟都可以独立的使能或禁止,这样可以关闭掉不使用的外设时钟,起到省电的目的

I.MX6U 的系统时钟参考《I.MX6UL 参考手册》的第 18 章“Chapter 18: Clock Controller Module(CCM)”,这一章主要讲解 I.MX6U 的时钟系统,很复杂。我们先不研究I.MX6U的 时 钟 系 统 , 我 们 只 看 一 下 CCM 里 面 的 外 设 时 钟 使 能 寄 存 器

CMM 有 CCM_CCGR0~CCM_CCGR6 这 7 个寄存器,这 7 个寄存器控制着 I.MX6U 的所有外设时钟开关,我们以 CCM_CCGR0 为例来看一下如何禁止或使能一个外设的时钟,CCM_CCGR0 结构体如图所示:

BlogImage-20210909105015

CCM_CCGR0 是个 32 位寄存器,其中每 2 位控制一个外设的时钟,比如 bit31:30 控制着GPIO2 的外设时钟,两个位就有 4 种操作方式,如表所示:

BlogImage-20210909105349

根据表中的位设置,如果我们要打开 GPIO2 的外设时钟,那么只需要设置CCM_CCGR0 的 bit31 和 bit30 都为 1 即可,也就是 CCM_CCGR0=3 << 30。反之,如果要关闭GPIO2 的 外 设 时 钟 , 那 就 设 置 CCM_CCGR0 的 bit31 和 bit30 都 为 0 即 可 。

CCM_CCGR0~CCM_CCGR6 这 7 个寄存器操作都是类似的,只是不同的寄存器对应不同的外设时钟而已为了方便开发,本教程后面所有的例程将 I.MX6U 的所有外设时钟都打开了

至此我们就解决了前面的所有问题都解决了,I.MX6U 的每个外设的时钟都可以独立的禁止和使能,这个和 STM32 是一样

总结

总结一下,要将 I.MX6U 的 IO 作为 GPIO 使用,我们需要一下几步:

  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,设置输入/输出、是否使用中断、默认输出电平等。

硬件原理分析

打开 I.MX6U-ALPHA 开发板底板原理图,I.MX6U-ALPHA 开发板上有一个 LED 灯,原理图如下所示:

BlogImage-20210909110400

输出低电平(0)的时候发光二极管 LED0 就会导通点亮,当 GPIO1_IO03 输出高电平(1)的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮

所以 LED0 的亮灭取决于 GPIO1_IO03的输出电平,输出 0 就亮,输出 1 就灭

实验程序编写

编程步骤

按照前面讲的,我们需要对 GPIO1_IO03 做如下设置:

使能 GPIO1 时钟

GPIO1 的时钟由 CCM_CCGR1 的 bit27 和 bit26 这两个位控制,将这两个位都设置位 11 即可。本教程所有例程已经将 I.MX6U 的所有外设时钟都已经打开了,因此这一步可以不用做。

设置 GPIO1_IO03 的复用功能

找到 GPIO1_IO03 的复用寄存器“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03”的地址为0X020E0068,然后设置此寄存器,将 GPIO1_IO03 这个 IO 复用为 GPIO 功能,也就是 ALT5

配置 GPIO1_IO03

找到 GPIO1_IO03 的配置寄存器“IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03”的地址为
0X020E02F4根据实际使用情况,配置此寄存器

设置 GPIO

我们已经将 GPIO1_IO03 复用为了 GPIO 功能,所以我们需要配置 GPIO。找到 GPIO3 对
应的 GPIO 组寄存器地址在《IMX6ULL 参考手册》的 1357 页,如图所示:

BlogImage-20210909111732

本实验中 GPIO1_IO03 是作为输出功能的,因此GPIO1_GDIR 的 bit3 要设置为 1,表示输出。

控制 GPIO 的输出电平

经过前面几步,GPIO1_IO03 已经配置好了,只需要向 GPIO1_DR 寄存器的 bit3 写入 0 即可控制 GPIO1_IO03 输出低电平。

打开 LED,向 bit3 写入 1 可控制 GPIO1_IO03 输出高电平,关闭LED

编写代码

创建VSCode工程

所有的裸机实验我们都在 Ubuntu 下完成,使用 VSCode 编辑器!

既然是实验,肯定要自己动手创建工程,新建一个名为“1_leds”的文件夹,然后在“1_leds”这个目录下新建一个名为“led.s”的汇编文件和一个名为“.vscode”的目录,创建好以后“1_leds”文件夹如图所示:

BlogImage-20210909145751

上图中

  • .vscode 文件夹里面存放 VSCode 的工程文件
  • led.s 就是我们新建的汇编文件

我们稍后会在 led.s 这个文件中编写汇编程序。

参考文章:第2期ARM裸机篇:【2】VSode软件的安装与使用_心飞的博客-CSDN博客

参考文章:第2期ARM裸机篇:【2】VSode软件的安装与使用_心飞的博客-个人网站

使用 VSCode 打开 1_leds 这个文件夹,打开以后如图所示:

BlogImage-20210909150031

打开led.s文件。如下图:

BlogImage-20210909150204

编写汇编代码

/**************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : led.s
作者 : weixin
版本 : V1.0
描述 : 裸机实验 1 汇编点灯
 使用汇编来点亮开发板上的 LED 灯,学习和掌握如何用汇编语言来
 完成对 I.MX6U 处理器的 GPIO 初始化和控制。
其他 : 无
论坛 : www.openedv.com
日志 : 无
**************************************************************/

 .global _start  /* 定义全局标号 */


 /*
  * 描述: _start 函数,程序从此函数开始执行此函数完成时钟使能、
  * GPIO 初始化、最终控制 GPIO 输出低电平来点亮 LED 灯。
  */
_start:


 /* 1、使能所有时钟 */

ldr r0, =0X020C4068 /* 寄存器 CCGR0 的地址*/
ldr r1, =0XFFFFFFFF /* 32位数都为1*/
str r1, [r0]        /* 向地址指向的寄存器中写入32位1,开启所有时钟*/

ldr r0, =0X020C406C /* 寄存器 CCGR1 的地址*/
str r1, [r0]        /* 向地址指向的寄存器中写入32位1,开启所有时钟*/

ldr r0, =0X020C4070 /* 寄存器 CCGR2 的地址*/
str r1, [r0]        /* 向地址指向的寄存器中写入32位1,开启所有时钟*/

ldr r0, =0X020C4074 /* 寄存器 CCGR3 的地址*/
str r1, [r0]        /* 向地址指向的寄存器中写入32位1,开启所有时钟*/

ldr r0, =0X020C4078 /* 寄存器 CCGR4 的地址*/
str r1, [r0]        /* 向地址指向的寄存器中写入32位1,开启所有时钟*/

ldr r0, =0X020C407C /* 寄存器 CCGR5 的地址*/
str r1, [r0]        /* 向地址指向的寄存器中写入32位1,开启所有时钟*/

ldr r0, =0X020C4080 /* 寄存器 CCGR6 的地址*/
str r1, [r0]        /* 向地址指向的寄存器中写入32位1,开启所有时钟*/


/* 2、设置 GPIO1_IO03 复用为 GPIO1_IO03 */

ldr r0, =0X020E0068 /* 将寄存器 SW_MUX_GPIO1_IO03_BASE 的地址加载到 r0 中 */
ldr r1, =0X05       /* 设置寄存器 SW_MUX_GPIO1_IO03_BASE 的 MUX_MODE 为 5 */
str r1,[r0]         /* 向地址指向的寄存器中写入0101,复用为模式5即作为GPIO*/


/* 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     /*将配置的位拼凑为一个32位数据,备用的位填0*/
str r1,[r0]        /* 向地址指向的寄存器中写入0X10B0,配置GPIO功能*/


 /* 4、设置 GPIO1_IO03 为输出 */

ldr r0, =0X0209C004 /*寄存器 GPIO1_GDIR 的地址*/
ldr r1, =0X00000008
str r1,[r0]        /*向地址指向的寄存器中写入0X000008,因为是GPIO1组的第三个IO将第三位写1即 0000 0000 0000 0100*/

 /* 5、打开 LED0
  * 设置 GPIO1_IO03 输出低电平
  */

ldr r0, =0X0209C000 /*寄存器 GPIO1_DR 的地址*/
ldr r1, =0          /* 向地址指向的寄存器中写入0X00,设置为低电平*/
str r1,[r0]        /* 向地址指向的寄存器中写入0X00,设置为低电平*/

/*
 * 描述: loop 死循环
 */

 loop:
    b loop       
					/* 留空行*/

我们来详细的分析一下上面的汇编代码,我们以后分析代码都根据行号来分析。

  • 第 14 行定义了一个全局标号_start,代码就是从_start 这个标号开始顺序往下执行的。

  • 第 26 行使用 ldr 指令向寄存器 r0 写入 0X020C4068,也就是 r0=0X020C4068,这个是CCM_CCGR0 寄存器的地址。

  • 第 27行使用 ldr 指令向寄存器 r1 写入 0XFFFFFFFF,也就是 r1=0XFFFFFFFF。因为我们要开启所有的外设时钟,因此 CCM_CCGR0~CCM_CCGR6 所有寄存器的 32 位都要置 1,也就是写入0XFFFFFFFF。

  • 第 28 行使用 str 将 r1 中的值写入到 r0 所保存的地址中去,也就是给 0X020C4068 这个地址写入 0XFFFFFFFF,相当于 CCM_CCGR0=0XFFFFFFFF,就是打开 CCM_CCGR0 寄存器所控制的所有外设时钟。

  • 第 30~46 行都是向 CCM_CCGRX(X=1~6)寄存器写入 0XFFFFFFFF。这样我就通过汇编代码使能了 I.MX6U 的所有外设时钟。

  • 第51~53行是设置GPIO1_IO03的复用功能,GPIO1_IO03的复用寄存器地址为0X020E0068,寄存器 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的 MUX_MODE 设置为 5 就 是 将GPIO1_IO03 设置为GPIO。

  • 第 67~69 行 是 设 置 GPIO1_IO03 的 配 置 寄 存 器 , 也 就 是 寄 存 器IOMUX_SW_PAD_CTL_PAD_GPIO1_IO03 的值,此寄存器地址为 0X020E02F4,代码里面已经给出了这个寄存器详细的位设置。

  • 第 74~84 行是设置 GPIO 功能,经过上面几步操作,GPIO1_IO03 这个 IO 已经被配置为了GPIO功能,所以还需要设置跟 GPIO 有关的寄存器。第 74~76 行是设置 GPIO1->GDIR 寄存器,将GPIO1_IO03 设置为输出模式,也就是寄存器的 GPIO1_GDIR 的 bit3 置 1。 第 82~84 行设置GPIO1->DR 寄存器,也就是设置 GPIO1_IO03 的输出,我们要点亮开发板上的 LED0,那么 GPIO1_IO03 就必须输出低电平,所以这里设置 GPIO1_DR 寄存器为 0。 第 90~91 行是死循环,通过 b 指令,CPU重复不断的跳到 loop 函数执行,进入一个死循环。

其他

相关资源下载

i.MAX6ULL资料.rar-嵌入式文档类资源-CSDN下载

最近更新

查看本文最近更新请点击

欢迎关注微信公众号

weixingognzhonghaoerweima

猜你喜欢

转载自blog.csdn.net/aa1319594154/article/details/120206708