五、寄存器点亮 LED 灯
1.1.配置流程
一般我们使用 GPIO 的端口,都需要有以下几个步骤。
- 开启 GPIO 的端口时钟
- 配置 GPIO 的模式
- 配置 GPIO 的输出
从 LED 介绍那一章节我们了解到 LED1 接的是单片机的 PE3,LED2 接的是单片机的 PD7,LED3 接的是单片机的 PG3,LED4 接的是单片机的 PA5。我们要使能 LED 就需要配置 GPIOA 端口,GPIOD 端口,GPIOE 端口和 GPIOG 端口。下面我们就以 LED2 接的 PD7 进行介绍,其它也是类似的。
1.1.1.开启 GPIO 的端口时钟
GD32 的所有外设资源时钟默认都是关闭的,在配置外设之前需要先开启对应的时钟。
要使能 LED2,就要先开启 GPIOD 的时钟,从用户手册的第 38 页我们了解到 GPIOD 挂载在 AHB1 总线上,那操作 GPIOD 的时钟肯定要配置 AHB1 使能寄存器。在用户手册的第 113 页,AHB1 使能寄存器如图 1-1-1 所示。
从图 1-1-1 可以看到地址偏移量为 0x30,那这个基地址是多少呢?因为 AHB1 使能寄存器是在 RCU 外设的地址范围内,所以 RCU 的外设基地址就是 AHB1 使能寄存器的基地址,在用户手册的第 95 页有明确说明,RCU 基地址:0x4002 3800。那我们 RCU_AHB1EN 寄存器的地址就是基地址加上偏移量,0x4002 3800 + 0x30 = 0x4002 3830。找到要操作的地址了,那我们怎么去配置这个值呢?我们继续看这个寄存器的说明,在用户手册的第 115 页有寄存器说明,如图 1-1-2 所示。
从图 1-1-2 可以了解到 RCU_AHB1EN 寄存器的第 3 位就是 GPIOD 端口时钟使能,我们要开启 GPIOD 端口时钟使能,就需要往 RCU_AHB1EN 的第 3 位写 1,然后为了保持其他位不变,我们可以使用一个或运算,也就是
RCU_AHB1EN |= 0x00000008
也就是相当于拉高 RCU_AHB1EN 寄存器的第 3 位,其他位保持不变。其实也可以写为
RCU_AHB1EN |=(1 << 3)
是一样的效果,这里的 3 就是寄存器的第 3 位,如果是 4 就是寄存器的第 4 位,依此类推。
1.1.2.配置 GPIO 模式
GPIO 的模式配置可分为两步,第一步就是通过控制寄存器(GPIOx_CTL)配置为输入功能,输出功能,复用功能还是模拟功能。第二步就是通过 GPIO 上/下拉寄存器(GPIOx_PUD)配置 GPIO 的上下拉模式或者浮空。
配置为输出功能
我们要使能 LED,自然是配置 GPIO 为输出模式,找到控制寄存器(GPIOx_CTL),如图 1-2-1 所示。
从图 1-2-1 可以看到 GPIOX_CTL 的地址偏移是 0x00,我们要使能的是 GPIOD 的引脚,所以要加上 GPIOD 的基地址 0x4002 0C00,即 GPIOD_CTL 寄存器的地址为 0x4002 0C00 + 0x00。我们接着看这个寄存器的介绍说明,如图 1-2-2 所示。
从图 1-2-2 可以看到每一个引脚都由 2 位控制,我们要操作的是 PD7 引脚,也就是 pin7,也就是 GPIOD_CTL 寄存器的第 15 位和第 14 位。我们要配置为输出模式,也就是把这两位配置为 01,我们用二进制来表示一下,0000 0000 0000 0000 0100 0000 0000 0000 ,转换成十六进制就是 0x00004000。所以我们将 GPIOD_CTL 寄存器里面写入 0x00004000,就是把 PD7 这个引脚配置为了输出模式,为了保持其它位的数据不发生变化,我们先清空这两位,然后再往这两位里面写值。操作为 GPIOD_CTL &= 0xffff3fff; 这句代码是清空第 15 位和第 14 位。GPIOD_CTL |= 0x00004000;这句代码就是把第 15 位和第 14 位配置为 01,这样操作保证了其它位不发生变化。
其实也可以这样配置,GPIOD_CTL &= ~(0x03 << (14));这句代码就是把 GPIOD_CTL 寄存器的第 15 位和第 14 位清零,GPIOD_CTL |= (0x01 << (14)); 这句代码就是把第 15 位和第 14 位配置为 01。这里的 7 就是 pin7,如果是 pin6 的话就配置为 6 即可。
配置为浮空模式,无上拉下拉
一般输出模式我们都配置为浮空模式,输入模式我们才需要考虑上拉还是下拉,根据默认电平状态进行判断。
第一步配置好为输出模式之后,我们还需要进行第二步配置,配置 PD7 为浮空模式,无上拉和下拉。关于端口上下拉寄存器如图 1-2-3 所示。
从图 1-2-3 可以看到端口上下拉寄存器的地址偏移是 0x0C,那 GPIOD_PUD 的地址就是 0x4002 0C00 + 0x0C。我们接着往下看这个寄存器的介绍,如图 1-2-4 所示。
从图 1-2-4 可以看到,每一个引脚都由 2 位进行控制,和 GPIOD_CTL 寄存器的配置一样,也是先清空这两位,然后再配置这两位为 00。转化为下面这两句代码。
GPIOD_PUD &= ~(0x03 << (2*7));
GPIOD_PUD |= (0x00 << (2*7));
2
3
其实我们发现,第二句代码其实不用写,因为第一句就把这两位清零了。
1.1.3.配置 GPIO 的输出
配置 GPIO 的输出也分为两步,第一步配置端口输出模式寄存器 GPIOx_OMODE,第二步配置端口速度寄存器 GPIOx_OSPD。
配置为推挽输出
当我们配置为输出模式的时候,可以有两种输出模式选择,一种是推挽输出模式,一种是开漏输出模式。
推挽输出: 可以输出高低电平,推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。 开漏输出: 输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行,适合做电流型的驱动,其吸收电流的能力相对强(一般 20ma 以内)。开漏输出可以很方便的调节输出的电平,因为输出电平完全是由上拉电阻连接的电源电平决定。开漏输出可以实现“线与”功能,就是可以把多个信号线连接在一起,只有当所有信号全部为高电平时,合在一起的总线为高电平,否则就为低电平。
开漏是需要外接上拉电阻才可以输出高电平,这里并不适合,所以需要设置为推挽输出。
端口输出模式寄存器如图 1-3-1 所示。
从图 1-3-1 可以看到端口输出模式寄存器的地址偏移为 0x04,那 GPIOD_OMODE 寄存器的地址就是 0x4002 0C00 + 0x04。我们接着往下看这个寄存器的介绍,如图 1-3-2 所示。
从图 1-3-2 可以看到每一个引脚由 1 位进行控制,要配置为推挽输出,只需要往对应的寄存器写 0 即可。转化为代码为 GPIOD_OMODE &= ~(0x01 << 7)。
配置速度
我们配置引脚输出模式之后,我们还要选择这个引脚对应的速度,端口输出速度寄存器如图 1-3-3 所示。
从图 1-3-3 可以看到端口输出速度寄存器的地址偏移是 0x08,那么 GPIOD_OSPD 寄存器的地址就是 0x4002 0C00 + 0x08。我们接着往下看这个寄存器的介绍,如图 1-3-4 所示。
从图 1-3-4 可以看出每一个引脚由 2 位进行控制,要配置为多少等级,只需要往对应的位写入对应的值即可。从官方提供的代码中查找到了速度等级对应的频率关系,如下所示。
/* GPIO output max speed value */
#define GPIO_OSPEED_2MHZ GPIO_OSPEED_LEVEL0 /*!< output max speed 2MHz */
#define GPIO_OSPEED_25MHZ GPIO_OSPEED_LEVEL1 /*!< output max speed 25MHz */
#define GPIO_OSPEED_50MHZ GPIO_OSPEED_LEVEL2 /*!< output max speed 50MHz */
#define GPIO_OSPEED_MAX GPIO_OSPEED_LEVEL3 /*!< GPIO very high output speed, max speed more than 50MHz */
2
3
4
5
我们这里选择配置为 50MHZ,所以需要配置为等级 2,需要往对应的位写入 10。转化为代码就是
GPIOD_OSPD &= ~(0x03 << (2 * 7));
GPIOD_OSPD |= (0x02 << (2 * 7));
2
通过这两句我们就可以把 PD7 引脚的速度配置为 50MHZ。
到此,我们关于 GPIO 的配置就完成了。
1.2.配置 GPIO 输出高电平
配置好 GPIO 之后,我们就可以进行点灯了。其实也就是让 GPIO 引脚输出高低电平,到我们的开发板上就是让 PD7 输出高电平。
如何让 PD7 输出高电平呢?通过查阅用户手册 7.4 章节的 GPIO 寄存器,这里为大家总结了几种操作的方式。
1.2.1.端口输出控制寄存器
关于端口输出控制寄存器如图 2-1-1 所示。
从图 2-1-1 可以看到端口输出控制寄存器的地址偏移为 0x14,那么对应的 GPIOD_OCTL 寄存器的地址为 0x4002 0C00 + 0x14。我们接着往下看这个寄存器的介绍,如图 2-1-2 所示。
从图 2-1-2 可以了解到 GPIOD_OCTL 寄存器的低 16 位有效,每一个引脚对应一位,往对应的位写 0 就是输出低电平,写 1 就输出高电平。
输出低电平转换为代码为GPIOD_OCTL &= ~ (0x01 << 7);
输出高电平转换为代码为GPIOD_OCTL |= (0x01 << 7);
2
1.2.2.端口位操作寄存器
关于端口位操作寄存器如图 2-2-1 所示。
从图 2-2-1 可以看到端口位操作寄存器的地址偏移为 0x18,那么对应的 GPIOD_BOP 寄存器的地址为 0x4002 0C00 + 0x18。我们接着往下看这个寄存器的介绍,如图 2-2-2 所示。
从图 2-2-2 可以了解到 GPIOD_BOP 寄存器的低 16 位是置 1 位,高 16 位是清 0 位。对于低 16 位每一个引脚对应一位,往对应的位写 1 就是输出高电平,写 0 电平状态不改变。对于高 16 位每一个引脚对应一位,往对应的位写 1 就是输出低电平,写 0 电平状态不改变。
输出低电平转换为代码为GPIOD_BOP |= (0x01 << (7 + 16));
输出高电平转换为代码为GPIOD_BOP |= (0x01 << 7);
2
1.2.3.端口位翻转寄存器
使用端口位翻转寄存器也有类似的功能,从名称就可以知道这个寄存器是把对应的位电平进行翻转,这是一个很不错的操作,使用起来也很方便。但前提是我们要知道当下的引脚电平是一个什么状态,然后我们翻转之后又是一个什么状态。关于这一部分就不过多说明,大家可以自行研究一下。
1.3.实验现象
如果用上面的代码需要注意一下,像 RCU_BASE 这种宏定义其实在 GD32 的头文件中已经定义了,可以直接使用原来定义的,这样我们就不用再编写,也可以把我们编写的名称稍微修改一下,比如在它们前面加上一个 LED 或者 BSP 等等都可。
关于这一章节的代码,在资源包/04 软件资料/代码例程/里面的 001 寄存器点灯。
烧写我们的代码之后,可以看到开发板的 LED2 这个灯将被点亮。如图 3-1-1 所示。