RT1021之LED小灯闪烁(EVK)

  • 第一步,先看看硬件部分,得看看连接到哪个GPIO口了

 

USER------GPIO_AD_B0_05 查阅手册观察GPIO引脚,默认引脚在数据手册(对应的管脚号)上:

 

 

 知道了对应引脚名称,然后在软件上配置寄存器

 

 

 复用功能为GPIO1 具体端口及引脚号 引脚名 复用功能序号

 USER------GPIO_AD_B0_05 -------GPIO1_IO05(后面有用*)

了解了对应的引脚,接下来就是软件部分设计,主要是对例程进行讲解

由于是第一次讲解M7架构的MCU,所以要讲的详细些,我们先引申出一个存储器映射的概念

存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射,具体见图 5‑5。如果给存储器再分配一个地址就叫存储器重映射。

 

image5

 

图 5‑5 Cortex-M7存储器映射

 

存储器区域功能划分

在这4GB的地址空间中,ARM已经粗线条的平均分成了8个块,,每个块也都规定了用途,具体分类见表格 5‑2。大部分块的大小都有512MB以上,显然这是非常大的,芯片厂商在每个块的范围内设计各具特色的外设时并不一定都用得完,都是只用了其中的一部分而已。表格 5‑2 内核划分的存储器功能分类

 

 

序号

用途

地址范围

Block 0

Code

0x0000 0000 ~ 0x1FFF FFFF(512MB)

Block 1

SRAM

0x2000 0000 ~ 0x3FFF FFFF(512MB)

Block 2

Peripheral片上外设

0x4000 0000 ~ 0x5FFF FFFF(512MB)

Block 3

External RAM(外部RAM)

0x6000 0000 ~ 0x9FFF FFFF (1024MB)

Block 4

External device(外部设备)

0xA000 0000 ~ 0xDFFF FFFF(1024MB)

Block 5

内核私有外设内部总线

0xE000 0000 ~ 0xE003 FFFF(256KB)

Block 6

内核私有外设外部总线

0xE004 0000 ~ 0xE00F FFFF(768KB)

Block 7

系统外设

0xE010 0000 ~ 0xFFFF FFFF(511MB)

 

 

 

 

 

在这8个Block里面,有这3个块非常重要,也是我们最关心的三个块。Block0主要用于存储程序代码,一般采用FLASH存储器,Block1主要用于运行时的内存,一般采用SRAM存储器,Block2用来设计成片上的外设,内核通过相应的地址访问片上外设。下面我们简单的介绍下这三个Block里面的具体区域的功能划分。存储器Block0内部区域功能划分根据ARM内核的设计,Block0主要用于存储程序代码,在i.MX RT1021芯片内部又把这部分划分了几个类型。也就是说NXP之类的芯片厂商在ARM内核的存储器功能划分草图之上,细致地根据自己的芯片需求设计出具体的功能分配方案。RT1021芯片对Block0内部区域的功能划分具体见表格首先是ITCM,ITCM是Instruction Tightly-Coupled Memory的缩写,译为指令紧耦合内存。所谓紧耦合是指该内存与内核连接紧密,有非常高的访问速度,而“指令”则表示该内存专用于缓存指令。对于那些我们希望有着极高执行速度的代码,我们可以要求内核上电后把相应的代码从外部FLASH加载至ITCM,那么在运行时,代码的执行速度就不会因外部FLASH的访问速度而存在瓶颈。第二部分是ROMCP,这是一小段ROM空间,用于存储芯片启动时的加载代码,即bootloader,bootloader负责把指令从外部存储器加载至ITCM。第三部分中的SEMC及FlexSPI是RT1052可用于控制外部并行及串行NorFlash的两个外设,此处把它们映射到此代码空间,是为了支持XIP功能(即指令直接在NorFlash上运行,不需要加载到内部的ITCM)。储存器Block1内部区域功能划分Block1用于设计片内的SRAM,也就是芯片运行时的内存,在i.MX RT1021芯片内部把这部分划分了两种RAM类型,Block1内部区域的功能划分具体见表格 5‑4。

 

第一种类型为DTCM,是Data Tightly-Coupled Memory的缩写,译为数据紧耦合内存,它跟ITCM类似,有着极高的访问速度,不过它是专门用来存储程序数据的,即代码中变量的存储位置。第二种类型为OCRAM,它是On-chip RAM的缩写,即片上内存,可以完全把它理解为传统MCU的内部SRAM,它没有像ITCM和DTCM的专用限制,可用于存储指令和数据(通用目的)。注意:在RT1052芯片中,前面提到的ITCM、DTCM及OCRAM地址范围均分配了512KB,但这并不是说这三种存储器都有512KB大小。实际上这三种存储器共享内部FlexRAM的空间,而这个内部FlexRAM空间在RT1052芯片中为512KB,在RT1060芯片中为1MB。另外,这三种存储器的空间是可以动态调整的,如在RT1052中,默认ITCM和DTCM各占128KB,OCRAM占256KB,也可以调整成ITCM占64KB,DTCM占128KB,OCRAM占320KB,一共有16种配置方式,具体可参考《Using the i.MX RT FlexRAM》文档。储存器Block2内部区域功能划分Block2用于设计片内的外设,在RT1052芯片中,它的外设使用4条总线与内核进行连接,它们的地址分配情况见表格 5‑5。表格 5‑5 存储器Block2 内部区域功能划分(此处仅列出AIPS总线相关部分)

 

 

表格 5‑6 AIPS-2总线上的地址分配

 

 

image9

 

该表格的最右一栏是挂载在AIPS-2总线上的外设名称,在后面的章节中我们会对相应的外设进行讲解。以表中阴影处为例,它表示一个名为GPIO1的外设的内存地址分配情况,而GPIO1外设下又包含了RT1021芯片相关的32个引脚,它被分配的内存地址为0x401B 8000~0x401B BFFF,也就是说通过访问这些地址就可以控制这32个引脚了,例如控制引脚输出高低电平,从而达到控制连接到引脚的LED灯亮灭的效果。我们知道,存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么叫寄存器映射?寄存器到底是什么?在上述存储器Block2这块区域,设计的是片上外设,在相应的地址空间内它们以四个字节为一个单元,共32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过C语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名对应的内存区就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。比如,我们找到GPIO1端口的输出数据寄存器DR的地址是0x401B 8000(至于这个地址如何找到可以先跳过,后面我们会有详细的讲解),DR寄存器是32bit,对应着32个外部IO,写0/1对应的的IO则输出低/高电平。现在我们通过C语言指针的操作方式,让GPIO1的32个IO都输出高电平,具体见代码清单 5‑1。

 

 

代码清单 5‑1通过绝对地址访问内存单元

 

// GPIO1端口全部输出 高电平
*(unsigned int*)(0x401B8000) = 0xFFFFFFFF;

 

0x401B 8000在我们看来是GPIO1端口数据输出寄存器DR的地址,但是在编译器看来,这只是一个普通的变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针,即(unsigned int *)0x401B 8000,然后再对这个指针进行 * 操作。刚刚我们说了,通过绝对地址访问内存单元不好记忆且容易出错,我们可以通过寄存器的方式来操作,具体见代码清单 5‑2。

代码清单 5‑2 通过寄存器别名方式访问内存单元

 

// GPIO1 端口全部输出 高电平
#define GPIO1_DR              (unsigned int*)(0x401B8000)
*GPIOF_DR = 0xFFFFFFFF;

 

为了方便操作,我们干脆把指针操作“*”也定义到寄存器别名里面,具体见代码清单 5‑3。

代码清单 5‑3通过寄存器别名访问内存单元

 

// GPIO1 端口全部输出 高电平
#define GPIO1_DR              *(unsigned int*)(0x401B8000)
GPIOF_DR = 0xFFFFFFFF;

 

片上外设区的四条AIPS总线挂载着不同的外设,相应总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。

总线基地址

表格 5‑7 总线基地址

总线名称总线基地址
AIPS-1 0x4000 0000
AIPS-2 0x4010 0000
AIPS-3 0x4020 0000
AIPS-4 0x4030 0000
外设基地址

 

总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为“XX外设基地址”,也叫XX外设的边界地址。具体有关RT1021外设的边界地址请参考《IMXRT1021RM》(参考手册)的第2章中的存储器映射的表。这里面我们以GPIO这个外设来讲解外设的基地址,具体见表格 5‑8。

 

表格 5‑8 外设GPIO基地址

 

外设名称外设基地址
GPIO1 0x401B 8000
GPIO2 0x401B C000
GPIO3 0x401C 0000
GPIO4 0x401C 4000
GPIO5 0x400C 0000

 

可以看到,GPIO1~GPIO4的外设基地址都位于AIPS-2总线地址的范围,而GPIO5比较特殊,它是属于AIPS-1总线的。不过除了所属总线及地址的差异外,它们的功能都一样。

 

外设寄存器

在XX外设的地址范围内,分布着的就是该外设的寄存器。以GPIO外设为例,GPIO是通用输入输出端口的简称,简单来说就是RT1052可控制的引脚,基本功能是控制引脚输出高电平或者低电平。最简单的应用就是把GPIO的引脚连接到LED灯的阴极,LED灯的阳极接电源,然后通过RT1021控制该引脚的电平,从而实现控制LED灯的亮灭。GPIO有很多个寄存器,每一个都有特定的功能。每个寄存器为32bit,占4个字节,在该外设的基地址上按照顺序排列,因此寄存器的位置可以用相对该外设基地址的偏移地址来描述。这里我们以GPIO1端口为例,来说明GPIO都有哪些寄存器,具体见表格 5‑8。表格 5‑9 GPIO1端口的寄存器地址列表

 

 

寄存器名称类型寄存器地址相对于GPOI1的地址偏移
GPIO1_DR 数据寄存器 0x401B 8000 0x00
GPIO1_GDIR 方向寄存器 0x401B 8004 0x04
GPIO1_PSR 状态寄存器 0x401B 8008 0x08
GPIO1_ICR1 中断配置寄存器 0x401B 800C 0x0C
GPIO1_ICR2 中断配置寄存器 0x401B 8010 0x10
GPIO1_IMR 中断掩码寄存器 0x401B 8014 0x14
GPIO1_ISR 中断状态寄存器 0x401B 8018 0x18
GPIO1_EDGE_SEL 边沿选择寄存器 0x401B 801C 0x1C

 

 

表中数据寄存器GPIO1_DR是GPIO1中的首个寄存器,所以它的寄存器地址与GPIO1的外设基地址相同,为0x401B 8000,它相对GPIO1基地址的偏移为0;紧挨着的是方向寄存器GPIO1_GDIR,由于前面的GPIO1_DR占据了4个字节,所以安排给它的地址也相对GPIO1_DR增加了4,最终地址为0x401B 8004,它相对GPIO1基地址增加了4,其余的寄存器地址安排依次类推。由于各个GPIO端口的控制方式完全一致,GPIO1~GPIO5都具有同样功能的寄存器用于控制对应端口引脚的特性,所以在《IMXRT1050RM》(参考手册)中对寄存器地址是统一用表格 5‑10来说明的。表格 5‑10 GPIO端口的 寄存器地址列表

 

image10表格上面的两行文字说明了GPIO1~GPIO5端口的基地址,然后表格列出各寄存器相对端口基地址的偏移。例如,想要知道GPIO2的中断掩码寄存器(IMR)实际地址时,可以根据公式算出:GPIO2 基地址:0x401B 8000+(2-1)×0x4000 = 0x401B C000IMR寄存器相对基地址的偏移为0x14,所以:GPIO2_IMR寄存器地址:0x401B C000 + 0x14 = 0x401B C014芯片对外设寄存器的这种统一安排不仅方便理解,也简化了程序中对寄存器地址的定义。有关外设的寄存器说明可参考《IMXRT1050RM》(参考手册)中具体章节的寄存器描述部分,在编程的时候我们需要反复的查阅外设的寄存器说明。这里我们以“GPIO中断配置寄存器GPIO_ICR1”为例,教大家如何理解寄存器的说明,具体见图 5‑6。

 

image11

image11

 

 

 

  • 寄存器名称

寄存器说明中首先列出了该寄存器中的名称,同类型外设的寄存器说明是通用的,也就是说,本例子中的GPIO1~GPIO5端口都有这样的一个寄存器,都适用于本说明。

  • 偏移地址

偏移地址是指本寄存器相对于这个外设的基地址的偏移。跟前面的说明一样,本寄存器的偏移地址是0x0C,从参考手册中我们可以查到GPIO1外设的基地址为0x401B 8000 ,我们就可以算出GPIO1的这个GPIO1_ICR1寄存器的地址为:0x401B 8000+0x0C ;同理,由于GPIO2的外设基地址为0x401B C000,可算出GPIO2_ICR1寄存器的地址为:0x401B C000+0x0C 。其他GPIO端口以此类推即可。

  • 寄存器功能说明

它简要地了本寄存器的主要功能,此处表明GPIO_ICR1寄存器包含16组2位的配置域,每个配置域控制一个输入引脚的中断配置。

  • 寄存器位表

紧接着的是本寄存器的位表,表中列出它的0-31位的名称及权限。表上方的数字为位编号,中间为位名称,侧面为读写权限,其中W表示只写,R表示只读,RW表示可读写。本寄存器中的位权限都是RW。有的寄存器位只读,一般是用于表示外设的某种工作状态的,由芯片硬件自动更改,程序通过读取那些寄存器位来判断外设的工作状态。表中最下一栏是对应寄存器位复位后的默认值,每位的值用0或1表示,本寄存器的复位值为全0。

  • 配置域功能说明

配置域功能是寄存器说明中最重要的部分,它详细介绍了寄存器每一个位的功能。例如本寄存器中只有一种寄存器配置域,即ICRn,其中的n数值可以是0-15,这里的0-15表示端口的引脚号,如ICR0用于控制GPIOx的第0个引脚,若x表示GPIO1,那就是控制GPIO1的第0引脚,而ICR1就是控制GPIO1第1个引脚。对于GPIO中的16~31号引脚,不在本寄存器的控制范围之内,它们使用另一个寄存器GPIO_ICR2进行配置(此处请注意区分寄存器GPIO_ICR1、GPIO_ICR2和配置域ICR0~ICR15的概念)。图中仅截取了ICR15配置域的说明,其余ICR14~ICR0的部分是完全一样的。该说明以通用的ICRn[1:0]表述,其中n就是代表引脚号的0~15,当ICRn被设置成二进制数00b、01b、10b或11b时分别可把对应引脚设置成低电平、高电平、上升沿或下降沿时引起中断(b表示2进制数,跟h表示16进制数的方式类似)。也就是说,当我们想配置GPIO1编号为15的引脚设置成上升沿引起中断时,需要把GPIO1_ICR1寄存器(地址为0x401B 800C)中的第31~30位写入二进制数10b,其它引脚类似。

 

C语言对寄存器的封装

以上所有的关于存储器映射的内容,最终都是为大家更好地理解如何用C语言控制读写外设寄存器做准备,此处是本章的重点内容。

封装外设基地址

在编程上为了方便理解和记忆,我们把外设基地址和寄存器地址都以相应的宏定义起来,外设或寄存器都以他们的名字作为宏名,具体见代码清单 5‑4。

代码清单 5‑4外设基地址及寄存器地址的宏定义
/* GPIO - 外设基地址 */
/** GPIO1 外设基地址 */
#define GPIO1_BASE                               (0x401B8000u)
/** GPIO2 外设基地址 */
#define GPIO2_BASE                               (0x401BC000u)
/** GPIO3 外设基地址 */
#define GPIO3_BASE                               (0x401C0000u)
/** GPIO4 外设基地址 */
#define GPIO4_BASE                               (0x401C4000u)
/** GPIO5 外设基地址 */
#define GPIO5_BASE                               (0x400C0000u)

/* 寄存器地址,以 GPIO1 为例*/
#define GPIO1_DR                                (GPIO1_BASE+0x00)
#define GPIO1_GDIR                              (GPIO1_BASE+0x04)
#define GPIO1_PSR                               (GPIO1_BASE+0x08)
#define GPIO1_ICR1                              (GPIO1_BASE+0x0C)
#define GPIO1_ICR2                              (GPIO1_BASE+0x10)
#define GPIO1_IMR                               (GPIO1_BASE+0x14)
#define GPIO1_ISR                               (GPIO1_BASE+0x18)
#define GPIO1_EDGE_SEL                          (GPIO1_BASE+0x1C)

这代码首先定义了GPIO1~GPIO5的外设基地址,最后在外设基地址上加入各寄存器的地址偏移,得到特定寄存器的地址。一旦有了具体地址,就可以用指针进行读写操作,具体见代码清单 5‑5。

代码清单 5‑5使用指针控制ICR1寄存器
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/* 控制GPIO1 引脚6配置为高电平引起中断
(GPIO1_ICR1寄存器的ICR6设置为01b,即0x01) */
/* 先对配置域ICR6的2个数据位清0 */
*(unsigned int *)GPIO1_ICR1 &= ~(0x3<<(2*6));
/* 给配置域ICR6的2个数据位赋值01b */
*(unsigned int *)GPIO1_ICR1 |= (0x01<<(2*6));

/* 控制GPIO1 引脚6配置为上升沿引起中断
(GPIO1_ICR1寄存器的ICR6设置为10b,即0x02) */
/* 先对配置域ICR6的2个数据位清0 */
*(unsigned int *)GPIO1_ICR1 &= ~(0x3<<(2*6));
/* 给配置域ICR6的2个数据位赋值10b */
*(unsigned int *)GPIO1_ICR1 |= (0x02<<(2*6));

unsigned int temp;
/* 控制GPIO1 端口所有引脚的电平(读DR寄存器) */
temp = *(unsigned int *)GPIO1_DR;

该代码使用 (unsigned int *) 把GPIO1_ICR1宏的数值强制转换成了地址,然后再用“*”号做取指针操作,对该地址的赋值,从而实现了写寄存器的功能(整个过程包括位清零及赋值两个过程,具体说明见本教程5.5.3 小节)。同样,读寄存器也是用取指针操作,把寄存器中的数据取到变量里,从而获取外设的状态。

 

 

封装寄存器列表

用上面的方法去定义地址,还是稍显繁琐,例如GPIO1~GPIO5都各有一组功能相同的寄存器,如GPIO1_DR/GPIO2_DR/GPIO3_DR等等,它们只是地址不一样,但却要为每个寄存器都定义它的地址。为了更方便地访问寄存器,我们引入C语言中的结构体语法对寄存器进行封装,具体见代码清单 5‑6。

使用结构体对GPIO寄存器组的封装
 
typedef unsigned           int uint32_t; /*无符号32位变量*/
typedef unsigned short     int uint16_t; /*无符号16位变量*/

/** GPIO - 寄存器列表  */
typedef struct {
   uint32_t DR;       /**< GPIO 数据寄存器, 地址偏移: 0x0 */
   uint32_t GDIR;     /**< GPIO 方向寄存器, 地址偏移: 0x4 */
   uint32_t PSR;      /**< GPIO 状态寄存器, 地址偏移: 0x8 */
   uint32_t ICR1;     /**< GPIO 中断配置寄存器1, 地址偏移: 0xC */
   uint32_t ICR2;     /**< GPIO 中断配置寄存器2, 地址偏移: 0x10 */
   uint32_t IMR;      /**< GPIO 中断掩码寄存器, 地址偏移: 0x14 */
   uint32_t ISR;      /**< GPIO 中断状态寄存器, 地址偏移: 0x18 */
   uint32_t EDGE_SEL; /**< GPIO 边沿选择寄存器, 地址偏移: 0x1C */
} GPIO_Type;

 

这段代码用typedef 关键字声明了名为GPIO_Type的结构体类型,结构体内有8个 成员变量,变量名正好对应寄存器的名字。C语言的语法规定,结构体内变量的存储空间是连续的,其中32位的变量

 

占用4个字节,16位的变量占用2个字节,具体见图 5‑7

 

image12

 

图 5‑7 GPIO_Type结构体成员的地址偏移也就是说,假如我们定义一个GPIO_Type 类型的结构体,且结构体的首地址为0x401B 8000(这也是第一个成员变量DR的地址), 那么结构体中第二个成员变量GDIR的地址即为0x401B 8000 +0x04 ,加上的这个0x04 ,正是代表DR所占用的4个字节地址的偏移量,其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给出。这样的地址偏移与RT1052的 GPIO外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存器了,具体见代码清单 5‑7。

代码清单 5‑7 通过结构体指针访问寄存器
GPIO_Type * GPIOx;        //定义一个GPIO_Type型结构体指针变量GPIOx
GPIOx = GPIO1_BASE;          //把指针地址设置为宏GPIO1_BASE地址
GPIOx->DR = 0xFFFF;         //通过指针访问并修改GPIO1_DR寄存器
GPIOx->GDIR = 0xFFFFFFFF;    //修改GPIO1_GDIR寄存器
GPIOx->ICR1 =0xFFFFFFFF;    //修改GPIO1_ICR1寄存器

uint32_t temp;
temp = GPIOx->DR;          //读取GPIOF_DR寄存器的值到变量temp中

这段代码先用GPIO_Type类型定义一个结构体指针GPIOx,并让指针指向地址GPIO1_BASE(0x401B 8000),使地址确定下来,然后根据C语言访问结构体的语法,用GPIOx->DR、GPIOx->GDIR及GPIOx->ICR1等方式读写寄存器。最后,我们更进一步,直接使用宏来定义好GPIO_Type类型的指针,而且指针指向各个GPIO端口的基地址,使用时我们直接用该宏访问寄存器即可,具体代码清单 5‑8。

代码清单 5‑8定义好GPIO端口基地址址针
/*使用GPIO_Type把地址强制转换成指针*/
#define GPIO1               ((GPIO_Type *) GPIO1_BASE)
#define GPIO2               ((GPIO_Type *) GPIO2_BASE)
#define GPIO3               ((GPIO_Type *) GPIO3_BASE)
#define GPIO4               ((GPIO_Type *) GPIO4_BASE)
#define GPIO5               ((GPIO_Type *) GPIO5_BASE)

/*使用定义好的宏直接访问*/
/*访问GPIO1端口的寄存器*/
GPIO1->DR = 0xFFFF;       //通过指针访问并修改GPIO1_DR寄存器
GPIO1->GDIR = 0xFFFFFFF;    //修改GPIO1_GDIR寄存器
GPIO1->ICR1 =0xFFFFFFF;    //修改GPIO1_ICR1寄存器

uint32_t temp;
temp = GPIO1->DR;          //读取GPIO1_DR寄存器的值到变量temp中

/*访问GPIO5端口的寄存器*/
GPIO5->DR = 0xFFFF;       //通过指针访问并修改GPIO5_DR寄存器
GPIO5->GDIR = 0xFFFFFFF;    //修改GPIO5_GDIR寄存器
GPIO5->ICR1 =0xFFFFFFF;    //修改GPIO5_ICR1寄存器

uint32_t temp;
temp = GPIO5->IDR;          //读取GPIO5_DR寄存器的值到变量temp中

这里我们仅是以GPIO这个外设为例,给大家讲解了C语言对寄存器的封装。以此类推,其他外设也同样可以用这种方法来封装。好消息是,这部分工作都由固件库帮我们完成了,这里我们只是分析了下这个封装的过程,让大家知其然,也只其所以然。

 

 修改寄存器的位操作方法

使用C语言对寄存器赋值时,我们常常要求只修改该寄存器的某几位的值,且其它的寄存器位不变,这个时候我们就需要用到C语言的位操作方法了。

把变量的某位清零

此处我们以变量a代表寄存器,并假设寄存器中本来已有数值,此时我们需要把变量a的某一位清零,且其它位不变,方法见代码清单 5‑9。

代码清单 5‑9 对某位清零
/定义一个变量a = 1001 1111 b (二进制数)
unsigned char a = 0x9f;

//对bit2 清零

a &= ~(1<<2);

//括号中的1左移两位,(1<<2)得二进制数:0000 0100 b
//按位取反,~(1<<2)得1111 1011 b
//假如a中原来的值为二进制数: a = 1001 1111 b
//所得的数与a作”位与&”运算,a = (1001 1111 b)&(1111 1011 b),
//经过运算后,a的值 a=1001 1011 b
// a的bit2 位被被零,而其它位不变。
 

 

 

把变量的某几个连续位清零

由于寄存器中有时会有连续几个寄存器位用于控制某个功能,现假设我们需要把寄存器的某几个连续位清零,且其它位不变,方法见代码清单 5‑10。

代码清单 5‑10 对某几个连续位清零
//若把a中的二进制位分成2个一组
//即bit0、bit1为第0组,bit2、bit3为第1组,
//  bit4、bit5为第2组,bit6、bit7为第3组
//要对第1组的bit2、bit3清零

a &= ~(3<<2*1);

//括号中的3左移两位,(3<<2*1)得二进制数:0000 1100 b
//按位取反,~(3<<2*1)得1111 0011 b
//假如a中原来的值为二进制数: a = 1001 1111 b
//所得的数与a作”位与&”运算,a = (1001 1111 b)&(1111 0011 b),
//经过运算后,a的值 a=1001 0011 b
// a的第1组的bit2、bit3被清零,而其它位不变。

//上述(~(3<<2*1))中的(1)即为组编号;如清零第3组bit6、bit7此处应为3
//括号中的(2)为每组的位数,每组有2个二进制位;若分成4个一组,此处即为4
//括号中的(3)是组内所有位都为1时的值;若分成4个一组,此处即为二进制数“1111 b”

//例如对第2组bit4、bit5清零
a &= ~(3<<2*2);

 

 

对变量的某几位进行赋值。

寄存器位经过上面的清零操作后,接下来就可以方便地对某几位写入所需要的数值了,且其它位不变,方法见代码清单 5‑11,这时候写入的数值一般就是需要设置寄存器的位参数。

代码清单 5‑11 对某几位进行赋值
//a = 1000 0011 b
//此时对清零后的第2组bit4、bit5设置成二进制数“01 b ”

a |= (1<<2*2);
//a = 1001 0011 b,成功设置了第2组的值,其它组不变

 

 

对变量的某位取反

某些情况下,我们需要对寄存器的某个位进行取反操作,即 1变0 ,0变1,这可以直接用如下操作,其它位不变,见代码清单 5‑12。

代码清单 5‑12 对某位进行取反操作
//a = 1001 0011 b
//把bit6取反,其它位不变

a ^=(1<<6);
//a = 1101 0011 b
GPIO是通用输入输出端口的简称,简单来说就是RT1052可控制的引脚,RT1052芯片的GPIO引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。RT1052芯片的GPIO被分成很多组,每组有32个引脚,如型号为MIMXRT1052型号的芯片有GPIO1、GPIO2至GPIO5共5组GPIO,芯片一共196个引脚,其中GPIO就占了一大部分,所有的GPIO引脚都有基本的输入输出功能。最基本的输出功能是由RT1052控制引脚输出高、低电平,实现开关控制,如把GPIO引脚接入到LED灯,那就可以控制LED灯的亮灭,引脚接入到继电器或三极管,那就可以通过继电器或三极管控制外部大功率电路的通断。最基本的输入功能是检测外部输入电平,如把GPIO引脚连接到按键,通过电平高低区分按键是否被按下。

 GPIO框图剖析

 上图为RT1021框图细节image1图 7‑1 GPIO结构框图通过GPIO硬件结构框图,就可以从整体上深入了解GPIO外设及它的各种应用模式。该图从最右端看起,①中的就表示RT1021芯片引出的GPIO引脚,其余部件都位于芯片内部。

7.2.1. 基本结构分析

下面我们按图 7‑1中的编号对GPIO端口的结构部件进行说明。

7.2.1.1. PAD

PAD代表了一个RT1052的GPIO引脚。在它的左侧是一系列信号通道及控制线,如input_on控制输入开关,Dir控制引脚的输入输出方向,Data_out控制引脚输出高低电平,Data_in作为信号输入,这些信号都经过一个IOMUX的器件连接到左侧的寄存器。另外,对于每个引脚都有很多关于属性的配置,这些配置是由图 7‑2中的框架结构实现的。image2图 7‑2 PAD接口框架

 ①PAD引脚

框图中的最右侧的PAD同样是代表一个RT1052的引脚。

 ②输出缓冲区

当输出缓冲区使能时,引脚被配置为输出模式。在输出缓冲区中,又包含了如下的属性配置:
  • DSE驱动能力
    DSE可以调整芯片内部与引脚串联电阻R0的大小,从而改变引脚的驱动能力。例如,R0的初始值为260欧姆,在3.3V电压下其电流驱动能力为12.69mA,通过DSE可以把R0的值配置为原值的1/2、1/3…1/7等。
  • SRE压摆率配置
    压摆率是指电压转换速率,可理解为电压由波谷升到波峰的时间。增大压摆率可减少输出电压的上升时间。RT1052的引脚通过SRE支持低速和高速压摆率这两种配置。压摆率是大信号特性,下面的带宽是小信号特性。
  • SPEED带宽配置
    通过SPEED可以设置IO的带宽,分别可设置为50MHz、100MHz以及200MHz。带宽的意思是能通过这个IO口最高的信号频率,通俗点讲就是方波不失真,如果超过这个频率方波就变正弦波。但是这个带宽要区别于IO的翻转速率,IO的翻转速率的信号来自于GPIO这个外设,而IO的带宽只是限制了IO口引脚的物理特性,IO口的信号可以来自于内部定时器输出的PWM信号,也可以来自于GPIO翻转输出的信号,两者相比之下,PWM信号的频率是远远高于GPIO翻转输出的信号频率。况且1052没有高速GPIO,GPIO的翻转率约为10M,1060系列则有高速GPIO,GPIO翻转速率达到了逆天的150M。但要使用1060的快速GPIO,需要改用GPIO编号。
  • ODE开漏输出配置
    通过ODE可以设置引脚是否工作在开漏输出模式。在该模式时引脚可以输出高阻态和低电平,输出高阻态时可由外部上拉电阻拉至高电平。开漏输出模式常用在一些通讯总线中,如I2C。

 ③输入缓冲区

当输入缓冲区使能时,引脚被配置为输入模式。在输入缓冲区中,又包含了如下的属性配置
  • HYS滞后使能
    RT1052的输入检测可以使用普通的CMOS检测或施密特触发器模式(滞后模式)。施密特触发器具有滞后效应,对正向和负向变化的输入信号有不同的阈值电压,常被用于电子开关、波形变换等场合,其转换特性和对比见图 7‑3及图 7‑4,如检测按键时,使用施密特模式即可起到消抖的功能。
image3图 7‑3施密特触发器的转换特性image4图 7‑4 在CMOS模式和滞后模式下的接收器输出

 ④Pull/Keeper上下拉、保持器

引脚的控制逻辑中还包含了上下拉、保持器的功能。芯片内部的上拉和下拉电阻可以将不确定的信号钳位在高、低电平,或小幅提高的电流输出能力,上拉提供输出电流,下拉提供输入电流。注意这些上下拉配置只是弱拉,对于类似I2C之类的总线,还是必须使用外部上拉电阻。RT1052芯片的电源模块中包含转换器,当转换器停止工作时,保持器会保持输入输出电压。上下拉、保持器可以通过如下属性配置:
  • PUS上下拉配置
    PUS可配置项可选为100K欧下拉以及22K欧、47K欧及100K欧上拉。
  • PUE上下拉、保持器选择
    上下拉功能和保持器功能是二选一的,可以通过PUE来选择。
  • PKE上下拉、保持器配置
    上下拉功能和保持器还通过PKE来控制是否使能。
注意,当引脚被配置为输出模式时,不管上下拉、保持器是什么配置,它们都会被关闭。 

IOMUX复用选择器

继续分析图 7‑1,图中标注的第2部分IOMUX译为IO复用选择器。RT1052的芯片每个GPIO都通过IOMUX支持多种功能,例如一个IO可用于网络外设ENET的数据接收引脚,也可以被配置成PWM外设的输出引脚,这样的设计大大增加了芯片的适用性,这样可选的功能就是由IOMUX实现的。IOMUX相当于增加了多根内部信号线与IO引脚相连,最多有8根,也就是说一个IO最多可支持8种可选的功能。IOMUX由其左侧的IOMUXC控制(C表示Controler),IOMUXC提供寄存器给用户进行配置,它又分成MUX Mode(IO模式控制)以及Pad Settings(Pad配置)两个部分:
  • MUX Mode配置
    MUX Mode就是用来配置引脚的复用功能,按上面的例子,即是具体是用于网络外设ENET的数据接收,还是用于PWM外设的输出引脚,当然,也可以配置成普通的IO口,仅用于控制输出高低电平。
  • Pad Settings配置
    Pad Settings用于配置引脚的属性,例如驱动能力,是否使用上下拉电阻,是否使用保持器,是否使用开漏模式以及使用施密特模式还是CMOS模式等。关于属性的介绍会在后面给出,在学习各种外设时,也将会接触到这些属性在不同场合下的应用。
在IOMUXC外设中关于MUX Mode和Pad Settings寄存器命名格式见表格 7‑1。表格 7‑1 IOMUXC寄存器命名的方式
IOMUXC控制类型寄存器名称
MUX Mode IOMUXC_SW_MUX_CTL_PAD_XXXX
Pad Settings IOMUXC_SW_PAD_CTL_PAD_XXXX
每个引脚都包含这两个寄存器,表中的XXXX表示引脚的名字,例如本芯片中有一个名为GPIO_AD_B0_09的引脚,编号为GPIO1.9,你可以在参考手册的IOMUXC章节中找到它的这两个寄存器:IOMUXC_SW_MUX_CTL_PAD_ GPIO_AD_B0_09以及IOMUXC_SW_PAD_CTL_PAD_ GPIO_AD_B0_09,以下简称MUX寄存器及PAD寄存器。根据寄存器说明即可对该引脚进行相应的配置。

 IOMUXC_SW_MUX_CTL_PAD_XXXX引脚模式寄存器

下面以GPIO_AD_B0_09引脚为例对 MUX寄存器进行说明,该引脚相应的MUX寄存器在参考手册中的描述见图 7‑5。image5图 7‑5 参考手册中对GPIO1.9引脚MUX配置寄存器的说明 可以看到,该寄存器主要有两个配置域,分别是SION和MUX_MODE,其中SION用于设置引脚在输出模式下同时开启输入通道。重点是MUX_MODE的配置,它使用3个寄存器位表示可选的ALT0~ALT7这8个模式,如ALT2模式就是用于ENET以太网外设的数据接收信号;若配置为ALT4则引脚会连接至CSI摄像头外设,作为数据信号;配置为ALT5时,该引脚则用于最基础的GPIO模式,简单地通过后面介绍的GPIO外设控制输入输出。

 IOMUXC_SW_PAD_CTL_PAD_XXXX引脚属性寄存器

类似地,以GPIO_AD_B0_09引脚中PAD寄存器在参考手册中的描述见图 7‑6。image6图 7‑6 参考手册中对GPIO1.9引脚PAD配置寄存器的说明(部分)相对来说PAD寄存器的配置项就更丰富了,而且图中仅是该寄存器的部分说明,仔细看这些配置项,它们就是前面图 7‑2介绍的各项属性,如HYS设置使用施密特模式的滞后功能,PUS配置上下拉电阻的阻值,其它的还包含PUE、PKE、ODE、SPEED、DSE及SRE的配置。

Block外设功能控制块

Block是外设功能控制块,例如具有ENET的数据接收功能的引脚,它就需要网络外设ENET的支持,具有PWM输出功能的引脚,它需要PWM外设的支持,这些外设在芯片内部会有独立的功能逻辑控制块,这些控制块通过IOMUX的复用信号与IO引脚相连。使用时通过IOMUX选择具体哪个外设连接到IO。

GPIO外设

GPIO模块是每个IO都具有的外设,它具有IO控制最基本的功能,如输出高低电平、检测电平输入等。它也占用IOMUX分配的复用信号,也就是说使用GPIO模块功能时同样需要使用IOMUX选中GPIO外设。图中的GPIO.DR、GPIO.GDIR、GPIO.PSR等是指GPIO外设相关的控制寄存器,它们分别是数据寄存器、方向寄存器以及引脚状态寄存器,功能介绍如下:

GPIO.GDIR方向寄存器

控制一个GPIO引脚时,要先用GDIR方向寄存器配置该引脚用于输出电平信号还是用作输入检测。典型的例子是使用输出模式可以控制LED灯的亮灭,输入模式时可以用来检测按键是否按下。GDIR寄存器的每一个数据位代表一个引脚的方向,对应的位被设置为0时该引脚为输入模式,被设置为1时该引脚为输出模式,具体见图 7‑7。image7图 7‑7 参考手册中对GDIR的寄存器说明例如,对GPIO1的GDIR寄存器的bit3位被写入为1,那么GPIO1.3引脚的模式即为输出。

GPIO.DR数据寄存器

DR数据寄存器直接代表了引脚的电平状态,它也使用1个数据位表示1个引脚的电平,每位用1表示高电平,用0表示低电平。DR寄存器在参考手册中的说明见图 7‑8。image8图 7‑8 参考手册中对DR数据寄存器的说明当GDIR方向寄存器设置引脚为输出模式时,写入DR数据寄存器对应的位即可控制该引脚输出的电平状态,如这时GPIO1的DR寄存器的bit3被写入为1,则引脚为输出高电平。当GDIR方向寄存器设置引脚为输入模式时,读取DR数据寄存器对应的位即可获取该引脚当前的输入电平状态,例如这里读取GPIO1的DR寄存器的bit3,得到该位的值为0,表示当前引脚的输入状态为低电平。

GPIO.PSR引脚状态寄存器

PSR引脚状态寄存器相当于DR寄存器的简化版,它仅在GDIR方向寄存器设置为输入模式时有效,它的每个位表示一个引脚当前的输入电平状态。PSR寄存器的权限是只读的,对它进行写操作是无效的。特别地,当引脚被配置成输出模式时,若IOMUXC中的MUX寄存器使能了SION功能(输出通道回环至输入),可以通过PSR寄存器读取回引脚的状态值。

与其它引脚的连接

GPIO功能框中的第5项表示另一个引脚PAD2,它与PAD1有一根信号线连接,表示部分引脚的输出可以作为另一个引脚的输入。

 实验:使用寄存器点亮LED灯

本小节中,我们以实例讲解如何通过控制寄存器来点亮LED灯。此处侧重于讲解原理,请您直接用KEIL5软件打开我们提供的实验例程配合阅读,先了解原理,学习完本小节后,再尝试自己建立一个同样的工程。本节配套例程名称为“使用寄存器点亮LED灯”,在工程目录下找到后缀为“.uvprojx”的文件,用KEIL5打开即可。 打开该工程,具体见图 7‑9,可看到一共有三个文件,分别startup_MIMXRT1052.s、MIMXRT1052.h 以及main.c,接下来我们讲会对这三个工程文件进行讲解。image9图 7‑9 工程文件结构

硬件连接

在本教程中核心板上RT1021芯片的GPIO1_IO05引脚与一个LED灯连接,这可以在核心板原理图查到,具体见图 7‑10。注意:如果以后硬件有升级,则以最新的硬件接线为主,不要纠结与具体使用的是哪个IO,重点是要掌握原理。
 图 7‑10表示LED灯的阳极串联一个电阻与电源3.3V相连,阴极与标号为JTAG_nTRST的引脚相连,所以当JTAG_nTRST引脚为低电平的时候,LED灯就会被点亮。在RT1052芯片中这个JTAG_nTRST引脚同时是GPIO1_IO05引脚,即GPIO1端口编号05的引脚。   单看LED灯部分的原理图,有时并不清楚这个JTAG_TDI引脚与电路中的其它模块是如何连接的,这时就可以在pdf软件的搜索栏输入要查询的引脚标号查看其它模块的连接了,见图 7‑11。  图 7‑11在原理图中查找引脚标号,摘自《i.MX RT1021核心板原理图》这个电路模块包含了RT1021芯片的部分引脚,从中可以查询到标号为JTAG_nTRST的信号直接与芯片的GPIO_AD_B0_05引脚相连,在芯片的参考手册中,就是直接用这个引脚名命名寄存器的,如它对应的MUX寄存器名为:SW_MUX_CTL_PAD_GPIO_AD_B0_05,了解这些在后续写程序的时候会方便我们查阅相关资料。现在,我们的目标是把这个与LED灯相连的GPIO_IO05(GPIO_AD_B0_09)引脚设置成输出模式并且输出低电平,这样就能让LED灯亮起来了。

MIMXRT1021.h文件

看完启动文件,那我们立即写SystemInit和main函数吧?别着急,定义好了SystemInit函数和main我们又能写什么内容?连接LED灯的GPIO引脚,是要通过读写寄存器来控制的,就这样空着手,如何控制寄存器。在上一章,我们知道寄存器就是特殊的内存空间,可以通过指针操作访问寄存器。所以此处我们根据RT1052的存储器映射先定义好各个寄存器的地址,把这些地址定义都统一写在MIMXRT1052.h文件中,见代码清单 7‑3。
代码清单 7‑3 外设地址定义
#define GPIO_DR_DR_MASK                          (0xFFFFFFFFU)
#define GPIO_DR_DR_SHIFT                         (0U)
/*! DR - DR data bits
 */
#define GPIO_DR_DR(x)                            (((uint32_t)(((uint32_t)(x)) << GPIO_DR_DR_SHIFT)) & GPIO_DR_DR_MASK)
/*! @} */

/*! @name GDIR - GPIO direction register */
/*! @{ */

#define GPIO_GDIR_GDIR_MASK                      (0xFFFFFFFFU)
#define GPIO_GDIR_GDIR_SHIFT                     (0U)
/*! GDIR - GPIO direction bits
 */
#define GPIO_GDIR_GDIR(x)                        (((uint32_t)(((uint32_t)(x)) << GPIO_GDIR_GDIR_SHIFT)) & GPIO_GDIR_GDIR_MASK)
/*! @} */

/*! @name PSR - GPIO pad status register */
/*! @{ */

#define GPIO_PSR_PSR_MASK                        (0xFFFFFFFFU)
#define GPIO_PSR_PSR_SHIFT                       (0U)
/*! PSR - GPIO pad status bits
 */
#define GPIO_PSR_PSR(x)                          (((uint32_t)(((uint32_t)(x)) << GPIO_PSR_PSR_SHIFT)) & GPIO_PSR_PSR_MASK)
/*! @} */

/*! @name ICR1 - GPIO interrupt configuration register1 */
/*! @{ */

#define GPIO_ICR1_ICR0_MASK                      (0x3U)
#define GPIO_ICR1_ICR0_SHIFT                     (0U)
/*! ICR0 - Interrupt configuration field for GPIO interrupt 0
 *  0b00..Interrupt 0 is low-level sensitive.
 *  0b01..Interrupt 0 is high-level sensitive.
 *  0b10..Interrupt 0 is rising-edge sensitive.
 *  0b11..Interrupt 0 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR0(x)                        (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR0_SHIFT)) & GPIO_ICR1_ICR0_MASK)

#define GPIO_ICR1_ICR1_MASK                      (0xCU)
#define GPIO_ICR1_ICR1_SHIFT                     (2U)
/*! ICR1 - Interrupt configuration field for GPIO interrupt 1
 *  0b00..Interrupt 1 is low-level sensitive.
 *  0b01..Interrupt 1 is high-level sensitive.
 *  0b10..Interrupt 1 is rising-edge sensitive.
 *  0b11..Interrupt 1 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR1(x)                        (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR1_SHIFT)) & GPIO_ICR1_ICR1_MASK)

#define GPIO_ICR1_ICR2_MASK                      (0x30U)
#define GPIO_ICR1_ICR2_SHIFT                     (4U)
/*! ICR2 - Interrupt configuration field for GPIO interrupt 2
 *  0b00..Interrupt 2 is low-level sensitive.
 *  0b01..Interrupt 2 is high-level sensitive.
 *  0b10..Interrupt 2 is rising-edge sensitive.
 *  0b11..Interrupt 2 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR2(x)                        (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR2_SHIFT)) & GPIO_ICR1_ICR2_MASK)

#define GPIO_ICR1_ICR3_MASK                      (0xC0U)
#define GPIO_ICR1_ICR3_SHIFT                     (6U)
/*! ICR3 - Interrupt configuration field for GPIO interrupt 3
 *  0b00..Interrupt 3 is low-level sensitive.
 *  0b01..Interrupt 3 is high-level sensitive.
 *  0b10..Interrupt 3 is rising-edge sensitive.
 *  0b11..Interrupt 3 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR3(x)                        (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR3_SHIFT)) & GPIO_ICR1_ICR3_MASK)

#define GPIO_ICR1_ICR4_MASK                      (0x300U)
#define GPIO_ICR1_ICR4_SHIFT                     (8U)
/*! ICR4 - Interrupt configuration field for GPIO interrupt 4
 *  0b00..Interrupt 4 is low-level sensitive.
 *  0b01..Interrupt 4 is high-level sensitive.
 *  0b10..Interrupt 4 is rising-edge sensitive.
 *  0b11..Interrupt 4 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR4(x)                        (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR4_SHIFT)) & GPIO_ICR1_ICR4_MASK)

#define GPIO_ICR1_ICR5_MASK                      (0xC00U)
#define GPIO_ICR1_ICR5_SHIFT                     (10U)
/*! ICR5 - Interrupt configuration field for GPIO interrupt 5
 *  0b00..Interrupt 5 is low-level sensitive.
 *  0b01..Interrupt 5 is high-level sensitive.
 *  0b10..Interrupt 5 is rising-edge sensitive.
 *  0b11..Interrupt 5 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR5(x)                        (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR5_SHIFT)) & GPIO_ICR1_ICR5_MASK)

#define GPIO_ICR1_ICR6_MASK                      (0x3000U)
#define GPIO_ICR1_ICR6_SHIFT                     (12U)
/*! ICR6 - Interrupt configuration field for GPIO interrupt 6
 *  0b00..Interrupt 6 is low-level sensitive.
 *  0b01..Interrupt 6 is high-level sensitive.
 *  0b10..Interrupt 6 is rising-edge sensitive.
 *  0b11..Interrupt 6 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR6(x)                        (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR6_SHIFT)) & GPIO_ICR1_ICR6_MASK)

#define GPIO_ICR1_ICR7_MASK                      (0xC000U)
#define GPIO_ICR1_ICR7_SHIFT                     (14U)
/*! ICR7 - Interrupt configuration field for GPIO interrupt 7
 *  0b00..Interrupt 7 is low-level sensitive.
 *  0b01..Interrupt 7 is high-level sensitive.
 *  0b10..Interrupt 7 is rising-edge sensitive.
 *  0b11..Interrupt 7 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR7(x)                        (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR7_SHIFT)) & GPIO_ICR1_ICR7_MASK)

#define GPIO_ICR1_ICR8_MASK                      (0x30000U)
#define GPIO_ICR1_ICR8_SHIFT                     (16U)
/*! ICR8 - Interrupt configuration field for GPIO interrupt 8
 *  0b00..Interrupt 8 is low-level sensitive.
 *  0b01..Interrupt 8 is high-level sensitive.
 *  0b10..Interrupt 8 is rising-edge sensitive.
 *  0b11..Interrupt 8 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR8(x)                        (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR8_SHIFT)) & GPIO_ICR1_ICR8_MASK)

#define GPIO_ICR1_ICR9_MASK                      (0xC0000U)
#define GPIO_ICR1_ICR9_SHIFT                     (18U)
/*! ICR9 - Interrupt configuration field for GPIO interrupt 9
 *  0b00..Interrupt 9 is low-level sensitive.
 *  0b01..Interrupt 9 is high-level sensitive.
 *  0b10..Interrupt 9 is rising-edge sensitive.
 *  0b11..Interrupt 9 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR9(x)                        (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR9_SHIFT)) & GPIO_ICR1_ICR9_MASK)

#define GPIO_ICR1_ICR10_MASK                     (0x300000U)
#define GPIO_ICR1_ICR10_SHIFT                    (20U)
/*! ICR10 - Interrupt configuration field for GPIO interrupt 10
 *  0b00..Interrupt 10 is low-level sensitive.
 *  0b01..Interrupt 10 is high-level sensitive.
 *  0b10..Interrupt 10 is rising-edge sensitive.
 *  0b11..Interrupt 10 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR10(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR10_SHIFT)) & GPIO_ICR1_ICR10_MASK)

#define GPIO_ICR1_ICR11_MASK                     (0xC00000U)
#define GPIO_ICR1_ICR11_SHIFT                    (22U)
/*! ICR11 - Interrupt configuration field for GPIO interrupt 11
 *  0b00..Interrupt 11 is low-level sensitive.
 *  0b01..Interrupt 11 is high-level sensitive.
 *  0b10..Interrupt 11 is rising-edge sensitive.
 *  0b11..Interrupt 11 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR11(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR11_SHIFT)) & GPIO_ICR1_ICR11_MASK)

#define GPIO_ICR1_ICR12_MASK                     (0x3000000U)
#define GPIO_ICR1_ICR12_SHIFT                    (24U)
/*! ICR12 - Interrupt configuration field for GPIO interrupt 12
 *  0b00..Interrupt 12 is low-level sensitive.
 *  0b01..Interrupt 12 is high-level sensitive.
 *  0b10..Interrupt 12 is rising-edge sensitive.
 *  0b11..Interrupt 12 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR12(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR12_SHIFT)) & GPIO_ICR1_ICR12_MASK)

#define GPIO_ICR1_ICR13_MASK                     (0xC000000U)
#define GPIO_ICR1_ICR13_SHIFT                    (26U)
/*! ICR13 - Interrupt configuration field for GPIO interrupt 13
 *  0b00..Interrupt 13 is low-level sensitive.
 *  0b01..Interrupt 13 is high-level sensitive.
 *  0b10..Interrupt 13 is rising-edge sensitive.
 *  0b11..Interrupt 13 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR13(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR13_SHIFT)) & GPIO_ICR1_ICR13_MASK)

#define GPIO_ICR1_ICR14_MASK                     (0x30000000U)
#define GPIO_ICR1_ICR14_SHIFT                    (28U)
/*! ICR14 - Interrupt configuration field for GPIO interrupt 14
 *  0b00..Interrupt 14 is low-level sensitive.
 *  0b01..Interrupt 14 is high-level sensitive.
 *  0b10..Interrupt 14 is rising-edge sensitive.
 *  0b11..Interrupt 14 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR14(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR14_SHIFT)) & GPIO_ICR1_ICR14_MASK)

#define GPIO_ICR1_ICR15_MASK                     (0xC0000000U)
#define GPIO_ICR1_ICR15_SHIFT                    (30U)
/*! ICR15 - Interrupt configuration field for GPIO interrupt 15
 *  0b00..Interrupt 15 is low-level sensitive.
 *  0b01..Interrupt 15 is high-level sensitive.
 *  0b10..Interrupt 15 is rising-edge sensitive.
 *  0b11..Interrupt 15 is falling-edge sensitive.
 */
#define GPIO_ICR1_ICR15(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR1_ICR15_SHIFT)) & GPIO_ICR1_ICR15_MASK)
/*! @} */

/*! @name ICR2 - GPIO interrupt configuration register2 */
/*! @{ */

#define GPIO_ICR2_ICR16_MASK                     (0x3U)
#define GPIO_ICR2_ICR16_SHIFT                    (0U)
/*! ICR16 - Interrupt configuration field for GPIO interrupt 16
 *  0b00..Interrupt 16 is low-level sensitive.
 *  0b01..Interrupt 16 is high-level sensitive.
 *  0b10..Interrupt 16 is rising-edge sensitive.
 *  0b11..Interrupt 16 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR16(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR16_SHIFT)) & GPIO_ICR2_ICR16_MASK)

#define GPIO_ICR2_ICR17_MASK                     (0xCU)
#define GPIO_ICR2_ICR17_SHIFT                    (2U)
/*! ICR17 - Interrupt configuration field for GPIO interrupt 17
 *  0b00..Interrupt 17 is low-level sensitive.
 *  0b01..Interrupt 17 is high-level sensitive.
 *  0b10..Interrupt 17 is rising-edge sensitive.
 *  0b11..Interrupt 17 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR17(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR17_SHIFT)) & GPIO_ICR2_ICR17_MASK)

#define GPIO_ICR2_ICR18_MASK                     (0x30U)
#define GPIO_ICR2_ICR18_SHIFT                    (4U)
/*! ICR18 - Interrupt configuration field for GPIO interrupt 18
 *  0b00..Interrupt 18 is low-level sensitive.
 *  0b01..Interrupt 18 is high-level sensitive.
 *  0b10..Interrupt 18 is rising-edge sensitive.
 *  0b11..Interrupt 18 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR18(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR18_SHIFT)) & GPIO_ICR2_ICR18_MASK)

#define GPIO_ICR2_ICR19_MASK                     (0xC0U)
#define GPIO_ICR2_ICR19_SHIFT                    (6U)
/*! ICR19 - Interrupt configuration field for GPIO interrupt 19
 *  0b00..Interrupt 19 is low-level sensitive.
 *  0b01..Interrupt 19 is high-level sensitive.
 *  0b10..Interrupt 19 is rising-edge sensitive.
 *  0b11..Interrupt 19 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR19(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR19_SHIFT)) & GPIO_ICR2_ICR19_MASK)

#define GPIO_ICR2_ICR20_MASK                     (0x300U)
#define GPIO_ICR2_ICR20_SHIFT                    (8U)
/*! ICR20 - Interrupt configuration field for GPIO interrupt 20
 *  0b00..Interrupt 20 is low-level sensitive.
 *  0b01..Interrupt 20 is high-level sensitive.
 *  0b10..Interrupt 20 is rising-edge sensitive.
 *  0b11..Interrupt 20 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR20(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR20_SHIFT)) & GPIO_ICR2_ICR20_MASK)

#define GPIO_ICR2_ICR21_MASK                     (0xC00U)
#define GPIO_ICR2_ICR21_SHIFT                    (10U)
/*! ICR21 - Interrupt configuration field for GPIO interrupt 21
 *  0b00..Interrupt 21 is low-level sensitive.
 *  0b01..Interrupt 21 is high-level sensitive.
 *  0b10..Interrupt 21 is rising-edge sensitive.
 *  0b11..Interrupt 21 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR21(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR21_SHIFT)) & GPIO_ICR2_ICR21_MASK)

#define GPIO_ICR2_ICR22_MASK                     (0x3000U)
#define GPIO_ICR2_ICR22_SHIFT                    (12U)
/*! ICR22 - Interrupt configuration field for GPIO interrupt 22
 *  0b00..Interrupt 22 is low-level sensitive.
 *  0b01..Interrupt 22 is high-level sensitive.
 *  0b10..Interrupt 22 is rising-edge sensitive.
 *  0b11..Interrupt 22 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR22(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR22_SHIFT)) & GPIO_ICR2_ICR22_MASK)

#define GPIO_ICR2_ICR23_MASK                     (0xC000U)
#define GPIO_ICR2_ICR23_SHIFT                    (14U)
/*! ICR23 - Interrupt configuration field for GPIO interrupt 23
 *  0b00..Interrupt 23 is low-level sensitive.
 *  0b01..Interrupt 23 is high-level sensitive.
 *  0b10..Interrupt 23 is rising-edge sensitive.
 *  0b11..Interrupt 23 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR23(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR23_SHIFT)) & GPIO_ICR2_ICR23_MASK)

#define GPIO_ICR2_ICR24_MASK                     (0x30000U)
#define GPIO_ICR2_ICR24_SHIFT                    (16U)
/*! ICR24 - Interrupt configuration field for GPIO interrupt 24
 *  0b00..Interrupt 24 is low-level sensitive.
 *  0b01..Interrupt 24 is high-level sensitive.
 *  0b10..Interrupt 24 is rising-edge sensitive.
 *  0b11..Interrupt 24 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR24(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR24_SHIFT)) & GPIO_ICR2_ICR24_MASK)

#define GPIO_ICR2_ICR25_MASK                     (0xC0000U)
#define GPIO_ICR2_ICR25_SHIFT                    (18U)
/*! ICR25 - Interrupt configuration field for GPIO interrupt 25
 *  0b00..Interrupt 25 is low-level sensitive.
 *  0b01..Interrupt 25 is high-level sensitive.
 *  0b10..Interrupt 25 is rising-edge sensitive.
 *  0b11..Interrupt 25 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR25(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR25_SHIFT)) & GPIO_ICR2_ICR25_MASK)

#define GPIO_ICR2_ICR26_MASK                     (0x300000U)
#define GPIO_ICR2_ICR26_SHIFT                    (20U)
/*! ICR26 - Interrupt configuration field for GPIO interrupt 26
 *  0b00..Interrupt 26 is low-level sensitive.
 *  0b01..Interrupt 26 is high-level sensitive.
 *  0b10..Interrupt 26 is rising-edge sensitive.
 *  0b11..Interrupt 26 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR26(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR26_SHIFT)) & GPIO_ICR2_ICR26_MASK)

#define GPIO_ICR2_ICR27_MASK                     (0xC00000U)
#define GPIO_ICR2_ICR27_SHIFT                    (22U)
/*! ICR27 - Interrupt configuration field for GPIO interrupt 27
 *  0b00..Interrupt 27 is low-level sensitive.
 *  0b01..Interrupt 27 is high-level sensitive.
 *  0b10..Interrupt 27 is rising-edge sensitive.
 *  0b11..Interrupt 27 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR27(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR27_SHIFT)) & GPIO_ICR2_ICR27_MASK)

#define GPIO_ICR2_ICR28_MASK                     (0x3000000U)
#define GPIO_ICR2_ICR28_SHIFT                    (24U)
/*! ICR28 - Interrupt configuration field for GPIO interrupt 28
 *  0b00..Interrupt 28 is low-level sensitive.
 *  0b01..Interrupt 28 is high-level sensitive.
 *  0b10..Interrupt 28 is rising-edge sensitive.
 *  0b11..Interrupt 28 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR28(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR28_SHIFT)) & GPIO_ICR2_ICR28_MASK)

#define GPIO_ICR2_ICR29_MASK                     (0xC000000U)
#define GPIO_ICR2_ICR29_SHIFT                    (26U)
/*! ICR29 - Interrupt configuration field for GPIO interrupt 29
 *  0b00..Interrupt 29 is low-level sensitive.
 *  0b01..Interrupt 29 is high-level sensitive.
 *  0b10..Interrupt 29 is rising-edge sensitive.
 *  0b11..Interrupt 29 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR29(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR29_SHIFT)) & GPIO_ICR2_ICR29_MASK)

#define GPIO_ICR2_ICR30_MASK                     (0x30000000U)
#define GPIO_ICR2_ICR30_SHIFT                    (28U)
/*! ICR30 - Interrupt configuration field for GPIO interrupt 30
 *  0b00..Interrupt 30 is low-level sensitive.
 *  0b01..Interrupt 30 is high-level sensitive.
 *  0b10..Interrupt 30 is rising-edge sensitive.
 *  0b11..Interrupt 30 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR30(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR30_SHIFT)) & GPIO_ICR2_ICR30_MASK)

#define GPIO_ICR2_ICR31_MASK                     (0xC0000000U)
#define GPIO_ICR2_ICR31_SHIFT                    (30U)
/*! ICR31 - Interrupt configuration field for GPIO interrupt 31
 *  0b00..Interrupt 31 is low-level sensitive.
 *  0b01..Interrupt 31 is high-level sensitive.
 *  0b10..Interrupt 31 is rising-edge sensitive.
 *  0b11..Interrupt 31 is falling-edge sensitive.
 */
#define GPIO_ICR2_ICR31(x)                       (((uint32_t)(((uint32_t)(x)) << GPIO_ICR2_ICR31_SHIFT)) & GPIO_ICR2_ICR31_MASK)
/*! @} */

/*! @name IMR - GPIO interrupt mask register */
/*! @{ */

#define GPIO_IMR_IMR_MASK                        (0xFFFFFFFFU)
#define GPIO_IMR_IMR_SHIFT                       (0U)
/*! IMR - Interrupt Mask bits
 */
#define GPIO_IMR_IMR(x)                          (((uint32_t)(((uint32_t)(x)) << GPIO_IMR_IMR_SHIFT)) & GPIO_IMR_IMR_MASK)
/*! @} */

/*! @name ISR - GPIO interrupt status register */
/*! @{ */

#define GPIO_ISR_ISR_MASK                        (0xFFFFFFFFU)
#define GPIO_ISR_ISR_SHIFT                       (0U)
/*! ISR - Interrupt status bits
 */
#define GPIO_ISR_ISR(x)                          (((uint32_t)(((uint32_t)(x)) << GPIO_ISR_ISR_SHIFT)) & GPIO_ISR_ISR_MASK)
/*! @} */

/*! @name EDGE_SEL - GPIO edge select register */
/*! @{ */

#define GPIO_EDGE_SEL_GPIO_EDGE_SEL_MASK         (0xFFFFFFFFU)
#define GPIO_EDGE_SEL_GPIO_EDGE_SEL_SHIFT        (0U)
/*! GPIO_EDGE_SEL - Edge select
 */
#define GPIO_EDGE_SEL_GPIO_EDGE_SEL(x)           (((uint32_t)(((uint32_t)(x)) << GPIO_EDGE_SEL_GPIO_EDGE_SEL_SHIFT)) & GPIO_EDGE_SEL_GPIO_EDGE_SEL_MASK)
/*! @} */

/*! @name DR_SET - GPIO data register SET */
/*! @{ */

#define GPIO_DR_SET_DR_SET_MASK                  (0xFFFFFFFFU)
#define GPIO_DR_SET_DR_SET_SHIFT                 (0U)
/*! DR_SET - Set
 */
#define GPIO_DR_SET_DR_SET(x)                    (((uint32_t)(((uint32_t)(x)) << GPIO_DR_SET_DR_SET_SHIFT)) & GPIO_DR_SET_DR_SET_MASK)
/*! @} */

/*! @name DR_CLEAR - GPIO data register CLEAR */
/*! @{ */

#define GPIO_DR_CLEAR_DR_CLEAR_MASK              (0xFFFFFFFFU)
#define GPIO_DR_CLEAR_DR_CLEAR_SHIFT             (0U)
/*! DR_CLEAR - Clear
 */
#define GPIO_DR_CLEAR_DR_CLEAR(x)                (((uint32_t)(((uint32_t)(x)) << GPIO_DR_CLEAR_DR_CLEAR_SHIFT)) & GPIO_DR_CLEAR_DR_CLEAR_MASK)
/*! @} */

/*! @name DR_TOGGLE - GPIO data register TOGGLE */
/*! @{ */

#define GPIO_DR_TOGGLE_DR_TOGGLE_MASK            (0xFFFFFFFFU)
#define GPIO_DR_TOGGLE_DR_TOGGLE_SHIFT           (0U)
/*! DR_TOGGLE - Toggle
 */
#define GPIO_DR_TOGGLE_DR_TOGGLE(x)              (((uint32_t)(((uint32_t)(x)) << GPIO_DR_TOGGLE_DR_TOGGLE_SHIFT)) & GPIO_DR_TOGGLE_DR_TOGGLE_MASK)
/*! @} */


/*!
 * @}
 */ /* end of group GPIO_Register_Masks */


/* GPIO - Peripheral instance base addresses */
/** Peripheral GPIO1 base address */
#define GPIO1_BASE                               (0x401B8000u)
/** Peripheral GPIO1 base pointer */
#define GPIO1                                    ((GPIO_Type *)GPIO1_BASE)
/** Peripheral GPIO2 base address */
#define GPIO2_BASE                               (0x401BC000u)
/** Peripheral GPIO2 base pointer */
#define GPIO2                                    ((GPIO_Type *)GPIO2_BASE)
/** Peripheral GPIO3 base address */
#define GPIO3_BASE                               (0x401C0000u)
/** Peripheral GPIO3 base pointer */
#define GPIO3                                    ((GPIO_Type *)GPIO3_BASE)
/** Peripheral GPIO5 base address */
#define GPIO5_BASE                               (0x400C0000u)
/** Peripheral GPIO5 base pointer */
#define GPIO5                                    ((GPIO_Type *)GPIO5_BASE)
/** Array initializer of GPIO peripheral base addresses */
#define GPIO_BASE_ADDRS                          { 0u, GPIO1_BASE, GPIO2_BASE, GPIO3_BASE, 0u, GPIO5_BASE }
/** Array initializer of GPIO peripheral base pointers */
#define GPIO_BASE_PTRS                           { (GPIO_Type *)0u, GPIO1, GPIO2, GPIO3, (GPIO_Type *)0u, GPIO5 }
/** Interrupt vectors for the GPIO peripheral type */
#define GPIO_IRQS                                { NotAvail_IRQn, GPIO1_INT0_IRQn, GPIO1_INT1_IRQn, GPIO1_INT2_IRQn, GPIO1_INT3_IRQn, GPIO1_INT4_IRQn, GPIO1_INT5_IRQn, GPIO1_INT6_IRQn, GPIO1_INT7_IRQn, NotAvail_IRQn, NotAvail_IRQn, NotAvail_IRQn, NotAvail_IRQn }
#define GPIO_COMBINED_LOW_IRQS                   { NotAvail_IRQn, GPIO1_Combined_0_15_IRQn, GPIO2_Combined_0_15_IRQn, GPIO3_Combined_0_15_IRQn, NotAvail_IRQn, GPIO5_Combined_0_15_IRQn }
#define GPIO_COMBINED_HIGH_IRQS                  { NotAvail_IRQn, GPIO1_Combined_16_31_IRQn, GPIO2_Combined_16_31_IRQn, GPIO3_Combined_16_31_IRQn, NotAvail_IRQn, GPIO5_Combined_16_31_IRQn }
 主要用到下面的部分:
/* 地址可查阅 参考手册-GPIO章节-GPIO Memory Map */
/* GPIO1外设基地址 */
#define GPIO1_BASE        (unsigned int)(0x401B8000u)

/* GPIO1寄存器地址,强制转换成指针 */
#define GPIO1_DR            *(unsigned int*)(GPIO1_BASE+0x00)
#define GPIO1_GDIR          *(unsigned int*)(GPIO1_BASE+0x04)
#define GPIO1_PSR           *(unsigned int*)(GPIO1_BASE+0x08)
#define GPIO1_ICR1          *(unsigned int*)(GPIO1_BASE+0x0C)
#define GPIO1_ICR2          *(unsigned int*)(GPIO1_BASE+0x10)
#define GPIO1_IMR           *(unsigned int*)(GPIO1_BASE+0x14)
#define GPIO1_ISR           *(unsigned int*)(GPIO1_BASE+0x18)
#define GPIO1_EDGE_SEL      *(unsigned int*)(GPIO1_BASE+0x1C)

/* 地址可查阅 参考手册-IOMUXC章节-IOMUXC Memory Map/Register Definition */
/* IOMUXC基地址 */
#define IOMUXC_BASE         (unsigned int)(0x401F8000u)

/* MUX模式选择寄存器 IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_09 ,强制转换成指针*/
#define IOMUXC_MUX_GPIO_AD_B0_09   *(unsigned int*)(IOMUXC_BASE + 0xE0 )

/* PAD属性设置寄存器 IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B0_09 ,强制转换成指针*/
#define IOMUXC_PAD_GPIO_AD_B0_09   *(unsigned int*)(IOMUXC_BASE + 0x2D0 )

/* 地址可查阅 参考手册-CCM章节-CCM Memory Map/Register Definition */
/* 时钟控制外设基地址 */
#define CCM_BASE            (unsigned int)(0x400FC000u)

/* 时钟配置寄存器CCM_CCGR1地址 */
#define CCM_CCGR1           *(unsigned int*)(CCM_BASE + 0x6C)
 
 
GPIO外设的地址跟上一章讲解的相同,不过此处把寄存器的地址值都直接强制转换成了指针,方便使用。代码的最后两段是CCM外设寄存器的地址定义,CCM外设是用来设置时钟的,以后我们会详细分析,本实验中只要了解到使用GPIO外设必须开启它的时钟即可。

main文件

现在就可以开始编写程序了,在main文件中先编写一个 main 函数,里面什么都没有,暂时为空。
main文件
int main (void)
{
}
 

只写main函数,如果此时直接编译的话,会出现如下错误: “Error: L6218E: Undefined symbol SystemInit (referred from startup_mimxrt1052.o).” 错误提示SystemInit 没有定义。从分析启动文件时我们知道,Reset_Handler 调用了该函数用来初始化RT1021芯片的基础功能,如浮点运算单元、系统定时器、看门狗以及指令缓存,为了简单起见,我们在 main 文件里面定义一个 SystemInit 空函数,什么也不做,为的是骗过编译器,把这个错误去掉。也就是说,不使用这些基础功能,我们也能控制芯片完成点亮LED灯的任务,我们在main中添加SystemInit空函数:

main文件2
// 函数为空,目的是为了骗过编译器不报错
void SystemInit(void)
{
}

这时再编译就没有错了,完美解决。接下来在main函数中添加代码,对寄存器进行控制,有关GPIO寄存器的详细描述请阅读《IMXRT1050RM》(参考手册)中IOMUXC以及GPIO章节的寄存器描述部分。

IOMUX复用模式由于RT1021芯片每个引脚都连接到多个片上外设,有多种功能,使用时要设置好它与哪个外设相连,也就是说选择引脚的复用模式。控制LED灯时,只需要控制引脚简单地输出高低电平,所以使用GPIO外设功能即可。在参考手册中找到与该LED灯连接的引脚GPIO_AD_B0_05对应的MUX寄存器 SW_MUX_CTL_PAD_GPIO_AD_B0_05的说明,见图 7‑13
 
SW_MUX_CTL_PAD_GPIO_AD_B0_09寄存器说明《IOMUX Controller(IOMUXC)》章节可以看到,寄存器的MUX_MODE域列出了这个引脚的8个可选功能,把该域设置成0b101即十进制数5即可选中ATL5选项,表示GPIO1_IO09,这个就是引脚的GPIO功能。寄存器还有一个SION配置域,使能了SION的话可以通过GPIO的PSR寄存器读取回引脚的状态,本应用中不需要,所以SION域配置为0不使用即可。对于寄存器的其它部分都是保留域,即芯片厂商并未给它们设置功能,所以都设置成0即可。也就是说对这个引脚的MUX寄存器,我们给它赋值为5即可控制为GPIO_IO09功能。使用代码设置的方式见代码清单 7‑4。
代码清单 7‑4 配置IOMUX复用模式
 
/* 设置MUX寄存器为0x05,表示把引脚用于普通GPIO */
IOMUXC_MUX_GPIO_AD_B0_09 = (unsigned int)0x05;
#define BOARD_USER_LED_GPIO                                                GPIO1   /*!< GPIO peripheral base pointer */
#define BOARD_USER_LED_GPIO_PIN                                               5U   /*!< GPIO pin number */
#define BOARD_USER_LED_GPIO_PIN_MASK                                  (1U << 5U)   /*!< GPIO pin mask */
#define BOARD_USER_LED_PORT                                                GPIO1   /*!< PORT peripheral base pointer */
#define BOARD_USER_LED_PIN                                                    5U   /*!< PORT pin number */
#define BOARD_USER_LED_PIN_MASK                                       (1U << 5U)   /*!< PORT pin mask */
#define EXAMPLE_LED_GPIO     BOARD_USER_LED_GPIO
#define EXAMPLE_LED_GPIO_PIN BOARD_USER_LED_PIN
/** Peripheral IOMUXC base address */ #define IOMUXC_BASE (0x401F8000u) /** Peripheral IOMUXC base pointer */ #define IOMUXC ((IOMUXC_Type *)IOMUXC_BASE) /** Array initializer of IOMUXC peripheral base addresses */ #define IOMUXC_BASE_ADDRS { IOMUXC_BASE } /** Array initializer of IOMUXC peripheral base pointers */ #define IOMUXC_BASE_PTRS { IOMUXC }

static inline void IOMUXC_SetPinMux(uint32_t muxRegister, uint32_t muxMode, uint32_t inputRegister, uint32_t inputDaisy, uint32_t configRegister, uint32_t inputOnfield) { *((volatile uint32_t *)muxRegister) = IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(muxMode) | IOMUXC_SW_MUX_CTL_PAD_SION(inputOnfield); if (inputRegister != 0UL) { *((volatile uint32_t *)inputRegister) = inputDaisy; } }
static inline void IOMUXC_SetPinConfig(uint32_t muxRegister, uint32_t muxMode, uint32_t inputRegister, uint32_t inputDaisy, uint32_t configRegister, uint32_t configValue) { if (configRegister != 0UL) { *((volatile uint32_t *)configRegister) = configValue; } }
void GPIO_PinWrite(GPIO_Type *base, uint32_t pin, uint8_t output)
{
    assert(pin < 32U);
    if (output == 0U)
    {
#if (defined(FSL_FEATURE_IGPIO_HAS_DR_CLEAR) && FSL_FEATURE_IGPIO_HAS_DR_CLEAR)
        base->DR_CLEAR = (1UL << pin);
#else
        base->DR &= ~(1UL << pin); /* Set pin output to low level.*/
#endif
    }
    else
    {
#if (defined(FSL_FEATURE_IGPIO_HAS_DR_SET) && FSL_FEATURE_IGPIO_HAS_DR_SET)
        base->DR_SET = (1UL << pin);
#else
        base->DR |= (1UL << pin);  /* Set pin output to high level.*/
#endif
    }
}
IOMUXC_ MUX _GPIO_AD_B0_09是前面在MIMXRT1021.h头文件定义好的指针,该指针指向GPIO_AD_B0_05引脚(即GPIO1_IO09)的MUX寄存器,并且对该指针进行了“ * ”取值操作,所以这里只要简单地对IOMUXC_ MUX _GPIO_AD_B0_05直接赋值为(unsigned int)0x05即可(程序里习惯使用16进制表示),其中的unsigned int强制转换是确保它是一个32位的无符号数,与寄存器的数据类型对应。
PAD属性配置由于每个引脚还有多种属性,所以控制时还要根据需求配置好各种属性。在参考手册中找到与该LED灯连接的引脚GPIO_AD_B0_09对应的PAD寄存器 SW_PAD_CTL_PAD_GPIO_AD_B0_09的说明,见图 7‑14。image14图 7‑14 SW_PAD_CTL_PAD_GPIO_AD_B0_09寄存器说明《IOMUX Controller(IOMUXC)》章节PAD属性配置寄存器包含输入输出属性、上拉保持器的配置,由于控制LED灯时使用的是输出功能,所以只需要配置输出属性和上拉保持器即可。
SRE压摆率配置控制LED灯时,对压摆率没什么要求,所以慢速和快速都可以。在本实验中PAD寄存器的位设置为SRE=0b0,即慢压摆率。
DSE驱动能力DSE可以控制串联一个电阻,可选阻值为R0(260欧姆)、R0/2…R0/7,电阻值的大小会影响通过管脚电流的大小从而影响LED灯的亮度。在本实验中,使用不同阻值时对LED亮度的影响人眼分辨不出来,所以也是随便选择一个即可,这里设置为DSE=0b110,即阻值为R0/6。
.SPEED带宽配置带宽是指引脚支持高低电平切换的最高频率,本实验控制LED灯对于带宽也没有要求,此处直接配置为SPEED=0b10即100MHz。
ODE开漏输出配置开漏类型不能直接输出高电平,要输出高电平还要在芯片外部接上拉电阻,不符合我们的硬件设计,所以此处我们配置ODE=0b0,不使用开漏模式。
PKE、PUE及PUS上下拉、保持器配置引脚还包含上下拉和保持器的配置,使用上下拉可以在没控制输出的时候引脚固定在高电平或低电平。在本实验中,控制LED灯最终会把引脚配置成输出模式,在输出模式下不管上下拉及保持器被配置成什么模式,它们都会被自动关闭。也就是说,在控制DR寄存器输出时,引脚会无视上下拉而根据DR寄存器的值输出电平。为简单起见,控制LED灯时我们直接关闭上下拉及保持器,即PKE=0b0。而PUE用于在PKE使能的时候选择使用上下拉还是保持器,PUS用于工作于上下拉模式时(PKE使能,PUE为上下拉时)选择使用上拉还是下拉以及对应的电阻。因为关闭了上下拉及保持器,所以PUE和PUS为任意值都无影响,此处直接赋值为PUE=0b0, PUS=0b00。
 HYS滞后配置这个是设置是否使用施密特触发器的,只对输入有效,所以这里配置HYS=0b0,不使用该功能。综上所述,在本实验控制LED灯时,对这个PAD寄存器的配置如下:
位号配置域配置值(二进制)功能
0 SRE 0 慢压摆率
1-2 00
3-5 DSE 110 串联电阻R0/6
6-7 SPEED 10 带宽100MHz
8-10 000
11 ODE 0 不使用开漏模式
12 PKE 0 关闭上下拉、保持器
13 PUE 0
  • 关闭上下拉、保持器后,任意值无影响
14-15 PUS 00
  • 关闭上下拉、保持器后,任意值无影响
16 HYS 0 不使用滞后功能
17-31 0000 0000 0000 0000
把上述配置组合,得到二进制数0b0000 0000 0000 1011 0000,化成十六进制数即为0x000B0,也就是说,我们只要给PAD寄存器赋值为0x000B0即可得到以上配置,写成代码的形式如下,见代码清单 7‑5。
代码清单 7‑5 配置引脚的PAD寄存器
/* 设置PAD寄存器控制引脚的属性 */
IOMUXC_PAD_GPIO_AD_B0_09 = (unsigned int)0x000B0;

/*  属性配置说明:
十六进制数0x000B0 = 二进制数0b0000 0000 0000 1011 0000
*/

/*  bit0:         SRE:  0b0       压摆率: 慢压摆率
   bit1~bit2:    -:    0b00      寄存器保留项
   bit3~bit5:    DSE:  0b110     驱动强度: R0/6 (仅作为输出时有效 )
   bit6~bit7:    SPEED:0b10      带宽 : medium(100MHz)
   bit8~bit10:   -:    0b000     寄存器保留项
   bit11:        ODE:  0b0       开漏配置: 关闭
                                 (开漏高阻态常用于总线配置,如I2C )
   bit12:        PKE:  0b0       拉/保持器配置: 关闭
   bit13:        PUE:  0b0       拉/保持器选择: 关闭了上下拉及保持器,任意值无效
   bit14~bit15:  PUS:  0b00      上拉/下拉选择: 关闭了上下拉及保持器,任意值无效
   bit16:        HYS:  0b0       滞回器配置: 关闭
                              (仅输入时有效,施密特触发器,使能后可以过滤输入噪声)
   bit17~bit31:  -:    0b0       寄存器保留项
*/
类似地,IOMUXC_PAD_GPIO_AD_B0_09也是前面在MIMXRT1052.h头文件定义好的指针,它指向GPIO_AD_B0_09(即GPIO1_09)引脚PAD寄存器,此处直接对IOMUXC_PAD_GPIO_AD_B0_09赋值为计算好的0x070B0即可得到需要的配置。
控制引脚方向控制LED灯需要把GPIO引脚设置为输出模式,这需要配置GPIO外设的GDIR方向寄存器,它在参考手册中的说明见图 7‑15。image15图 7‑15 GPIO的GDIR方向寄存器的说明《General Purpose Input/Output(GPIO)》章节GDIR寄存器中的每个数据位控制GPIO端口其中一个引脚的方向,为1时表示输出,为0时表示输入。本实验中要控制的引脚GPIO_AD_B0_09(GPIO1_IO09)是GPIO1端口中编号为9的引脚,所以控制时把GPIO1对应的GDIR寄存器的bit9赋值为1,即可把它设置为输出模式,使用代码实现具体见代码清单 7‑6。
代码清单 7‑6 设置引脚为输出模式
/* 设置GPIO1_09引脚的方向,使用输出模式 */
GPIO1_GDIR |= (unsigned int)(1<<9);
代码中的说明如下:
  • GPIO1_GDIR在MIMXRT1052.h头文件已经定义好,它表示的就是GPIO1端口的GDIR寄存器指针,并且包含“* ”取指针操作。
  • (1<<9)是一个bit9为1的数字,即0x200,通常来说在代码中不会写0x200这样的结果数字,而是用(1<<9)这种运算来表示。如bit10为1的数字,则用(1<<10)表示。
  • 使用“|=”这种位操作方法是为了避免影响到寄存器中的其它位。这种操作的等效代码是
GPIO1_GDIR = GPIO1_GDIR | (unsigned int)(1<<9),
也就是说它会先把GDIR寄存器原来的值读取回来,然后与(1<<9)进行位或运算,再把结果赋值给GDIR。要这样操作的原因是寄存器不能按位读写,假如我们直接给GDIR寄存器赋值:
GPIO1_GDIR = (unsigned int)(1<<9);
那么这代码执行后GPIO1_GDIR的值就变为0x00000200,寄存器的bit9为1,貌似符合我们的预期,但要注意的是GDIR寄存器是同时控制GPIO端口的32个引脚的,经过这样赋值后就只有bit9为1,其余的寄存器位均为0了,假如GDIR的bit0、bit2等寄存器位原值为1,即原本为输出方向,经过这样粗暴的赋值方式就会把它们改为输入方向了。而使用“|=”只会修改bit9,其它寄存器位不变。
控制引脚输出电平设置好引脚方向后,可以通过DR数据寄存器控制引脚的电平,它在参考手册中的说明见图 7‑16。image16图 7‑16 GPIO的DR数据寄存器的说明《General Purpose Input/Output(GPIO)》章节类似地,DR数据寄存器中的每个数据位控制GPIO端口其中一个引脚的电平,为1时表示高电平,为0时表示低电平。在本实验中使用GPIO_IO09连接LED灯,控制时把GPIO1对应的DR寄存器的bit9赋值为1可输出高电平,LED灯灭,给bit9赋值为0可输出低电平,LED灯亮,使用代码实现具体见代码清单 7‑7。
代码清单 7‑7控制引脚输出电平
/* 控制引脚为高电平,关闭LED灯 */
GPIO1_DR |= (unsigned int)(1<<5);

/* 控制引脚为低电平,点亮LED灯 */
GPIO1_DR &=~ (unsigned int)(1<<5);
跟GDIR寄存器的操作方式类似,代码中使用了“&=~”(清0)、“|=”(置位)这些位操作方法来实现对寄存器的写操作,避免影响到寄存器中的其它位。
开启外设时钟设置完GPIO的引脚,控制电平输出,以为现在总算可以点亮 LED 了吧,其实还差最后一步。因为RT1052外设很多,为了降低功耗,每个外设都对应着一个时钟,在芯片刚上电的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。RT1052的所有外设的时钟由一个专门的外设来管理,叫 CCM(Clock Controler Module),CCM在《IMXRT1050RM》(参考手册)的第18章有详细的讲解。有关RT1052的时钟系统我们在往后的CCM章节会详细的讲解,此处我们只需知道在访问GPIO外设的寄存器之前,要先开启它的时钟。本实验要控制GPIO1相关的引脚,就需要开启GPIO1时钟,即要对CCM_CCGR1寄存器进行配置,该寄存器的在参考手册的说明具体见图 7‑17。image17图 7‑17 CCM_CCGR1寄存器说明《Clock Controller Module(CCM)》章节从图中可以了解到CCM_CCGR1寄存器可以配置很多外设的时钟,如bit28~bit29配置CSU外设时钟,bit26~27配置GPIO1外设时钟,每个配置域包含2位。这2个位包含3个有效选择,0b00表示该时钟关闭,0b01和0b11表示在RT1052芯片的RUN模式下都开启该时钟。其中RUN、WAIT、STOP是RT1052芯片的运行模式,目前我们只关注RUN模式即可,它指芯片的正常运行状态。也就是说,我们把CCM_CCGR1寄存器的bit26~bit27设置为0b01或0b11都可以开启GPIO1端口的时钟,本实验把它设置为0b01,具体代码见代码清单 7‑8。
代码清单 7‑8 开启端口时钟
1
2
3
4
5
6
7
/* 开启GPIO1端口的时钟 */

/* 清空控制GPIO1端口时钟的bit26、bit27 */
CCM_CCGR1 &= ~(unsigned int)(3<<26);

/* 把bit26、bit27设置为0b01,即开启GPIO1时钟 */
CCM_CCGR1 |= (unsigned int)(1<<26);
代码说明如下:
  • CCM_CCGR1在MIMXRT1052.h头文件已经定义好,它表示的就是CCM的CCGR1寄存器指针,并且包含“* ”取指针操作。
  • (3<<26)表示十六进制数0x0c000000,它的bit26~bit27这2位为1,使用这样的数值与CCM_CCGR1寄存器进行“&=~”操作可以把它的bit26、bit27进行清零,其它位的值不变。代码中(3<<26)的3就是二进制值0b11,2个数字1表示要清空2个寄存器位,26表示bit26,类似地,假如要清除寄存器的bit10、bit11、bit12以及bit13这4位,操作时就使用二进制数0b1111<<10即(0x0F<<10)参与“&=~”运算。
  • bit26、bit27被清零后,使用(1<<26)与CCM_CCGR1寄存器进行“|=”,把它的bit26、bit27位设置为0b01,即开启GPIO1端口的时钟,然后就可以对GPIO1端口进行操作了。
水到渠成开启时钟,配置引脚复用模式,配置引脚属性,控制电平,经过这几步,我们总算可以控制一个 LED了。现在我们完整组织下用 RT1052控制一个 LED 的代码,见代码清单 7‑9。注意控制时要先通过CCM_CCGR1寄存器使能GPIO1的时钟,再对GPIO1的寄存器进行操作,否则操作是无效的。在本章节中,要求完全理解MIMXRT1021.h文件及main文件的内容(CCM相关的除外)。
int main(void)
{
    /* Board pin init */
    BOARD_ConfigMPU();
    BOARD_InitBootPins();
    BOARD_InitBootClocks();

    /* Set systick reload value to generate 1ms interrupt */
    if (SysTick_Config(SystemCoreClock / 1000U))
    {
        while (1)
        {
        }
    }

    while (1)
    {
        /* Delay 1000 ms */
        SysTick_DelayTicks(1000U);
        if (g_pinSet)
        {
            GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 0U);
            g_pinSet = false;
        }
        else
        {
            GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 1U);
            g_pinSet = true;
        }
    }
}
void GPIO_PinWrite(GPIO_Type *base, uint32_t pin, uint8_t output)
{
    assert(pin < 32U);
    if (output == 0U)
    {
#if (defined(FSL_FEATURE_IGPIO_HAS_DR_CLEAR) && FSL_FEATURE_IGPIO_HAS_DR_CLEAR)
        base->DR_CLEAR = (1UL << pin);
#else
        base->DR &= ~(1UL << pin); /* Set pin output to low level.*/
#endif
    }
    else
    {
#if (defined(FSL_FEATURE_IGPIO_HAS_DR_SET) && FSL_FEATURE_IGPIO_HAS_DR_SET)
        base->DR_SET = (1UL << pin);
#else
        base->DR |= (1UL << pin);  /* Set pin output to high level.*/
#endif
    }
}
涉及的函数及宏定义:#define IOMUXC_GPIO_AD_B0_05_GPIO1_IO05 0x401F80D0U, 0x5U, 0, 0, 0x401F8244U#define IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(x)        (((uint32_t)(((uint32_t)(x)) << IOMUXC_SW_MUX_CTL_PAD_MUX_MODE_SHIFT)) & IOMUXC_SW_MUX_CTL_PAD_MUX_MODE_MASK)
#define IOMUXC_SW_MUX_CTL_PAD_MUX_MODE_MASK      (0x7U)
#define IOMUXC_SW_MUX_CTL_PAD_MUX_MODE_SHIFT     (0U)
/*! MUX_MODE - MUX Mode Select Field.
 *  0b000..Select mux mode: ALT0 mux port: SEMC_DATA00 of instance: semc
 *  0b001..Select mux mode: ALT1 mux port: QTIMER2_TIMER0 of instance: qtimer2
 *  0b010..Select mux mode: ALT2 mux port: LPUART4_CTS_B of instance: lpuart4
 *  0b011..Select mux mode: ALT3 mux port: SPDIF_SR_CLK of instance: spdif
 *  0b100..Select mux mode: ALT4 mux port: LPSPI2_SCK of instance: lpspi2
 *  0b101..Select mux mode: ALT5 mux port: GPIO2_IO00 of instance: gpio2
 *  0b110..Select mux mode: ALT6 mux port: FLEXCAN1_TX of instance: flexcan1
 *  0b111..Select mux mode: ALT7 mux port: PIT_TRIGGER02 of instance: pit
 */
#define IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(x)        (((uint32_t)(((uint32_t)(x)) << IOMUXC_SW_MUX_CTL_PAD_MUX_MODE_SHIFT)) & IOMUXC_SW_MUX_CTL_PAD_MUX_MODE_MASK)

#define IOMUXC_SW_MUX_CTL_PAD_SION_MASK          (0x10U)
#define IOMUXC_SW_MUX_CTL_PAD_SION_SHIFT         (4U)
/*! SION - Software Input On Field.
 *  0b1..Force input path of pad GPIO_EMC_00
 *  0b0..Input Path is determined by functionality
 */
#define IOMUXC_SW_MUX_CTL_PAD_SION(x)            (((uint32_t)(((uint32_t)(x)) << IOMUXC_SW_MUX_CTL_PAD_SION_SHIFT)) & IOMUXC_SW_MUX_CTL_PAD_SION_MASK)
/*! @} */
 
 
 

下载验证把编译好的程序下载到开发板并复位,可看到核心板上的LED灯被点亮。 接下来就是调试部分:

 

 

 

posted on 2022-03-19 21:42  张凌001  阅读(699)  评论(0编辑  收藏  举报

导航