0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

GD32开发实战指南(基础篇) 第3章 GPIO流水灯的前世今生

嵌入式大杂烩 来源:嵌入式大杂烩 作者:嵌入式大杂烩 2023-05-10 08:58 次阅读

开发环境:

MDK:Keil 5.30

开发板:GD32F207I-EVAL

MCU:GD32F207IK

上一章通过控制GPIO的高低电平实现了流水灯,但只是告诉了大家怎么做,如何实现流水灯,本文将深入剖析的GPIO流水灯的前生今世,深入研究流水灯的调用逻辑和数据结构。

1 GPIO配置概述

前面一章大概讲解GPIO的配置过程和核心的寄存器,当然啦,关于GPIO的寄存器远不止我上一章列出来的,还有很多,具体请参看《GD32F20x_User_Manual》中GPIO相关的内容吧。

16836413176280iij1n1apo

根据前面实现的GPIO流水灯,本文将其归纳如下:

F:\\File\\1 Hardware\\1-1 MCU\\1-1-5 GD32\\笔记\\GD32F2开发指南\\3 GPIO流水灯的前世今生\\附件\\GPIO流水灯流程.png

要想控制LED亮灭,就需要做以上三件事:使能时钟,配置GPIO参数,最后循环控制GPIO的高低电平就能实现流水灯的效果,GPIO的寄存器这里就不说了,更多详细的寄存器描述看官方手册就行,下面先来看看GD32的时钟。

2 GD32的时钟系统

2.1 GD32的系统架构

GD32的系统架构比51单片机强大很多了。首先我们看看GD32的系统架构图:

168364131854943r6uyf009

GD32F20x系列器件是基于Arm® Cortex®-M3处理器的32位通用微控制器。 Arm® Cortex®-M3处理器包括三条AHB总线分别称为I-CODE总线、 D-Code总线和系统总线。

下面我们具体讲解一下图中几个总线的知识:

ICode 总线:该总线将 M3 内核指令总线和闪存指令接口相连,指令的预取在该总线上面完成。

DCode 总线:该总线将 M3 内核的 DCode 总线与闪存存储器的数据接口相连接,常量加载和调试访问在该总线上面完成。

③ 系统总线:该总线连接 M3 内核的系统总线到总线矩阵,总线矩阵协调内核和 DMA 间访问。

④ DMA 总线:该总线将 DMA 的 AHB 主控接口与总线矩阵相连,总线矩阵协调 CPU 的DCode 和 DMA 到 SRAM,闪存和外设的访问。

⑤ 总线矩阵:总线矩阵协调内核系统总线和 DMA 主控总线之间的访问仲裁,仲裁利用轮换算法

⑥ AHB/APB 桥:这两个桥在 AHB 和 2 个 APB 总线间提供同步连接,APB1 操作速度限于60MHz,APB2 操作速度全速。

对于系统架构的知识,在刚开始学习 GD32 的时候只需要一个大概的了解,大致知道是个什么情况即可。

2.2 GD32时钟架构

时钟是整个处理器运行的基础,时钟信号推动处理器内各个部分执行相应的指令。时钟系统就是CPU的脉搏,决定CPU速率,像人的心跳一样 只有有了心跳,人才能做其他的事情,而单片机有了时钟,才能够运行执行指令,才能够做其他的处理 (点灯,串口,ADC),时钟的重要性不言而喻。

我们在学习51单片机时,其最小系统必有晶振电路,这块电路就是单片机的时钟来源,晶振的振荡频率直接影响单片机的处理速度。GD32相比51单片机就复杂得多,不仅是外设非常多,就连时钟来源就有四个。但我们实际使用的时候只会用到有限的几个外设,使用任何外设都需要时钟才能启动,但并不是所有的外设都需要系统时钟那么高的频率,为了兼容不同速度的设备,有些高速,有些低速,如果都用高速时钟,势必造成浪费,而且,同一个电路,时钟越快功耗越快,同时抗电磁干扰能力也就越弱,所以较为复杂的MCU都是采用多时钟源的方法来解决这些问题,因此便有了GD32的时钟系统和时钟树。

1683641319046msyvu4bqni

GD32三个不同的时钟源可以用来驱动系统时钟(CK_SYS):

● IRC8M晶振时钟(高速内部时钟信号)

● HXTAL晶振时钟(高速外部时钟信号)

● PLL时钟

GD32有两个二级时钟源:

● 40kHz的低速内部IRC40K,它可以驱动独立看门狗,还可选择地通过程序选择驱动RTC。 RTC用于从停机/待机模式下自动唤醒系统。

● 32.768kHz的低速外部晶振LXTAL,可选择它用来驱动RTC。

每个时钟源在不使用时都可以单独被打开或关闭,这样就可以优化系统功耗。

2.3 GD32的时钟系统

GD32 芯片为了实现低功耗,设计了一个功能完善但却非常复杂的时钟系统。普通的MCU 一般只要配置好 GPIO 的寄存器就可以使用了,但 GD32还有一个步骤,就是开启外设时钟。

C:\\Users\\BruceOu\\Desktop\\12313.png

在 GD32中,可分为五种时钟源,为 IRC8M、HXTAL、IRC40K、LXTAL、PLL。从时钟频率来分可以分为高速时钟源和低速时钟源,其中 IRC8M, HXTAL以及 PLL 是高速时钟,IRC40K和 LXTAL是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中 HXTAL和 LXTAL是外部时钟源,其他的是内部时钟源。

下面我们看看 GD32 的 5 个时钟源,我们讲解顺序是按图中红圈标示的顺序:

①IRC8M是__高速内部时钟__,RC 振荡器,频率为 8MHz。

②HXTAL是__高速外部时钟__,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~32MHz。我们的开发板接的是 25M 的晶振。当使用有源晶振时,时钟从 OSC_IN 引脚进入, OSC_OUT 引脚悬空,当选用无源晶振时,时钟从 OSC_IN 和 OSC_OUT 进入,并且要配谐振电容。当确定 PLL 时钟来源的时候, HXTAL可以不分频或者 2 分频,这个由时钟配置寄存器 CFG0 的位 17。

③IRC40K是__低速内部时钟__,RC 振荡器,频率为 40kHz。独立看门狗的时钟源只能是 IRC40K,同时 IRC40K还可以作为 RTC 的时钟源。

④LXTAL是__低速外部时钟__,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。

⑤PLL 为锁相环倍频输出,其时钟输入源可选择为 IRC8M、HXTAL。倍频可选择为2~32倍,但是其输出频率最大不得超过 120MHz。

图中我们用 A~E 标示我们要讲解的地方。

A. OUT是 GD32 的一个时钟输出IO,它可以选择一个时钟信号输出, 可以选择为 PLL 输出的 2 分频、IRC8M、HXTAL、或者系统时钟。这个时钟可以用来给外部其他系统提供时钟源。

B. 这里是 RTC 时钟源,从图上可以看出,RTC 的时钟源可以选择 IRC40K,以及HXTAL的 128 分频。

C. 从图中可以看出 C 处 USB 的时钟是来自 PLL 时钟源。 GD32 中有一个全速功能的 USB 模块,其串行接口引擎需要一个频率为 48MHz 的时钟源。该时钟源只能从 PLL 输出端获取,可以选择为 1/1.5/2/2.5 分频。

D. D 处就是 GD32 的系统时钟 SYSCLK,它是供 GD32 中绝大部分部件工作的时钟源。系统时钟可选择为 PLL 输出、 IRC8M或者 HXTAL。系统时钟最大频率为 120MHz,当然你也可以超频,不过一般情况为了系统稳定性是没有必要冒风险去超频的。

E. 这里的 E 处是指其他所有外设了。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK。SYSCLK 通过 AHB 分频器分频后送给各模块使用。这些模块包括:

①AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟。

②通过 8 分频后送给 Cortex 的系统定时器时钟,也就是 systick 了。

③直接送给 Cortex 的空闲运行时钟 FCLK。

④送给 APB1 分频器。APB1 分频器输出一路供 APB1 外设使用(PCLK1,最大频率 60MHz),另一路送给定时器(Timer)使用。

⑤送给 APB2 分频器。APB2 分频器分频输出一路供APB2外设使用(PCLK2,最大频率 120MHz),另一路送给定时器(Timer)使用。

其中需要理解的是 APB1 和 APB2 的区别, APB1 上面连接的是低速外设,包括电源接口、备份接口、 CAN、 USB、 I2C0、 I2C1、 UART1、 UART2 等等, APB2 上面连接的是高速外设包括 UART0、 SPI0、 Timer0、 ADC0、 ADC1、所有普通 IO 口(PA~PG)、第二功能 IO 口等。

不同的总线有不同的频率,不同的外设挂在不同的总线下,为了更适合初学者查阅,笔者把常用的外设与总线的对应关系总结如下:

F:\\File\\1 Hardware\\1-1 MCU\\1-1-5 GD32\\笔记\\GD32F2开发指南\\3 GPIO流水灯的前世今生\\附件\\GD32总线与外设连接关系.png

SystemInit()函数中设置的系统时钟大小:

  • SYSCLK(系统时钟) =120MHz
  • AHB 总线时钟(使用 SYSCLK) =120MHz
  • APB1 总线时钟(PCLK1) =60MHz
  • APB2 总线时钟(PCLK2) =120MHz
  • PLL 时钟 =120MHz

值得注意的是,GD32F207系列有多个PLL,具体参看源码。

2.4 GD32的时钟配置剖析

既然时钟搞清楚了,接下来回到上一章的配置时钟的代码:

/*enable the LED clock*/
rcu_periph_clock_enable(RCU_GPIOF );

rcu_periph_clock_enable就是配置时钟的函数,函数原型如下:

/*!
    \\brief      enable the peripherals clock
    \\param[in]  periph: RCU peripherals, refer to rcu_periph_enum
                only one parameter can be selected which is shown as below:
      \\arg        RCU_GPIOx (x=A,B,C,D,E,F,G,H,I): GPIO ports clock
      \\arg        RCU_AF : alternate function clock
      \\arg        RCU_CRC: CRC clock
      \\arg        RCU_DMAx (x=0,1): DMA clock
      \\arg        RCU_ENET: ENET clock
      \\arg        RCU_ENETTX: ENETTX clock
      \\arg        RCU_ENETRX: ENETRX clock
      \\arg        RCU_USBFS: USBFS clock
      \\arg        RCU_EXMC: EXMC clock
      \\arg        RCU_TIMERx (x=0,1,2,3,4,5,6,7,8,9,10,11,12,13): TIMER clock
      \\arg        RCU_WWDGT: WWDGT clock
      \\arg        RCU_SPIx (x=0,1,2): SPI clock
      \\arg        RCU_USARTx (x=0,1,2,5): USART clock
      \\arg        RCU_UARTx (x=3,4,6,7): UART clock
      \\arg        RCU_I2Cx (x=0,1,2): I2C clock
      \\arg        RCU_CANx (x=0,1): CAN clock
      \\arg        RCU_PMU: PMU clock
      \\arg        RCU_DAC: DAC clock
      \\arg        RCU_RTC: RTC clock
      \\arg        RCU_ADCx (x=0,1,2): ADC clock
      \\arg        RCU_SDIO: SDIO clock
      \\arg        RCU_BKPI: BKP interface clock
      \\arg        RCU_TLI: TLI clock
      \\arg        RCU_DCI: DCI clock
      \\arg        RCU_CAU: CAU clock
      \\arg        RCU_HAU: HAU clock
      \\arg        RCU_TRNG: TRNG clock
    \\param[out] none
    \\retval     none
*/
void rcu_periph_clock_enable(rcu_periph_enum periph)
{
    RCU_REG_VAL(periph) |= BIT(RCU_BIT_POS(periph));
}

整个函数就一个参数,其参数就是具体的外设时钟,整个函数很简单,就是打开具体的外设时钟。

参数periph传入值是通过宏来定义的,这样的好处也是便于移植,如果换了MCU,架构一样,只需要就该底层驱动就行,不需要更改上层应用,这样就提高了开发效率。言归正传,我们传入的RCU_GPIOC定义如下。

16836413204081p9c0x672i

RCU_GPIOF是一个枚举类型。我们继续追溯以上的宏。

/* constants definitions */
/* define the peripheral clock enable bit position and its register index offset */
#define RCU_REGIDX_BIT(regidx, bitpos)      (((uint32_t)(regidx) << 6) | (uint32_t)(bitpos))
#define RCU_REG_VAL(periph)                 (REG32(RCU + ((uint32_t)(periph) >> 6)))
#define RCU_BIT_POS(val)                    ((uint32_t)(val) & 0x1FU)

#define APB2EN_REG_OFFSET               0x18U                     /*!< APB2 enable register offset */
#define BIT(x)                       ((uint32_t)((uint32_t)0x01U<<(x)))
/* RCU definitions */
#define RCU                             RCU_BASE
#define RCU_BASE              (AHB1_BUS_BASE + 0x00009000U)  /*!< RCU base address                                */
#define AHB1_BUS_BASE         ((uint32_t)0x40018000U)        /*!< ahb1 base address                               */

以上宏定义就是整个时钟初始化相关的宏定义了,将其带入函数中。RCU的基地址就是0x40018000+0x9000。可以从GD32参考手册中获取。

AHB1总线的基地址是0x40018000。

RCU偏移是0x9000。

1683641320891so54frpua3

RCU_REG_VAL(RCU_GPIOF)最终的结果是0x40021018。

宏定义BIT就是获取GPIO具体的使能位。

BIT(RCU_BIT_POS(RCU_GPIOF))最终的结果就是0x64。

最终的函数替换后如下:

0x40021018 |= 0x64;

都是宏定义直接替换就行,还是比较简单。

这里需要注意RCU_REGIDX_BIT宏定义。

#define RCU_REGIDX_BIT(regidx, bitpos)      (((uint32_t)(regidx) << 6) | (uint32_t)(bitpos))

该宏定义就是将要配置的寄存器偏移和bit位绑定在一起,然后再通过以下宏定义分开偏移和bit位。

#define RCU_REG_VAL(periph)                 (REG32(RCU + ((uint32_t)(periph) >> 6)))
#define RCU_BIT_POS(val)                    ((uint32_t)(val) & 0x1FU)

RCU的APB2使能寄存器如下:

1683641321195lnosma58zk

1683641321454ds8h5a7sen

这里配置GPIOF的时钟,需要将第7位置1,因此转换成10进制就是64,和代码就匹配起来了。

GD32的固件库和STM32的固件库还是有一些差别的,但是不管如何,最终都是配置的寄存器,只是STM32通过结构体对外设进行了封装,GD32是通过宏定义直接替换,偏向于直接操作寄存器。

3 GD32的地址映射

我们先看看51 单片机中是怎么做的,51 单片机开发中会引用一个 reg51.h 的头文件,51单片机是通过以下方式将名字和寄存器联系起来的:

sfr P0 =0x80;

sfr 也是一种扩充数据类型,占用一个内存单元,值域为 0~255。利用它可以访问 51 单片机内部的所有特殊功能寄存器。如用 sfr P1 = 0x90 这一句定义 P1 为 P1 端口在片内的寄存器。然后我们往地址为 0x80 的寄存器设值的方法是: P0=value;通过改变value的值来控制单片机。

16836413217748u4ellyv1o

所谓地址映射,就是将芯片上的存储器甚至 I/O 等资源与地址建立一一对应的关系。如果某地址对应着某寄存器,我们就可以运用 C 语言的指针来寻址并修改这个地址上的内容,从而实现修改该寄存器的内容。Cortex-M的地址映射也是类似的。Cortex-M有 32 根地址线,所以它的寻址空间大小为 2 32 bit=4 GB。ARM 公司设计时,预先把这 4 GB 的寻址空间大致地分配好了。它把从 0x40000000 至 0x5FFFFFFF( 512 MB)的地址分配给片上外设。通过把片上外设的寄存器映射到这个地址区,就可以简单地以访问内存的方式,访问这些外设的寄存器,从而控制外设的工作。这样,片上外设可以使用 C 语言来操作。

gd32f10x.h 这个文件中重要的内容就是把 GD32 的所有寄存器进行地址映射。如同51 单片机的 头文件一样,gd32f10x.h 像一个大表格,我们在使用的时候就是通过宏定义进行类似查表的操作,但是这样操作会很麻烦,而且32位的MCU寄存器很多,非常不方便。于是就有了现在的固件库。

在这里我们以流水灯中的 GPIOF为例进行剖析,如果是其他的 IO 端口,则改成相应的地址即可。在这个文件中一系列宏实现了地址映射。

#define APB2_BUS_BASE         ((uint32_t)0x40010000U)        /*!< apb2 base address                */
#define GPIO_BASE             (APB2_BUS_BASE + 0x00000800U)  /*!< GPIO base address                */

这几个宏定义是从文件中的几个部分抽离出来的,具体的内容读者可参考gd32f10x.h 源码。

宏APB2_BUS_BASE指向的地址为 0x40010000。这个 APB2_BUS_BASE宏是什么地址呢?GD32 不同的外设是挂载在不同的总线上的。GD32 芯片有 AHB 总线、APB2总线和 APB1 总线,挂载在这些总线上的外设有特定的地址范围。其中像 GPIO、串口 1、ADC 及部分定时器是挂载在称为 APB2 的总线上,挂载到APB2 总 线上的外设地址空间是从0x40010000 至 0x40017FFF地址。这里的第一个地址,也就是 0x40010000,称为 APB2_BUS_BASE(APB2 总线外设基地址)。

而 APB2 总线基地址相对于外设基地址的偏移量为 0x10000 个地址,即为 APB2 相对外设基地址的偏移地址。

最后到了宏 GPIO_BASE,宏展开为 APB2_BUS_BASE加上偏移量 0x1400得到了 GPIO端口的寄存器组的基地址。

在gd32f20x_gpio.h 文件,我们还可以发现有关各个 GPIO 基地址的宏。

/* GPIOx(x=A,B,C,D,E,F,G,H,I) definitions */
#define GPIOA                      (GPIO_BASE + 0x00000000U)       /*!< GPIOA bsae address */
#define GPIOB                      (GPIO_BASE + 0x00000400U)       /*!< GPIOB bsae address */
#define GPIOC                      (GPIO_BASE + 0x00000800U)       /*!< GPIOC bsae address */
#define GPIOD                      (GPIO_BASE + 0x00000C00U)       /*!< GPIOD bsae address */
#define GPIOE                      (GPIO_BASE + 0x00001000U)       /*!< GPIOE bsae address */
#define GPIOF                      (GPIO_BASE + 0x00001400U)       /*!< GPIOF bsae address */
#define GPIOG                      (GPIO_BASE + 0x00001800U)       /*!< GPIOG bsae address */
#define GPIOH                      (GPIO_BASE + 0x00006C00U)       /*!< GPIOH bsae address */
#define GPIOI                      (GPIO_BASE + 0x00007000U)       /*!< GPIOI bsae address */

除了 GPIOF寄存器组的地址,还有 GPIOA、GPIOB等地址,并且这些地址是不一样的。前面提到,每组 GPIO 都对应着独立的一组寄存器,查看 GD32 的数据手册。

注意到这个说明中有一个偏移地址:0x1400,这里的偏移地址是相对哪个地址的偏移呢?下面进行举例说明。

4 固件库对寄存器的封装

GD的工程师用结构体的形式封装了寄存器组,在gd32f20x_gpio.h文件定义的。

/* GPIOx(x=A,B,C,D,E,F,G,H,I) definitions */
#define GPIOA                      (GPIO_BASE + 0x00000000U)       /*!< GPIOA bsae address */
#define GPIOB                      (GPIO_BASE + 0x00000400U)       /*!< GPIOB bsae address */
#define GPIOC                      (GPIO_BASE + 0x00000800U)       /*!< GPIOC bsae address */
#define GPIOD                      (GPIO_BASE + 0x00000C00U)       /*!< GPIOD bsae address */
#define GPIOE                      (GPIO_BASE + 0x00001000U)       /*!< GPIOE bsae address */
#define GPIOF                      (GPIO_BASE + 0x00001400U)       /*!< GPIOF bsae address */
#define GPIOG                      (GPIO_BASE + 0x00001800U)       /*!< GPIOG bsae address */
#define GPIOH                      (GPIO_BASE + 0x00006C00U)       /*!< GPIOH bsae address */
#define GPIOI                      (GPIO_BASE + 0x00007000U)       /*!< GPIOI bsae address */

有了这些宏,我们就可以定位到具体的寄存器地址,gd32f10x_gpio.h 文件中定义了以下类型的宏定义。

/* GPIO registers definitions */
#define GPIO_CTL0(gpiox)           REG32((gpiox) + 0x00000000U)    /*!< GPIO port control register 0 */
#define GPIO_CTL1(gpiox)           REG32((gpiox) + 0x00000004U)    /*!< GPIO port control register 1 */
#define GPIO_ISTAT(gpiox)          REG32((gpiox) + 0x00000008U)    /*!< GPIO port input status register */
#define GPIO_OCTL(gpiox)           REG32((gpiox) + 0x0000000CU)    /*!< GPIO port output control register */
#define GPIO_BOP(gpiox)            REG32((gpiox) + 0x00000010U)    /*!< GPIO port bit operation register */
#define GPIO_BC(gpiox)             REG32((gpiox) + 0x00000014U)    /*!< GPIO bit clear register */
#define GPIO_LOCK(gpiox)           REG32((gpiox) + 0x00000018U)    /*!< GPIO port configuration lock register */

这里定义了 7 个宏定义,两个宏之间是4 个字节地址的偏移量。

0x010偏移量正是 GPIOx_BOP寄存器相对于所在寄存器组的偏移地址。

168364132205391k72qmavp

通过类似的方式,我们就可以给具体的寄存器写上适当的参数以控制 GD32 了。

这样我们就可以通过库函数实现了GPIO的初始化了。

/*!
    \\brief      GPIO parameter initialization
    \\param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G,H,I)
    \\param[in]  mode: gpio pin mode
                only one parameter can be selected which is shown as below:
      \\arg        GPIO_MODE_AIN: analog input mode
      \\arg        GPIO_MODE_IN_FLOATING: floating input mode
      \\arg        GPIO_MODE_IPD: pull-down input mode
      \\arg        GPIO_MODE_IPU: pull-up input mode
      \\arg        GPIO_MODE_OUT_OD: GPIO output with open-drain
      \\arg        GPIO_MODE_OUT_PP: GPIO output with push-pull
      \\arg        GPIO_MODE_AF_OD: AFIO output with open-drain
      \\arg        GPIO_MODE_AF_PP: AFIO output with push-pull
    \\param[in]  speed: gpio output max speed value
                only one parameter can be selected which is shown as below:
      \\arg        GPIO_OSPEED_10MHZ: output max speed 10MHz
      \\arg        GPIO_OSPEED_2MHZ: output max speed 2MHz
      \\arg        GPIO_OSPEED_50MHZ: output max speed 50MHz
    \\param[in]  pin: GPIO pin
                one or more parameters can be selected which are shown as below:
      \\arg        GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
    \\param[out] none
    \\retval     none
*/
void gpio_init(uint32_t gpio_periph, uint32_t mode, uint32_t speed, uint32_t pin)
{
    uint16_t i;
    uint32_t temp_mode = 0U;
    uint32_t reg = 0U;

    /* GPIO mode configuration */
    temp_mode = (uint32_t)(mode & ((uint32_t)0x0FU));

    /* GPIO speed configuration */
    if(((uint32_t)0x00U) != ((uint32_t)mode & ((uint32_t)0x10U))) {
        /* output mode max speed: 10MHz, 2MHz, 50MHz */
        temp_mode |= (uint32_t)speed;
    }

    /* configure the eight low port pins with GPIO_CTL0 */
    for(i = 0U; i < 8U; i++) {
        if((1U << i) & pin) {
            reg = GPIO_CTL0(gpio_periph);
            /* clear the specified pin mode bits */
            reg &= ~GPIO_MODE_MASK(i);
            /* set the specified pin mode bits */
            reg |= GPIO_MODE_SET(i, temp_mode);

            /* set IPD or IPU */
            if(GPIO_MODE_IPD == mode) {
                /* reset the corresponding OCTL bit */
                GPIO_BC(gpio_periph) = (uint32_t)((1U << i) & pin);
            } else {
                /* set the corresponding OCTL bit */
                if(GPIO_MODE_IPU == mode) {
                    GPIO_BOP(gpio_periph) = (uint32_t)((1U << i) & pin);
                }
            }
            /* set GPIO_CTL0 register */
            GPIO_CTL0(gpio_periph) = reg;
        }
    }
    /* configure the eight high port pins with GPIO_CTL1 */
    for(i = 8U; i < 16U; i++) {
        if((1U << i) & pin) {
            reg = GPIO_CTL1(gpio_periph);
            /* clear the specified pin mode bits */
            reg &= ~GPIO_MODE_MASK(i - 8U);
            /* set the specified pin mode bits */
            reg |= GPIO_MODE_SET(i - 8U, temp_mode);

            /* set IPD or IPU */
            if(GPIO_MODE_IPD == mode) {
                /* reset the corresponding OCTL bit */
                GPIO_BC(gpio_periph) = (uint32_t)((1U << i) & pin);
            } else {
                /* set the corresponding OCTL bit */
                if(GPIO_MODE_IPU == mode) {
                    GPIO_BOP(gpio_periph) = (uint32_t)((1U << i) & pin);
                }
            }
            /* set GPIO_CTL1 register */
            GPIO_CTL1(gpio_periph) = reg;
        }
    }
}


然后再main函数中调用gpio\\_init\\(\\)函数接口对GPIO初始化了。

通过对时钟和GPIO的分析,我想大家已经对固件的逻辑有了一定的认识,从本质上讲,都是在配置寄存器,只是地址和值不同罢了,而固件库就是对寄存器配置的封装,便于开发者调用。

值得注意的是,GD32的固件库并没有使用结构体来对寄存器组进行封装,全程用的宏定义,这点和STM32有很大的不同。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • mcu
    mcu
    +关注

    关注

    146

    文章

    16017

    浏览量

    343632
  • 流水灯
    +关注

    关注

    21

    文章

    432

    浏览量

    59104
  • 开发板
    +关注

    关注

    25

    文章

    4429

    浏览量

    93999
  • GPIO
    +关注

    关注

    16

    文章

    1133

    浏览量

    50573
  • GD32
    +关注

    关注

    7

    文章

    333

    浏览量

    23740
收藏 人收藏

    评论

    相关推荐

    GD32开发实战指南(基础篇) 第2章 初始GPIO流水灯

    熟悉单片机的朋友都知道,学习的第一个例程就是流水灯,要想实现流水灯,首先必须了解GPIO的工作原理。GPIO的基本结构如下图所示。
    的头像 发表于 05-08 09:03 6466次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础篇) 第2章 初始<b class='flag-5'>GPIO</b><b class='flag-5'>流水灯</b>

    ARM Cortex-M学习笔记:初识GPIO流水灯

    熟悉单片机的朋友都知道,学习的第一个例程就是流水灯,要想实现流水灯,首先必须了解GPIO的工作原理。GPIO的基本结构如图1所示。
    的头像 发表于 05-15 11:31 2224次阅读
    ARM Cortex-M学习笔记:初识<b class='flag-5'>GPIO</b><b class='flag-5'>流水灯</b>

    ARM Cortex-M学习笔记:GPIO流水灯前世今生

    上一章通过控制GPIO的高低电平实现了流水灯,但只是告诉了大家怎么做,如何实现流水灯,本文将深入剖析的GPIO流水灯的前生今世,深入研究
    的头像 发表于 05-15 14:44 1928次阅读
    ARM Cortex-M学习笔记:<b class='flag-5'>GPIO</b><b class='flag-5'>流水灯</b>的<b class='flag-5'>前世</b><b class='flag-5'>今生</b>

    GD32开发实战指南(基础篇) 第16章 RTC

    开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 RTC工作原理 1.1 RTC简介
    的头像 发表于 05-18 22:14 5740次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础篇) 第16章 RTC

    【图书分享】《STM32库开发实战指南

    GPIO入门之流水灯 4 深入分析流水灯例程 5
    发表于 03-13 17:01

    好书分享】入门经典:STM32库开发实战指南

    相关专业的教材,也适合作为从事嵌入式领域科技工作者的参考书。前言 第一部分 库开发初级 1 为什么学习STM32 2
    发表于 06-24 20:53

    Arduino开发实战指南 AVR

    第一基础1初识Arduino2编写Arduino程序
    发表于 08-03 16:14

    嵌入式ARM开发前世今生,看完你就懂了

    嵌入式ARM的开发方向是什么?嵌入式ARM开发前世今生
    发表于 04-20 06:39

    什么是GD32

    是不同的产品,不可能所有东西都沿用STM32,有些自主开发的东西还是有区别的。相同的地方我们就不说了,下面我给大家讲一下不同的地方。二、区别1、内核GD32采用二代的M3内核,STM32主要采用一代M
    发表于 08-12 07:46

    每日推荐 | STM32Cube高效开发教程,电源系统管理设计系列知识

    指南(基于RT-Thread系统)》3 GPIO使用和《嵌入式系统 – GD32应用
    发表于 05-09 10:38

    GD32 MCU原理及固件库开发指南》 + 初读感悟

    GD32 MCU原理固件库开发指南这本书内容丰富,囊括了GD32中的所有外设,书中首先介绍了如何使用MDK或IAR软件搭建GD32工程环境,让初学者能快速基于工程上手编程。书中主要对
    发表于 03-31 22:11

    GD32 MCU原理及固件库开发指南》+读后感

    2介绍GD32 MCU快速入门与开发平台搭建的方法,包括对软硬件开发平台、调试工具、GD32
    发表于 06-06 21:52

    GPIO-流水灯的前后今生

    主要是STM32系列的GPIO-流水灯的前后今生进行详细的讲解,需要的自行下载
    发表于 06-17 16:48 8次下载

    GD32开发实战指南(基础篇) 第19章 程序加密

    GD32通过读取芯片唯一ID号来实现程序的保护,防止被抄袭。96位的产品唯一身份标识所提供的参考号码对任意一个GD32微控制器
    的头像 发表于 05-20 09:10 3386次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础篇) 第19章 程序加密

    GD32 MCU如何将烧录口配置为GPIO使用?

    如果大家在进行GD32 MCU开发时发现GPIO引脚使用不足,可以尝试将烧录口配置为GPIO使用,这样就可以多出几个引脚使用,但使用的时候如何配置以及有哪些注意事项,本视频将会为大家进
    的头像 发表于 02-20 10:18 479次阅读
    <b class='flag-5'>GD32</b> MCU如何将烧录口配置为<b class='flag-5'>GPIO</b>使用?