STM32 GPIO_INIT()库函数的程序含义

STM32 GPIO一共有七个寄存器:CRL、CRH、IDR、ODR、BSRR、BRR、LCKR

具体功能不再赘述,正点原子的库函数开发指南以及STM32参考手册中都有详细的讲解,网上也有很多大佬通俗易懂的解释。

补充:CRL、CRH寄存器的相关知识

CRL寄存器有32位,主要功能是配置pin0-pin7的工作模式,每一个pin由4位负责配置,以pin0为例,它由CRL的bit0~bit3负责,其中高两位bit3、bit2为CNF1、CNF0,低两位为MODE1、MODE0,这里不多逼逼他们之间的互相关系,直接放结论:

1. 先看MODE:
    MODE = 00 : 输入模式
    MODE > 00 : 输出模式:01--输出速度10M
                                            10--输出速度2M
                                            11--输出速度50M
 2. 再看CNF1: CNF0 
     输入模式下:00--模拟输入  01--浮空输入  
                         1x--上拉输入、下拉输入
     输出模式下:00--推挽通用输出
                           01--开漏通用输出
                           10--推挽复用输出
                           11--开漏复用输出

 CRH寄存器的工作原理同CRL一样,区别在于CRH用来负责配置pin8~pin15

这里浅析一下其中几个常用寄存器的库函数的程序含义,不当之处,还请大佬们批评指正(爱心)

void GPIO_Init(GPIO_TypeDef* GPIOx,GPIO_InitTypeDef* GPIO_InitStruct)

GPIO初始化函数,传递两个参数,一个是GPIO_TypeDef类型的指针,一个是 GPIO_InitTypeDef 类型的结构体指针,但STM32的库函数限制了它们具体的表达形式,其中GPIOx只能有如下的表达形式

#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \
                                    ((PERIPH) == GPIOB) || \
                                    ((PERIPH) == GPIOC) || \
                                    ((PERIPH) == GPIOD) || \
                                    ((PERIPH) == GPIOE) || \
                                    ((PERIPH) == GPIOF) || \
                                    ((PERIPH) == GPIOG))

GPIO_InitTypeDef是一个结构体类型,其成员变量分别表示管脚号、端口输出速度、端口工作模式

typedef struct
{
  uint16_t GPIO_Pin;           

  GPIOSpeed_TypeDef GPIO_Speed;  
                                   
  GPIOMode_TypeDef GPIO_Mode;    
                                      
}GPIO_InitTypeDef;

管脚号用无符号16位二进制数表示,其参数被限制:

#define GPIO_Pin_0                 ((uint16_t)0x0001)  /*!< Pin 0 selected */
#define GPIO_Pin_1                 ((uint16_t)0x0002)  /*!< Pin 1 selected */
#define GPIO_Pin_2                 ((uint16_t)0x0004)  /*!< Pin 2 selected */
#define GPIO_Pin_3                 ((uint16_t)0x0008)  /*!< Pin 3 selected */
#define GPIO_Pin_4                 ((uint16_t)0x0010)  /*!< Pin 4 selected */
#define GPIO_Pin_5                 ((uint16_t)0x0020)  /*!< Pin 5 selected */
#define GPIO_Pin_6                 ((uint16_t)0x0040)  /*!< Pin 6 selected */
#define GPIO_Pin_7                 ((uint16_t)0x0080)  /*!< Pin 7 selected */
#define GPIO_Pin_8                 ((uint16_t)0x0100)  /*!< Pin 8 selected */
#define GPIO_Pin_9                 ((uint16_t)0x0200)  /*!< Pin 9 selected */
#define GPIO_Pin_10                ((uint16_t)0x0400)  /*!< Pin 10 selected */
#define GPIO_Pin_11                ((uint16_t)0x0800)  /*!< Pin 11 selected */
#define GPIO_Pin_12                ((uint16_t)0x1000)  /*!< Pin 12 selected */
#define GPIO_Pin_13                ((uint16_t)0x2000)  /*!< Pin 13 selected */
#define GPIO_Pin_14                ((uint16_t)0x4000)  /*!< Pin 14 selected */
#define GPIO_Pin_15                ((uint16_t)0x8000)  /*!< Pin 15 selected */
#define GPIO_Pin_All               ((uint16_t)0xFFFF)  /*!< All pins selected */

GPIO_Speed是GPIOSpeed_TypeDef(枚举)数据类型

typedef enum
{ 
  GPIO_Speed_10MHz = 1,
  GPIO_Speed_2MHz, 
  GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

GPIO_Mode是GPIOMode_TypeDef(枚举)类型,注意其16进制表示,后面的程序中有妙用!

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;

在void GPIO_Init()中,GPIO_InitTypeDef的成员变量表达形式也被限制

#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_AIN) || ((MODE) == GPIO_Mode_IN_FLOATING) || \
                            ((MODE) == GPIO_Mode_IPD) || ((MODE) == GPIO_Mode_IPU) || \
                            ((MODE) == GPIO_Mode_Out_OD) || ((MODE) == GPIO_Mode_Out_PP) || \
                            ((MODE) == GPIO_Mode_AF_OD) || ((MODE) == GPIO_Mode_AF_PP))

AIN:模拟输入,用于ADC功能;IPD(Input pull-down):下拉输入

OUT_OD:开漏输出(I2C等需要“线与”时需要用或者需要5V输出时要用);

AF_OD:复用开漏输出;IN_FLOATING:浮空输入(系统初始化默认时的状态);

IPU:(Input Pull-up):上拉输入; OUT_PP(Push-Pull):推挽输出(最常用);

AF_PP(复用推挽输出);

复用功能时,端口的工作状态需要查表~

下面介绍GPIO Mode Configuration 代码段含义

/*---------------------------- 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;   
  }

((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F):两个相与的对象都转换位32位二进制数,相与的结果只保留后四位结果,其他位均为0,结果赋值给currentmode,结合上文所述GPIOMode_TypeDef(枚举)类型不难推出,currentmode的第四位结果只有0000、0100、1000、1100四种情况。这里只是配置了CNF1、CNF0这两个高两位!

if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00) :此判断为真的条件为端口的Mode配置为输出模式:

OUT_OD、OUT_PP、AF_OD、AF_PP四种情况。说白了,这个枚举中真正有用的数据在后四位上,不难发现AIN对应的后四位是0000,浮空输入0100、上下拉输入1000、开漏输出0100、推挽输出0000、开漏复用1100、推挽复用1000(后四位中的标红的前两位对应CNF1、CNF0。后两位对应Speed,只是现在不确定是否要配置为输出模式,所以默认均为输入模式00

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;

如果判断为真,再进行参数有效性判断。

assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));  

为什么这样做呢?这是因为STM32的GPIO只有在输出模式下才会配置端口的速度,输入模式下无需配置速度~

currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;   

结合GPIO_Speed枚举

typedef enum
{ 
  GPIO_Speed_10MHz = 1,
  GPIO_Speed_2MHz, 
  GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

10M对应0001、2M对应0010、50M对应0011,有效位只有后两位!这样currentmode(目前配置好了高两位)与Speed相或,得到的结果就是CNF、MODE(高两位、低两位)均配置完毕了~

搞定了负责某个pin的四位寄存器配置,接下来要干的活就是把它们放到属于它们在CRL或者CRH中的位置中去。

/*---------------------------- GPIO CRL Configuration ------------------------*/
  /* Configure the eight low port pins */
  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)  //确定是pin0-pin7,则对CRL进行操作
  {
    tmpreg = GPIOx->CRL;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = ((uint32_t)0x01) << pinpos;
      /* Get the port pins position */
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos; //如果非零,则对应找到了postion,这句函数的结果要么是0,要么是pos!
      if (currentpin == pos) //对应上了pos
      {
        pos = pinpos << 2;   //膨胀关系,8位膨胀到32位对应的映射关系,pos其实是指CRL对应的位置。由于CRL是一个表示pin0-pin7一共八个端口的32位寄存器,一个端口占用4bit,因此pos在pinpos的位置上乘以4以确定要修改的四个bit位置
        /* Clear the corresponding low control register bits */
        pinmask = ((uint32_t)0x0F) << pos;  //第四位1111位移pos个位置,即找到对应的CRL中的位置
        tmpreg &= ~pinmask;
        /* Write the mode configuration in the corresponding bits */
        tmpreg |= (currentmode << pos);   //将设置好的currentmode对CRL中相应位置的位进行赋值
        /* 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)    //上拉输入模式下 , 将对应端口置1
          {
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
    GPIOx->CRL = tmpreg;
  }

if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)  这段代码的意思是判断pin的管脚号是pin0~pin7还是pin8~pin15,如果是低八位,则进行下面的CRL寄存器配置:

tmpreg = GPIOx->CRL: 建立一个临时寄存器tmpreg,读取目前CRL的信息并保存。

for (pinpos = 0x00; pinpos < 0x08; pinpos++)  从0开始到7进行遍历

pos = ((uint32_t)0x01) << pinpos;currentpin = (GPIO_InitStruct->GPIO_Pin) & pos:

每次将0000 0001向左位移pinpos位,每次位移后,都将其结果和GPIO_Pin进行相与,如果是0,则没有对应上,结果非零,说明对应上了。(位移遍历的操作是为了记录对应管脚在GPIO组内的位置,因为GPIO_Pin的宏定义和单纯的二进制数没有数量上的关系

接下来骚操作来了!!!!

 pos = pinpos << 2: pos不再表示pin引脚位置在二进制上的表示(例如0001 0000表示pin4),而是引脚号的四倍这个数据!目的是找到引脚对应在CRL中的位置!

结合CRL寄存器的说明,可以归纳出pinpos和CRL中pin之间的膨胀关系如图所示:

至此, 乘以四倍的原因找到了!!

pinmask = ((uint32_t)0x0F) << pos :将低四位1111 位移至pinpos对应的CRL中pin的位置,

tmpreg &= ~pinmask: pinmask取反以后与tmpreg相与,结果就是tmpreg将其他pinpos的CRL信息保留,把将要修改的pinpos在CRL中的四个位清除。

 tmpreg |= (currentmode << pos):把刚刚设置好的mode信息位移至对应的CRL中对应的四位上,至此,CRL就配置完成了!!!!

当然,如果引脚是输入模式,需要手动配置与之对应的缺省电平

if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)   //下拉输入模式下,将对应端口清零
        {
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);
        }
        else
        {
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)   //上拉输入模式下 , 将对应端口置1
          {
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }

简单记忆:BRR,用于端口清零,BSRR,用于端口置1

同理,CRH的配置与CRL非常类似,区别在于要找到pin8~pin15与CRL中bit0~bit32之间的膨胀关系,在代码中很容易实现,这里是在pinpos依然从0~7进行遍历,但pos偏移量不再是pinpos,而是pinpos+8,这样做的核心思想是虽然要找pin8~pin15与CRH的膨胀关系,但原理上依然是8位到32位的膨胀关系,与CRL其实是换汤不换药的。后续的赋值操作也无需修改代码,与CRL是一致的~

附:GPIO_Init()函数全部代码及注释

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;    //第八位前两位是CNF1,CNF0,后两位是MODE,或运算得到一组GPIO_PINx的设置
  }
/*---------------------------- GPIO CRL Configuration ------------------------*/
  /* Configure the eight low port pins */
  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)  //确定是pin0-pin7,则对CRL进行操作
  {
    tmpreg = GPIOx->CRL;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = ((uint32_t)0x01) << pinpos;
      /* Get the port pins position */
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos; //如果非零,则对应找到了postion,这句函数的结果要么是0,要么是pos!
      if (currentpin == pos) //对应上了pos
      {
        pos = pinpos << 2;   //膨胀关系,8位膨胀到32位对应的映射关系,pos其实是指CRL对应的位置。由于CRL是一个表示pin0-pin7一共八个端口的32位寄存器,一个端口占用4bit,因此pos在pinpos的位置上乘以4以确定要修改的四个bit位置
        /* Clear the corresponding low control register bits */
        pinmask = ((uint32_t)0x0F) << pos;  //第四位1111位移pos个位置,即找到对应的CRL中的位置
        tmpreg &= ~pinmask;
        /* Write the mode configuration in the corresponding bits */
        tmpreg |= (currentmode << pos);   //将设置好的currentmode对CRL中相应位置的位进行赋值
        /* 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)    //上拉输入模式下 , 将对应端口置1
          {
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
    GPIOx->CRL = tmpreg;
  }
/*---------------------------- GPIO CRH Configuration ------------------------*/
  /* Configure the eight high port pins */
  if (GPIO_InitStruct->GPIO_Pin > 0x00FF)    //对应pin8-pin15
  {
    tmpreg = GPIOx->CRH;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = (((uint32_t)0x01) << (pinpos + 0x08)); //pos从8遍历到15
      /* Get the port pins position */
      currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);    //原理同CRL
      if (currentpin == pos)
      {
        pos = pinpos << 2;   //pinpos从0开始遍历到8而不是从8遍历到15的原因在此!!膨胀关系一定要从0开始!
        /* Clear the corresponding high control register bits */
        pinmask = ((uint32_t)0x0F) << pos;  //原理同CRL
        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));    //BRR置零,高16位保持,低16位对应pin0-pin15
        }
        /* Set the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
        {
          GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));    //BSRR置1,高16位保持,低16位对应pin0-pin15
        }
      }
    }
    GPIOx->CRH = tmpreg;
  }
}

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值