打印

研究GD32 GPIO外设库是怎么写的

[复制链接]
1060|22
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
第一步:怎么找到GPIO的基地址?
参考手册知道GPIO寄存的基地址,可以看到GPIOA~GPIOI的基地址间隔0x400,这些地址空间用作每一个GPIO的端口自己的私有寄存器。

使用特权

评论回复
沙发
gaoke231|  楼主 | 2021-1-6 20:50 | 只看该作者
第二步:怎么初始化GPIO的引脚?
先看一下固件库是如何实现GPIO的引脚的初始化的,我们写程序
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;
        }
    }
}


使用特权

评论回复
板凳
gaoke231|  楼主 | 2021-1-6 20:50 | 只看该作者
在固件库中是如何来初始化GPIO的引脚的呢?我们先不着急,先来看两个寄存器,同样是在参考手册GPIO章节可以看到。
端口控制寄存器 0和 端口控制寄存器 1,端口端口控制寄存器 0负责Pin0~Pin7 , 端口端口控制寄存器 1负责Pin8~Pin15的配置。

使用特权

评论回复
地板
gaoke231|  楼主 | 2021-1-6 20:52 | 只看该作者
端口控制寄存器 0

使用特权

评论回复
5
gaoke231|  楼主 | 2021-1-6 20:55 | 只看该作者
端口控制寄存器 1

使用特权

评论回复
6
gaoke231|  楼主 | 2021-1-6 20:56 | 只看该作者
不管是端口控制寄存器 0还是端口控制寄存器 1,每一个Pin的配置引脚都有4bit来配置,分别是CTLx和MDx的控制,我以Pin 0配置为例,来说明一下应该CTLx和MDx如何配置。
先来看MD0,可知,当bit1:0 = 00时为输入模式,为01时为输出模式。

使用特权

评论回复
7
gaoke231|  楼主 | 2021-1-6 21:08 | 只看该作者
再来看CTL0,可知,当MD0为输入模式时,bit3:2 = 00为模拟输入,为01时为浮空输入,为10时为上下拉输入。当MD0为输出模式时,bit3:2 = 00为推完输出,为01时为开漏输出…..

使用特权

评论回复
8
gaoke231|  楼主 | 2021-1-6 21:09 | 只看该作者
以上明白之后,我们需要做的工作就是确定 端口控制寄存器 0还是端口控制寄存器 1的地址,只有知道了地址,我们才可以操作对应的bit的值,从而为相应的端口引脚配置相应的模式。我们通过GPIO的基地址偏移0x00来确定端口控制寄存器 0的地址,通过偏移0x04来确定端口控制寄存器 1的地址。

使用特权

评论回复
9
gaoke231|  楼主 | 2021-1-6 21:10 | 只看该作者
好了,我们来看一下标准库是如何做的?下面的代码可以清楚的看到标准库操作的来龙去脉。
// gd32f20x.h  文件
#define APB2_BUS_BASE        ((uint32_t)0x40010000U)  
#define GPIO_BASE             (APB2_BUS_BASE + 0x00000800U)
#define REG32(addr)            (*(volatile uint32_t *)(uint32_t)(addr))

// gd32f20x_gpio.h  文件
#define GPIOA                      (GPIO_BASE + 0x00000000U)
#define GPIOB                      (GPIO_BASE + 0x00000400U)
#define GPIOC                      (GPIO_BASE + 0x00000800U)
#define GPIOD                      (GPIO_BASE + 0x00000C00U)
#define GPIOE                      (GPIO_BASE + 0x00001000U)
#define GPIOF                      (GPIO_BASE + 0x00001400U)
#define GPIOG                      (GPIO_BASE + 0x00001800U)
#define GPIOH                      (GPIO_BASE + 0x00006C00U)
#define GPIOI                      (GPIO_BASE + 0x00007000U)

#define GPIO_CTL0(gpiox)           REG32((gpiox) + 0x00U)   
#define GPIO_CTL1(gpiox)           REG32((gpiox) + 0x04U)   


使用特权

评论回复
10
gaoke231|  楼主 | 2021-1-6 21:11 | 只看该作者
以上代码可以看到,我们并不需要定义GPIO_CTL0的地址,通过GPIO的基地址:GPIO_BASE  加偏移量可以确定GPIO端口的地址,再通过GPIO端口地址加偏移来确定 CTL0和CTL1寄存器的地址。因此,如果需要读取或者对GPIO_CTL0和GPIO_CTL1寄存器的值,只需要使用GPIO_CTL0(gpiox)即可,当使用GPIO_CTL0(GPIOA)时,即可读取或者设置GPIO端口A的CTL0的值。

使用特权

评论回复
11
一路向北lm| | 2021-1-7 08:57 | 只看该作者
总结的不错,希望可以继续更新

使用特权

评论回复
12
gaoke231|  楼主 | 2021-1-7 21:14 | 只看该作者
一路向北lm 发表于 2021-1-7 08:57
总结的不错,希望可以继续更新

感谢支持,今天再更新一些

使用特权

评论回复
13
gaoke231|  楼主 | 2021-1-7 21:15 | 只看该作者
库对于GPIO_CTL0的GPIO_CTL1的操作首先是读取GPIO_CTL0得值,使用reg = GPIO_CTL0(gpio_periph); gpio_periph 是需要填入的GPIO口,然后使用reg &= ~GPIO_MODE_MASK(i); 先清零需要配置引脚的的bit位,GPIO_MODE_MASK的宏定义如下:
#define GPIO_MODE_MASK(n)                (0xFU << (4U * (n)))

使用特权

评论回复
14
gaoke231|  楼主 | 2021-1-7 21:16 | 只看该作者
因此,可以写成 reg &= ~(0xFU << (4U * (n))); 这里可以看出参数n对应每一个Pin引脚,使用循环的方式可以依次对每一个Pin引脚的配置位清零。清零对应的bit位后,需要根据设定的模式来进行正确的配置,库使用 reg |= GPIO_MODE_SET(i, temp_mode); 来执行对选择的Pin引脚进行配置,GPIO_MODE_SET定义的宏如下:
#define GPIO_MODE_SET(n, mode)           ((uint32_t)((uint32_t)(mode) << (4U * (n))))

使用特权

评论回复
15
gaoke231|  楼主 | 2021-1-7 21:17 | 只看该作者
从而可以写成 reg |= ((uint32_t)((uint32_t)(mode) << (4U * (n)))),传入的为对应Pin引脚,mode参数是需要配置的模式变量。库使用的是temp_mode 变量,我们来追踪一下此变量,如下所示: temp_mode = (uint32_t)(mode & ((uint32_t)0x0FU));  

使用特权

评论回复
16
gaoke231|  楼主 | 2021-1-7 21:23 | 只看该作者
gpio_init 函数将参数mode传入进来,此时可以得到temp_mode,传入给gpio_init 函数已经定义在 gd32f20x_gpio.h  文件中,具体如下:
/* GPIO mode definitions */
#define GPIO_MODE_AIN                    ((uint8_t)0x00U)         
#define GPIO_MODE_IN_FLOATING           ((uint8_t)0x04U)         
#define GPIO_MODE_IPD                    ((uint8_t)0x28U)         
#define GPIO_MODE_IPU                    ((uint8_t)0x48U)         
#define GPIO_MODE_OUT_OD                 ((uint8_t)0x14U)         
#define GPIO_MODE_OUT_PP                 ((uint8_t)0x10U)         
#define GPIO_MODE_AF_OD                  ((uint8_t)0x1CU)         
#define GPIO_MODE_AF_PP                  ((uint8_t)0x18U)


使用特权

评论回复
17
gaoke231|  楼主 | 2021-1-7 21:24 | 只看该作者
这里定义的宏并没有直接根据对应寄存器的位来设定,只是绕了一个弯,大家可以仔细看一下,本来GPIO_MODE_AIN 为0x00 和GPIO_MODE_IN_FLOATING为0x04 正确,但是GPIO_MODE_IPD 和GPIO_MODE_IPU 就变成了0x28和0x48,为了区分上拉还是下拉,因此将0x8稍作改变,对于输出模式也是一样。

使用特权

评论回复
18
gaoke231|  楼主 | 2021-1-7 21:25 | 只看该作者
如何处理上拉与下拉?库处理的方式如下
/* 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);
        }
}


使用特权

评论回复
19
gaoke231|  楼主 | 2021-1-7 21:26 | 只看该作者
主要关注GPIO_BC和GPIO_BOP宏,在输入模式下使用GPIO_BC宏输出低电平来配置为下拉模式,使用GPIO_BOP宏输出高电平来配置上拉模式。

使用特权

评论回复
20
gaoke231|  楼主 | 2021-1-7 21:30 | 只看该作者
最后如何配置模式?
库使用GPIO_CTL0(gpio_periph) = reg; 将reg写入到GPIO_CTL0中来完成对GPIO引脚的模式配置。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

54

主题

1310

帖子

5

粉丝