2. GPIO读写

GPIO简介

GPIO(全称为General Purpose Input/Output),即通用输入/输出,可以认为GPIO是片外外设与片内的通信接口,通过控制GPIO的电平状态,可以实现片外外设与片内的通信以及数据的输入输出。

对于st32F103系列的GPIO,其命名规则为GPIO+端口号,如GPIOA,GPIOB。对于每个端口又有着16个引脚(pin),编号为0~15。

以下便是一个GPIO引脚的结构图:
image

通过对不同寄存器的配置,可以切换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读写

image

由上图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速度为50MHz
    GPIO_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);
        else
            GPIO_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端将保持高电平。

posted @ 2024-07-29 22:40  凪风sama  阅读(33)  评论(0编辑  收藏  举报