GPIO

一、GPIO介绍

GPIO(General -purpose input/output):通用型输入输出,功能类似与8051的P0-P3,接脚可以供使用者由程序自由控制。可以通过一些暂存器来确定引脚电位的高低,对于out可以让这个引脚输出高电位或低电位。
GPIO可以作为一组的输入输出,通过拉高/拉低可以将每个引脚可以设置为不同的逻辑电平,可以用作模拟信号IO、计数器/定时器、串口,用户可以通过GPIO口和硬件进行数据交互(如UART)、控制硬件工作(如LED、蜂鸣器等)、读取硬件的工作状态信号(如中断信号)等。此外,在一些IC(Integrated circuit )中,GPIO可能是复用的,所以需要配置引脚的行为。

图片来自百度图片

使用GPIO需要完成:

  1. GPIO引脚时钟使能
  2. GPIO引脚需要被配置为输入或者输出
  3. 要给GPIO引脚相应的逻辑值

1.1 GPIO功能描述

输入模式:输入浮空、输入上拉、输入下拉、模拟输入
输出模式:开漏输出、推挽式输出、推挽式复用功能、开漏复用功能
推挽输出 : 负责灌电流和拉电流,使其负载能力和开关速度有很大的提高。
开漏输出: 具有线与能力,若多个开漏模式引脚连接到一起,只有所有为高时才有上拉电阻提供高电平。
上拉/下拉输入:
上拉就是把电位拉高,如通过一个电阻连接到Vcc,默认状态下引脚数据位1。
下拉就是把电压拉低,拉到GND,默认为0。
浮空输入:既不拉高也不拉低。端口呈高阻态,引脚电压是不确定的。如果想要减少上下拉电阻对结果的影响,可以选择此模式。
模拟输入:既不拉高也不拉低,输入数字信号即01的二进制数字信号,通过数模转换,转换成模拟信号。如使用ADC外设

1.2 寄存器

  • 两个32位配置寄存器(GPIOx_CRL, GPIOx_CRH)
  • 两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),
  • 一个32位置位/复位寄存器(GPIOx_BSRR),
  • 一个16位复位寄存器(GPIOx_BRR)
  • 一个32位锁定寄存器(GPIOx_LCKR)

/* 端口输入数据寄存器GPIOx_IDR 地址偏移:0x08
31:16 保留
这些位为只读并只能以字(16位)的形式读出。读出的值为对应I/O口的状态 */

/* 端口输出数据寄存器GPIOx_ODR 地址偏移: 0x0C
31:16 保留
这些位为读写,只能以字(16位)的形式读出。读出的值为对应I/O口的状态
对GPIOx_BSRR(x = A…E),可以分别地对各个ODR位进行独立的设置/清除*/

/* 端口位设置/清除寄存器(GPIOx_BSRR) 地址偏移: 0x10
位31:16:BRy: 清除端口x的位y (y = 0…15)
0:对对应的ODRy位不产生影响
1:清除对应的ODRy位为0
位15:0 BSy: 设置端口x的位y (y = 0…15)
0:对对应的ODRy位不产生影响
1:设置对应的ODRy位为1 */

/* 端口位清除寄存器(GPIOx_BRR) 地址偏移: 0x14
位15:0 BRy: 清除端口x的位y (y = 0…15) (Port x Reset bit y)
这些位只能写入并只能以字(16位)的形式操作。
0:对对应的ODRy位不产生影响
1:清除对应的ODRy位为0 */

/* 端口配置锁定寄存器 地址偏移: 0x18
当执行正确的写序列设置了位16(LCKK)时,该寄存器用来锁定端口位的配置。位[15:0]用于锁
定GPIO端口的配置。在规定的写入操作期间,不能改变LCKP[15:0]。当对相应的端口位执行了
LOCK序列后,在下次系统复位之前将不能再更改端口位的配置。

位16 LCKK:锁键 (Lock key)
该位可随时读出,它只可通过锁键写入序列修改。
0:端口配置锁键位激活
1:端口配置锁键位被激活,下次系统复位前GPIOx_LCKR寄存器被锁住。
锁键的写入序列:
写1 -> 写0 -> 写1 -> 读0 -> 读1
最后一个读可省略,但可以用来确认锁键已被激活。
注:在操作锁键的写入序列时,不能改变LCK[15:0]的值。
操作锁键写入序列中的任何错误将不能激活锁键 */

二、GPIO控制LED灯亮灭

参考STM32使用手册

2.1 LED初始化函数

 void LED_Init(void)
{
 
 GPIO_InitTypeDef  GPIO_InitStructure;
    
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);  //使能PB,PE端口时钟
    
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;               //LED0-->PB.5 端口配置
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;        //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       //IO口速度为50MHz
 GPIO_Init(GPIOB, &GPIO_InitStructure);                  //根据设定参数初始化GPIOB.5
 GPIO_SetBits(GPIOB,GPIO_Pin_5);                         //PB.5 输出高

 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;               //LED1-->PE.5 端口配置, 推挽输出
 GPIO_Init(GPIOE, &GPIO_InitStructure);                  //推挽输出 ,IO口速度为50MHz
 GPIO_SetBits(GPIOE,GPIO_Pin_5);                         //PE.5 输出高 
}

从这里看出,LED初始化函数有重要的四个部分:

  1. GPIO_InitTypeDef 结构体
  2. RCC_APB2PeriphClockCmd函数
  3. GPIO_Init初始化函数,传入参数为GPIOgroup和GPIO_InitTypeDef 类型的结构体
  4. GPIO_SetBits()函数

2.2 GPIO_InitTypeDef 结构体

typedef struct
{
  uint16_t GPIO_Pin;           
  GPIOSpeed_TypeDef GPIO_Speed; 
  GPIOMode_TypeDef GPIO_Mode;   
}GPIO_InitTypeDef;

#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 */

typedef enum
{ 
  GPIO_Speed_10MHz = 1,
  GPIO_Speed_2MHz, 
  GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
                              
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;

2.3 RCC_APB2PeriphClockCmd结构体

GPIO 口时钟配置函数

void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));
  assert_param(IS_FUNCTIONAL_STATE(NewState));
  if (NewState != DISABLE)
  {
      /* #define RCC    ((RCC_TypeDef *) (( ((uint32_t)0x40000000) + 0x20000) + 0x1000))
         #define RCC_APB2Periph_GPIOB             ((uint32_t)0x00000008)      */
    RCC->APB2ENR |= RCC_APB2Periph;
  }
  else
  {
    RCC->APB2ENR &= ~RCC_APB2Periph;
  }
}

时钟使能就一句,现在来分析RCC(Reset Clock Controller, 复位与时钟控制)、AP2ENR:


AHB地址
#define RCC                 ((RCC_TypeDef *) RCC_BASE)

#define RCC_BASE              (AHBPERIPH_BASE + 0x1000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */

/* #define RCC  ((RCC_TypeDef *) (( ((uint32_t)0x40000000) + 0x20000) + 0x1000)) 8/
typedef struct
{
  __IO uint32_t CR;
  __IO uint32_t CFGR;
  __IO uint32_t CIR;
  __IO uint32_t APB2RSTR;
  __IO uint32_t APB1RSTR;
  __IO uint32_t AHBENR;
  __IO uint32_t APB2ENR;
  __IO uint32_t APB1ENR;
  __IO uint32_t BDCR;
  __IO uint32_t CSR;

} RCC_TypeDef;
RCC寄存器

RCC所定义的类型为什么这样定义?根据手册中的上图。

此时我们想使能GPIOB和GPIOE的时钟,在上图的RCC_APB2ENR寄存器中有使能配置,如下图,赋值相应的位就可以使能外设的时钟。

#define RCC_APB2Periph_GPIOB             ((uint32_t)0x00000008) /* 0000 1000*/
#define RCC_APB2Periph_GPIOE             ((uint32_t)0x00000040) /* 0100 0000*/
RCC_APB2ENR,时钟开启

2.4 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;
  
/*---------------------------- 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 */
  /* 判断是否是低8引脚 */
  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
  {
    tmpreg = GPIOx->CRL;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
        // 复位值应该是0x4444 4444,0100浮空输入
      pos = ((uint32_t)0x01) << pinpos;
      /* Get the port pins position */
      /* ???GPIO_InitStruct->GPIO_Pin,这里是确定是哪个引脚,还是引脚的状态值 */
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
      
      if (currentpin == pos)
      {
        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)
        {
            /* BRR:端口位清除寄存器 1:清楚对应的ODR y位为0 */
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);
        }
        else
        {
          /* Set the corresponding ODR bit */
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
          {
              /* 端口位设置/清除寄存器
              0:对对应的ODRy位不产生影 1:清除对应的ODRy位为0(31:16)
              1:设置对应的ODRy为1,0:不产生影响 (15:0)*/
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
    GPIOx->CRL = tmpreg;
  }
/*---------------------------- GPIO CRH Configuration ------------------------*/
/* 与CRL类似的 */
}

2.3.1 GPIO Mode Configuration

在初始化的代码中,
端口配置寄存器有两个(一共64位): GPIOx_CRL:端口配置低寄存器; GPIOx_CRH:端口配置高寄存器
一共可配置16个IO口,每个IO口由4位来控制(CNF1 CNF0 MODE1 MODE0)。
CNF1 CNF0:配置输入输出的具体模式;
MODE1 MODE0:配置输入还是输出,此外,配置输出时,还配置了输出速度。

CRL寄存器

输出:(mode > 00)
00:推挽输出 : 可以输出高,低电平,连接数字器件;
01:开漏输出: 输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行,适合于做电流型的驱动,其吸收电流的能力相对强。
特点:1. 利用外部电路的驱动能力,减少IC内部的驱动。 2. 开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。
10: 复用推挽输出
11: 复用开漏输出

输入:(mode = 00)
00:模拟输入:模拟输入是指传统方式的输入,数字输入是输入数字信号,即0,1的二进制数字信号,通过数模转换,转换成模拟信号,
01:浮空输入: 浮空就是逻辑器件与引脚即不接高电平,也不接低电平。由于逻辑器件的内部结构,当它输入引脚悬空时,相当于该引脚接了高电平。
10:上拉/下拉输入
上拉就是把点位拉高,比如拉到Vcc。上拉就是将不确定的信号通过一个电阻嵌位在高电平。
下拉就是把电压拉低,拉到GND。与上拉原理相似
11:保留

速度(max):00:保留 01:10MHz;10:2MHz; 11:50MHz

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;

端口配置不是4位吗?为什么定义时用到了8位呢?其实,在上图中可以看出输入模式和输出模式的CNF是一样的配置,不同之处在于Mode不同从而选择不同的方式。所以,配置了第4位,根据第4位的值判断输入还是输出。

2.3.2 GPIO配置

#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)

#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define GPIOB   ((GPIO_TypeDef *) ( (((uint32_t)0x40000000) + 0x10000) + 0x0C00))
APB2地址

2.4 GPIO引脚赋值

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  GPIOx->BSRR = GPIO_Pin;//1:清除对应的ODRy位为1 */
/*  GPIOE->BRR=GPIO_Pin;  //1:清除对应的ODRy位为0 */
/*  PS:点LED灯的时候,不仅考虑IO口的电平,还有考虑LED是低电平点亮还是高电平点亮 */
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容