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

[复制链接]
1915|22
 楼主| gaoke231 发表于 2021-1-6 20:49 | 显示全部楼层 |阅读模式
第一步:怎么找到GPIO的基地址?
参考手册知道GPIO寄存的基地址,可以看到GPIOA~GPIOI的基地址间隔0x400,这些地址空间用作每一个GPIO的端口自己的私有寄存器。
113265ff5b1dea4561.png

 楼主| gaoke231 发表于 2021-1-6 20:50 | 显示全部楼层
第二步:怎么初始化GPIO的引脚?
先看一下固件库是如何实现GPIO的引脚的初始化的,我们写程序
  1. void gpio_init(uint32_t gpio_periph, uint32_t mode, uint32_t speed, uint32_t pin)
  2. {
  3.     uint16_t i;
  4.     uint32_t temp_mode = 0U;
  5.     uint32_t reg = 0U;

  6.     /* GPIO mode configuration */
  7.     temp_mode = (uint32_t)(mode & ((uint32_t)0x0FU));
  8.    
  9.     /* GPIO speed configuration */
  10.     if(((uint32_t)0x00U) != ((uint32_t)mode & ((uint32_t)0x10U))){
  11.         /* output mode max speed:10MHz,2MHz,50MHz */
  12.         temp_mode |= (uint32_t)speed;
  13.     }

  14.     /* configure the eight low port pins with GPIO_CTL0 */
  15.     for(i = 0U;i < 8U;i++){
  16.         if((1U << i) & pin){
  17.             reg = GPIO_CTL0(gpio_periph);
  18.             
  19.             /* clear the specified pin mode bits */
  20.             reg &= ~GPIO_MODE_MASK(i);
  21.             /* set the specified pin mode bits */
  22.             reg |= GPIO_MODE_SET(i, temp_mode);
  23.             
  24.             /* set IPD or IPU */
  25.             if(GPIO_MODE_IPD == mode){
  26.                 /* reset the corresponding OCTL bit */
  27.                 GPIO_BC(gpio_periph) = (uint32_t)((1U << i) & pin);
  28.             }else{
  29.                 /* set the corresponding OCTL bit */
  30.                 if(GPIO_MODE_IPU == mode){
  31.                     GPIO_BOP(gpio_periph) = (uint32_t)((1U << i) & pin);
  32.                 }
  33.             }
  34.             /* set GPIO_CTL0 register */
  35.             GPIO_CTL0(gpio_periph) = reg;
  36.         }
  37.     }
  38.     /* configure the eight high port pins with GPIO_CTL1 */
  39.     for(i = 8U;i < 16U;i++){
  40.         if((1U << i) & pin){
  41.             reg = GPIO_CTL1(gpio_periph);
  42.             
  43.             /* clear the specified pin mode bits */
  44.             reg &= ~GPIO_MODE_MASK(i - 8U);
  45.             /* set the specified pin mode bits */
  46.             reg |= GPIO_MODE_SET(i - 8U, temp_mode);
  47.             
  48.             /* set IPD or IPU */
  49.             if(GPIO_MODE_IPD == mode){
  50.                 /* reset the corresponding OCTL bit */
  51.                 GPIO_BC(gpio_periph) = (uint32_t)((1U << i) & pin);
  52.             }else{
  53.                 /* set the corresponding OCTL bit */
  54.                 if(GPIO_MODE_IPU == mode){
  55.                     GPIO_BOP(gpio_periph) = (uint32_t)((1U << i) & pin);
  56.                 }
  57.             }
  58.             /* set GPIO_CTL1 register */
  59.             GPIO_CTL1(gpio_periph) = reg;
  60.         }
  61.     }
  62. }


 楼主| gaoke231 发表于 2021-1-6 20:50 | 显示全部楼层
在固件库中是如何来初始化GPIO的引脚的呢?我们先不着急,先来看两个寄存器,同样是在参考手册GPIO章节可以看到。
端口控制寄存器 0和 端口控制寄存器 1,端口端口控制寄存器 0负责Pin0~Pin7 , 端口端口控制寄存器 1负责Pin8~Pin15的配置。
 楼主| gaoke231 发表于 2021-1-6 20:52 | 显示全部楼层
端口控制寄存器 0
781535ff5b244246d3.png
 楼主| gaoke231 发表于 2021-1-6 20:55 | 显示全部楼层
端口控制寄存器 1
815505ff5b28b65cea.png
 楼主| gaoke231 发表于 2021-1-6 20:56 | 显示全部楼层
不管是端口控制寄存器 0还是端口控制寄存器 1,每一个Pin的配置引脚都有4bit来配置,分别是CTLx和MDx的控制,我以Pin 0配置为例,来说明一下应该CTLx和MDx如何配置。
先来看MD0,可知,当bit1:0 = 00时为输入模式,为01时为输出模式。
311075ff5b367d5436.png
 楼主| gaoke231 发表于 2021-1-6 21:08 | 显示全部楼层
再来看CTL0,可知,当MD0为输入模式时,bit3:2 = 00为模拟输入,为01时为浮空输入,为10时为上下拉输入。当MD0为输出模式时,bit3:2 = 00为推完输出,为01时为开漏输出…..
940995ff5b37b8ff83.png
 楼主| gaoke231 发表于 2021-1-6 21:09 | 显示全部楼层
以上明白之后,我们需要做的工作就是确定 端口控制寄存器 0还是端口控制寄存器 1的地址,只有知道了地址,我们才可以操作对应的bit的值,从而为相应的端口引脚配置相应的模式。我们通过GPIO的基地址偏移0x00来确定端口控制寄存器 0的地址,通过偏移0x04来确定端口控制寄存器 1的地址。
147725ff5b68767364.png
 楼主| gaoke231 发表于 2021-1-6 21:10 | 显示全部楼层
好了,我们来看一下标准库是如何做的?下面的代码可以清楚的看到标准库操作的来龙去脉。
  1. // gd32f20x.h  文件
  2. #define APB2_BUS_BASE        ((uint32_t)0x40010000U)  
  3. #define GPIO_BASE             (APB2_BUS_BASE + 0x00000800U)
  4. #define REG32(addr)            (*(volatile uint32_t *)(uint32_t)(addr))

  5. // gd32f20x_gpio.h  文件
  6. #define GPIOA                      (GPIO_BASE + 0x00000000U)
  7. #define GPIOB                      (GPIO_BASE + 0x00000400U)
  8. #define GPIOC                      (GPIO_BASE + 0x00000800U)
  9. #define GPIOD                      (GPIO_BASE + 0x00000C00U)
  10. #define GPIOE                      (GPIO_BASE + 0x00001000U)
  11. #define GPIOF                      (GPIO_BASE + 0x00001400U)
  12. #define GPIOG                      (GPIO_BASE + 0x00001800U)
  13. #define GPIOH                      (GPIO_BASE + 0x00006C00U)
  14. #define GPIOI                      (GPIO_BASE + 0x00007000U)

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


 楼主| 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的值。
一路向北lm 发表于 2021-1-7 08:57 | 显示全部楼层
总结的不错,希望可以继续更新
 楼主| gaoke231 发表于 2021-1-7 21:14 | 显示全部楼层
一路向北lm 发表于 2021-1-7 08:57
总结的不错,希望可以继续更新

感谢支持,今天再更新一些
 楼主| 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)))
 楼主| 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))))
 楼主| 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));  
 楼主| gaoke231 发表于 2021-1-7 21:23 | 显示全部楼层
gpio_init 函数将参数mode传入进来,此时可以得到temp_mode,传入给gpio_init 函数已经定义在 gd32f20x_gpio.h  文件中,具体如下:
  1. /* GPIO mode definitions */
  2. #define GPIO_MODE_AIN                    ((uint8_t)0x00U)         
  3. #define GPIO_MODE_IN_FLOATING           ((uint8_t)0x04U)         
  4. #define GPIO_MODE_IPD                    ((uint8_t)0x28U)         
  5. #define GPIO_MODE_IPU                    ((uint8_t)0x48U)         
  6. #define GPIO_MODE_OUT_OD                 ((uint8_t)0x14U)         
  7. #define GPIO_MODE_OUT_PP                 ((uint8_t)0x10U)         
  8. #define GPIO_MODE_AF_OD                  ((uint8_t)0x1CU)         
  9. #define GPIO_MODE_AF_PP                  ((uint8_t)0x18U)


 楼主| gaoke231 发表于 2021-1-7 21:24 | 显示全部楼层
这里定义的宏并没有直接根据对应寄存器的位来设定,只是绕了一个弯,大家可以仔细看一下,本来GPIO_MODE_AIN 为0x00 和GPIO_MODE_IN_FLOATING为0x04 正确,但是GPIO_MODE_IPD 和GPIO_MODE_IPU 就变成了0x28和0x48,为了区分上拉还是下拉,因此将0x8稍作改变,对于输出模式也是一样。
 楼主| gaoke231 发表于 2021-1-7 21:25 | 显示全部楼层
如何处理上拉与下拉?库处理的方式如下
  1. /* set IPD or IPU */
  2. if(GPIO_MODE_IPD == mode)
  3. {
  4. /* reset the corresponding OCTL bit */
  5. GPIO_BC(gpio_periph) = (uint32_t)((1U << i) & pin);
  6. }
  7. else
  8. {
  9.         /* set the corresponding OCTL bit */
  10.         if(GPIO_MODE_IPU == mode){
  11.                 GPIO_BOP(gpio_periph) = (uint32_t)((1U << i) & pin);
  12.         }
  13. }


 楼主| gaoke231 发表于 2021-1-7 21:26 | 显示全部楼层
主要关注GPIO_BC和GPIO_BOP宏,在输入模式下使用GPIO_BC宏输出低电平来配置为下拉模式,使用GPIO_BOP宏输出高电平来配置上拉模式。
 楼主| gaoke231 发表于 2021-1-7 21:30 | 显示全部楼层
最后如何配置模式?
库使用GPIO_CTL0(gpio_periph) = reg; 将reg写入到GPIO_CTL0中来完成对GPIO引脚的模式配置。
556985ff70c0f72657.png
您需要登录后才可以回帖 登录 | 注册

本版积分规则

54

主题

1310

帖子

5

粉丝
快速回复 在线客服 返回列表 返回顶部