GPIO简介
GPIO(全称为General Purpose Input/Output),即通用输入/输出,可以认为GPIO是片外外设与片内的通信接口,通过控制GPIO的电平状态,可以实现片外外设与片内的通信以及数据的输入输出。
对于st32F103系列的GPIO,其命名规则为GPIO+端口号,如GPIOA,GPIOB。对于每个端口又有着16个引脚(pin),编号为0~15。
以下便是一个GPIO引脚的结构图:
通过对不同寄存器的配置,可以切换GPIO的工作模式。
GPIO工作模式
从大致上分,GPIO有两种工作模式:
- 输入模式:GPIO可以作为输入,通过读取GPIO的电平状态来获取外设的输入信号。
- 输出模式:GPIO可以作为输出,通过控制GPIO的电平状态来驱动外设的输出信号。
而输入模式又可以分为:
- 上拉输入模式(GPIO_MODE_IPU):通过内部上拉电阻,引脚悬空时默认为高电平,当输入低电平时,GPIO的电平状态为低电平。
- 下拉输入模式(GPIO_MODE_IPD):通过内部下拉电阻,引脚悬空时默认为低电平,当输入高电平时,GPIO的电平状态为高电平。
- 浮空输入模式(GPIO_MODE_IN_FLOATING):内部无上拉电阻或下拉电阻,易受外部电平干扰,精度不高,可外接上拉或下拉电阻来实现输入电平的上拉或下拉。
- 模拟输入模式(GPIO_MODE_AIN):通过ADC模块,将模拟信号转换为数字信号,输入到GPIO。
输出模式又可以分为:
- 推挽输出模式(GPIO_MODE_OUT_PP):输出高电平或低电平(三极管导通压降),通过控制GPIO的电平状态来驱动外设的输出信号。输出的高低电平均有驱动能力。
- 开漏输出模式(GPIO_MODE_OUT_OD):该模式下,高电平无驱动能力(高阻态),低电平有驱动能力。
- 复用推挽输出模式(GPIO_MODE_AF_PP)和复用开漏输出模式(GPIO_MODE_AF_OD),用到再学。
使用标准库来实现GPIO读写
由上图GPIO的结构可知,GPIO算是stm32的片内外设,且挂载在APB2总线上。在
stm32结构中,可以知道想要使用stm32的片内外设,必须先使能外设对应的时钟,这样才能让外设运行。
这里使用标准库来实现GPIO的各种操作。
以下给出使用标准库使能GPIOA第0引脚的例子。
#include "stm32f10x.h"int main(void)
{// 使能GPIOA时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 设置GPIO的各种参数GPIO_InitTypeDef GPIO_InitStructure;// 设置GPIO为推挽输出模式GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;// 设置GPIO初始化第0引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;// 设置GPIO速度为50MHzGPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;// 调用GPIO初始化函数,并指定初始化的GPIO,传入参数结构GPIO_Init(GPIOA, &GPIO_InitStructure);while(1){}
}
在RCC_APB2PeriphClockCmd
函数中,传入需要使能的外设名称,具体参数的名称可以在函数定义中找到,第二个参数选择ENABLE或DISABLE,即使能或失能时钟。
而在GPIO_Init
函数中,需要指定初始化的GPIO代号,如GPIOA等,而第二个参数接受一个GPIO_InitTypeDef
结构体,该结构体中包含了GPIO的各种参数,如GPIO模式、引脚号、速度等,需要提前定义并配置好该结构,并以结构指针的形式传入。
值得注意的是GPIO_InitTypeDef
的结构体变量一般命名为GPIO_InitStructure
,这样可以使得代码更加易读。
初始化后,可以通过几个库函数来实现对应引脚的输入输出操作。
/*输入读取操作*/
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);/*输出设置操作*/
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
在这些函数中,GPIOx
参数代表GPIO的代号,如GPIOA等,GPIO_Pin
参数代表GPIO的引脚号,如GPIO_Pin_0等。
对于有后缀Bits / Bit
的函数,可以设置或读取单个引脚的电平状态,而对于没有后缀的函数,可以设置或读取整个端口的电平状态。
最后实现使用GPIO读取光敏传感器输入,并通过输入控制另一个GPIO的输出来控制Led亮灭。
代码如下
#include "stm32f10x.h" // Device header
#define true 1
#define false 0
typedef unsigned int uint;int main(void)
{ // 使能GPIOA, GPIOB时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);// 配置GPIO模式GPIO_InitTypeDef GPIO_InitStructureA;GPIO_InitTypeDef GPIO_InitStructureB;// 配置输入GPIOA_Pin_0, 连接到光敏传感器GPIO_InitStructureA.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入GPIO_InitStructureA.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructureA.GPIO_Speed= GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructureA);// 配置输出GPIOB_Pin_0,控制Led亮灭GPIO_InitStructureB.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructureB.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructureB.GPIO_Speed= GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructureB);// 变量区uint8_t light = 0;while(true){light = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);if (light == 1)GPIO_WriteBit(GPIOB, GPIO_Pin_0, Bit_RESET);elseGPIO_WriteBit(GPIOB, GPIO_Pin_0, Bit_SET);}
}
引脚连接: Led正极连接到3.3V,负极连接到GPIOB_Pin_0, 光敏传感器的VCC连接3.3V,GND连接GND,信号端DO连接GPIOA_Pin_0。当未被遮挡时,DO端一直将引脚下拉至0,当被遮挡时,DO端将保持高电平。