« 上一篇下一篇 »

STM32——库函数分析:GPIO相关

个人觉得,今天要说的这个内容,还是有点儿技术含量。在分析代码之前,先谈点感想,我是不得不佩服写出这些代码的前辈(真乃神人也!),我只是理解这些代码就痛苦不堪,差点儿还放弃了,这些前辈却能够自己想出这些代码,我真的是佩服的五体投地。前辈在上,请接受我万分的景仰。

以下库函数均来自于STM32F1系列控制器的外设函数库。

 /**
  * @brief  初始化控制LcD的IO
  * @param  无
  * @retval 无
  */
void LCD_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    /* 使能FSMC时钟*/
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);		// 开启FSMC时钟
    
    /* 使能FSMC对应相应管脚时钟*/													// GPIOB 时钟开启
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    
    /* 配置LCD背光控制管脚*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;		
    GPIO_Init(GPIOB, &GPIO_InitStructure); 
}
/**
  * @brief  Initializes the GPIOx peripheral according to the specified
  *         parameters in the GPIO_InitStruct.
  * @param  GPIOx: where x can be (A..G) to select the GPIO peripheral.
  * @param  GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that
  *         contains the configuration information for the specified GPIO peripheral.
  * @retval None
  */
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
  uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
  uint32_t tmpreg = 0x00, pinmask = 0x00;
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));  
  
/*---------------------------- GPIO Mode Configuration -----------------------*/
  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
  if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
  { 
    /* Check the parameters */
    assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
    /* Output mode */
    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
  }
/*---------------------------- GPIO CRL Configuration ------------------------*/
  /* Configure the eight low port pins */
  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)	// 相与结果为0x02,所以不为0,即条件符合
  {
    tmpreg = GPIOx->CRL;				// 这里的GPIOX=GPIOB,而GPIOB为一个结构体指针,可以看其定义就知道,而CRL为该结构体成员之一
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = ((uint32_t)0x01) << pinpos;
      /* Get the port pins position */
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;			// 只有两数相等时相与的结果才不为0,且等于pos值
      if (currentpin == pos)		// 当上面结果不为0时,即符合判断条件
      {
        pos = pinpos << 2;
        /* Clear the corresponding low control register bits */
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
        /* Write the mode configuration in the corresponding bits */
        tmpreg |= (currentmode << pos);
        /* Reset the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);
        }
        else
        {
          /* Set the corresponding ODR bit */
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
          {
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
    GPIOx->CRL = tmpreg;									// 哎,太叨了
  }
/*---------------------------- GPIO CRH Configuration ------------------------*/
  /* Configure the eight high port pins */
  if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
  {
    tmpreg = GPIOx->CRH;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = (((uint32_t)0x01) << (pinpos + 0x08));
      /* Get the port pins position */
      currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
      if (currentpin == pos)
      {
        pos = pinpos << 2;
        /* Clear the corresponding high control register bits */
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
        /* Write the mode configuration in the corresponding bits */
        tmpreg |= (currentmode << pos);
        /* Reset the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
          GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
        /* Set the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
        {
          GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
      }
    }
    GPIOx->CRH = tmpreg;
  }
}

我们从第一个函数开始分析,这是一个初始化LCD的函数,这里我们不讲太多其他的,主要是为了分析GPIO_Init()这个函数。首先定义一个结构体GPIO_InitTypeDef GPIO_InitStructure;

结构体的类型为GPIO_InitTypeDef,我们来看下这个类型的定义。

/** 
   * @brief  GPIO Init structure definition  
   */
 typedef struct
 {
   uint16_t GPIO_Pin;             /*!< Specifies the GPIO pins to be configured.
                                       This parameter can be any value of @ref GPIO_pins_define */
 
   GPIOSpeed_TypeDef GPIO_Speed;  /*!< Specifies the speed for the selected pins.
                                       This parameter can be a value of @ref GPIOSpeed_TypeDef */
 
   GPIOMode_TypeDef GPIO_Mode;    /*!< Specifies the operating mode for the selected pins.
                                       This parameter can be a value of @ref GPIOMode_TypeDef */
 }GPIO_InitTypeDef;

该结构体有3个成员:GPIO_Pin、GPIO_Speed、GPIO_Mode。成员GPIO_Speed、GPIO_Mode均为枚举类型,两者定义分别如下。

/** 
   * @brief  Output Maximum frequency selection  
   */
 typedef enum
 { 
   GPIO_Speed_10MHz = 1,
   GPIO_Speed_2MHz, 
   GPIO_Speed_50MHz
 }GPIOSpeed_TypeDef;

这是个枚举类型,表示GPIO_Speed_10MHz = 1,GPIO_Speed_2MHz = 2,GPIO_Speed_50MHz = 3

/** 
   * @brief  Configuration Mode enumeration  
   */
 typedef enum
 { GPIO_Mode_AIN = 0x0,
   GPIO_Mode_IN_FLOATING = 0x04,
   GPIO_Mode_IPD = 0x28,
   GPIO_Mode_IPU = 0x48,
   GPIO_Mode_Out_OD = 0x14,
   GPIO_Mode_Out_PP = 0x10,
   GPIO_Mode_AF_OD = 0x1C,
   GPIO_Mode_AF_PP = 0x18
 }GPIOMode_TypeDef;

同样是一个枚举类型,每个成员都表示一个特定的数值。定义好结构体GPIO_InitStructure后,开启GPIOB的时钟。接下来

 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

由上面分析可知

GPIO_InitStructure.GPIO_Mode = 0x10;
  GPIO_InitStructure.GPIO_Speed = 0x03;

接着往下执行代码

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;

GPIO_Pin_1的定义在<stm32f10x_gpio.h>头文件里面可以查到

#define GPIO_Pin_1                 ((uint16_t)0x0002)  /*!< Pin 1 selected */

即 GPIO_InitStructure.GPIO_Pin = 0x0002;

GPIO_Init()函数

GPIO_Init(GPIOB, &GPIO_InitStructure);

由上述代码可知,函数传递的参数为GPIOB与GPIO_InitStructure的地址,GPIOB实质上为一个结构体指针,在<stm32f10x.h>头文件里面的GPIO内存映射部分找到其定义

#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)

可以看出GPIOB为一个(GPIO_TypeDef *)类型的指针,我们再来看一下GPIO_TypeDef类型的定义

/** 
   * @brief General Purpose I/O
   */
 typedef struct
 {
   __IO uint32_t CRL;
   __IO uint32_t CRH;
   __IO uint32_t IDR;
   __IO uint32_t ODR;
   __IO uint32_t BSRR;
   __IO uint32_t BRR;
   __IO uint32_t LCKR;
 } GPIO_TypeDef;

可以看出GPIO_TypeDef为一个结构体,其成员名即为GPIO相关寄存器名的缩写,成员地址与寄存器地址一一对应,操作结构体的成员就是在操作相应的寄存器。
由此可知,GPIOB为一个结构体指针,其成员指向相应的寄存器。下面进入GPIO_Init()函数内部。

    uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
    uint32_t tmpreg = 0x00, pinmask = 0x00;

首先定义了6个变量,全部初始化为0。

/* Check the parameters */
   assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
   assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
   assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));

再是参数检测,这个是检测参数的输入是否符合要求,这里不做详细说明。

/*---------------------------- GPIO Mode Configuration -----------------------*/
   currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
   if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
   { 
     /* Check the parameters */
     assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
     /* Output mode */
     currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
   }

然后是GPIO模式配置
  第1行代码: 

currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);

即 currentmode = (0x10 & 0x0f) = 0x00;

第2行代码: 

if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)

即 if( (0x10 & 0x10) != 0x00),因为括号里面相与结果还是0x10,所以不等于0x00,即满足判断条件,继续往下执行。
  第4行代码同样是参数检测,略过。
  第5行代码:

currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;

即 currentmode |= 0x03,即 currentmode = 0x00 | 0x03 = 0x03;

接下来是CRL寄存器配置

/*---------------------------- GPIO CRL Configuration ------------------------*/
   /* Configure the eight low port pins */
   if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)    // 
相与结果为0x02,所以不为0,即条件符合
   {
     tmpreg = GPIOx->CRL;              // 这里的GPIOX=GPIOB,而GPIOB为一个结构体指针,可以看其定义就知道,而CRL为该结构体成员之一
     for (pinpos = 0x00; pinpos < 0x08; pinpos++)
     {
       pos = ((uint32_t)0x01) << pinpos;
       /* Get the port pins position */
       currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;         // 只有两数相等时相与的结果才不为0,且等于pos值
       if (currentpin == pos)       // 当上面结果不为0时,即符合判断条件
       {
         pos = pinpos << 2;
         /* Clear the corresponding low control register bits */
         pinmask = ((uint32_t)0x0F) << pos;
         tmpreg &= ~pinmask;
         /* Write the mode configuration in the corresponding bits */
         tmpreg |= (currentmode << pos);
         /* Reset the corresponding ODR bit */
         if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
         {
           GPIOx->BRR = (((uint32_t)0x01) << pinpos);
         }
         else
         {
           /* Set the corresponding ODR bit */
           if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
           {
             GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
           }
         }
       }
     }
     GPIOx->CRL = tmpreg;                                  // 哎,太叨了
   }

第3行代码:

if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)

 即 if( (0x0002 & 0x00ff) != 0x00),因为括号里运算结果为0x0002,所以不等于0x00,即满足判断条件
  第6行代码:

tmpreg = GPIOx->CRL;

 由前面所述可知,GPIOB为一个结构体指针,而这里GPIOX即为GPIOB,此时CRL=0,即tmpreg = 0x00;

第8行代码:

for (pinpos = 0x00; pinpos < 0x08; pinpos++)

 即 pinpos从0到7,整个for()循环执行8次

第10行代码:

pos = ((uint32_t)0x01) << pinpos;

第1次循环:pinpos = 0x00,即 pos = 0x01 << 0x00 = 0x01;
  第2次循环:pinpos = 0x01,即 pos = 0x01 << 0x01 = 0x02;
  第3次循环:pinpos = 0x02,即 pos = 0x01 << 0x02 = 0x04;
  ...

第一次循环时执行情况:
    第12行代码:

currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

即 currentpin = 0x0002 & 0x01 = 0x0000;

  第14行代码:

if (currentpin == pos)

  即 if (0x0000 == 0x01),不符合判断条件,后面的语句也就不需要再执行下去了。
  第二次循环时执行情况:
    第12行代码:

currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

  即 currentpin = 0x0002 & 0x02 = 0x0002;

  第14行代码:

if (currentpin == pos)

  即 if(0x0002 == 0x02),此时满足判断条件,由此可以看出,只有当两个对象相等时才能够执行 if()里面的语句,这里 GPIO_Pin = 0X0002,只有当pos = 0x02时才会执行后面的代码,其他情况全部跳过if()内语句。
  下面我们进入条件语句内。
  第16行代码:

pos = pinpos << 2;

  即 pos = 0x01 << 2 = 0x04;(只有当 pinpos = 0x01 时 pos = 0x02 ,即此时才能进入条件语句内)
  第18行代码:

pinmask = ((uint32_t)0x0F) << pos;

即 pinmask = 0x0f << 0x04 = 0xf0;

  第19行代码:

tmpreg &= ~pinmask;

即 tmpreg &= ~0xf0 = 0x00;

  第21行代码:

tmpreg |= (currentmode << pos);

 即 tmpreg |= 0x03 << 0x04 = 0x30; 

  第23行代码:

if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)

  很明显,这里不满足条件,跳过if内语句
  第30行代码:

if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)

  很明显,这里同样不满足条件,同样跳过。
  于是只剩最后一条代码 

GPIOx->CRL = tmpreg;

  即GPIOB->CRL = 0x30;

因为不满足下述代码

if (GPIO_InitStruct->GPIO_Pin > 0x00FF)

所以CRH寄存器就不用配置。

根据这个结果,由用户手册可知,如下图:

GPIOB被配置为通用推挽输出模式,最大速率为50MHz,引脚为1脚。现在我们回过头来看下配置代码是怎么写的。

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    
/* 配置LCD背光控制管脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;		
GPIO_Init(GPIOB, &GPIO_InitStructure);

这几句代码的字面意思是GPIO模式配置为通用推挽输出、速率配置为50M、引脚配置为1脚,然后按这个配置初始化GPIOB。很明显,这个字面意思与上面代码执行的结果一致,所以它成功的按要求配置了GPIO,而且非常的直观。真的是非常的聪明。

因作者水平有限,有错误处还请批评指正。


原创作品