一、GPIO介绍
GPIO(General -purpose input/output):通用型输入输出,功能类似与8051的P0-P3,接脚可以供使用者由程序自由控制。可以通过一些暂存器来确定引脚电位的高低,对于out可以让这个引脚输出高电位或低电位。
GPIO可以作为一组的输入输出,通过拉高/拉低可以将每个引脚可以设置为不同的逻辑电平,可以用作模拟信号IO、计数器/定时器、串口,用户可以通过GPIO口和硬件进行数据交互(如UART)、控制硬件工作(如LED、蜂鸣器等)、读取硬件的工作状态信号(如中断信号)等。此外,在一些IC(Integrated circuit )中,GPIO可能是复用的,所以需要配置引脚的行为。
使用GPIO需要完成:
- GPIO引脚时钟使能
- GPIO引脚需要被配置为输入或者输出
- 要给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初始化函数有重要的四个部分:
- GPIO_InitTypeDef 结构体
- RCC_APB2PeriphClockCmd函数
- GPIO_Init初始化函数,传入参数为GPIOgroup和GPIO_InitTypeDef 类型的结构体
- 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:
#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所定义的类型为什么这样定义?根据手册中的上图。
此时我们想使能GPIOB和GPIOE的时钟,在上图的RCC_APB2ENR寄存器中有使能配置,如下图,赋值相应的位就可以使能外设的时钟。
#define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008) /* 0000 1000*/
#define RCC_APB2Periph_GPIOE ((uint32_t)0x00000040) /* 0100 0000*/
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:配置输入还是输出,此外,配置输出时,还配置了输出速度。
输出:(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))
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是低电平点亮还是高电平点亮 */
}