一、GPIO 功能模式分析

1.推挽输出

推挽电路是两个参数相同的三极管或 MOSFET,以推挽方式存在于电路中。 电路工作时,两只对称的开关管每次只有一个导通,导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级提高电路的负载能力。 相对于开漏输出模式,推挽输出最大优势是输出高电平时,上升时间快,电压驱动能力强。

2.开漏输出

开漏端相当于 MOS 管的漏极(三极管的集电极),要得到高电平状态必须外接上拉电阻才行,因此输出高电平的驱动能力完全由外接上拉电阻决定,但是其输出低电平的驱动能力很强。开漏形式的电路有以下几个特点:

  1. 输出高电平时利用外部电路的驱动能力,减少 IC 内部的驱动。

  2. 开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平。 如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。上拉电阻的阻值决定了逻辑电平转换的速度。阻值越大,速度越低, 功耗越小。

  1. 开漏输出提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。

  1. 可以将多个开漏输出连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系,即“线与”。可以简单的理解为:在所有引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑 0,相当于接地,与之并联的回路“相当于被一根导线短路”,所以外电路逻辑电平便为 0,只有都为高电平时,与的结果才为逻辑 1。

3.复用推挽和开漏

复用指的是 GPIO 切换到 CPU 内部设备(比如 SPI,I2C,UART 等电路),也就是 GPIO 不是作为普通IO 使用,是由内部设备直接驱动。 推挽和开漏的特征同上。

4.四种输入模式

通过上面的引脚结构图可以得到如下三种方式

◆ 浮空输入: CPU 内部的上拉电阻、下拉电阻均断开的输入模式。

◆ 下拉输入: CPU 内部的下拉电阻使能、 上拉电阻断开的输入模式。

◆ 上拉输入: CPU 内部的上拉电阻使能、 下拉电阻断开的输入模式。

而模拟输入模式是 GPIO 引脚连接内部 ADC。

二、GPIO 的拉电流负载和灌电流负载能力

1.拉电流负载:

一种负载电流从驱动门流向外电路, 称为拉电流负载。 比如使用 STM32H7 的 GPIO 直接驱动 LED 就是拉电流形式。

2.灌电流负载:

负载电流从外电路流入驱动门, 称为灌电流负载。 比如下面这种形式的 LED 驱动电路

有了上面这些知识后再来看 STM32H7 的 IO 驱动能力(截图来自 STM32H7 数据手册) :

通过上面的截图可知: STM32H7 总的拉电流和灌电流不可超过 140mA,单个引脚最大不可超过 20mA

3.不使用的引脚推荐设置为模拟模式

主要从功耗和防干扰考虑。

◆ 所有用作带上拉电阻输入的 I/O 都会在引脚外部保持为低时产生电流消耗。此电流消耗的值可通过使

用的静态特性中给出的上拉 / 下拉电阻值简单算出。

◆ 对于输出引脚,还必须考虑任何外部下拉电阻或外部负载以估计电流消耗。

◆ 若外部施加了中间电平,则额外的 I/O 电流消耗是因为配置为输入的 I/O。此电流消耗是由用于区分输入值的输入施密特触发器电路导致。除非应用需要此特定配置,否则可通过将这些 I/O 配置为模拟模式以避免此供电电流消耗。 ADC 输入引脚应配置为模拟输入就是这种情况。

◆ 任何浮空的输入引脚都可能由于外部电磁噪声,成为中间电平或意外切换。为防止浮空引脚相关的电流消耗,它们必须配置为模拟模式,或内部强制为确定的数字值。这可通过使用上拉 / 下拉电阻或将引脚配置为输出模式做到。

综上考虑,不使用的引脚设置为模拟模式, 悬空即可

三、GPIO 的 HAL 库 API

1.HAL_GPIO_Init

函数原型:

/**
  * @brief  初始化指定的 GPIO 引脚
  * @param  GPIOx: 指向 GPIO 外设结构体的指针,如 GPIOA, GPIOB 等
  * @param  GPIO_Init: 指向 GPIO_InitTypeDef 结构体的指针,包含引脚配置信息
  * @retval 无
  */
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
{
    /* 局部变量定义:position用于循环遍历引脚,iocurrent/ioposition用于匹配目标引脚 */
    uint32_t position = 0U;
    uint32_t iocurrent = 0U;
    uint32_t ioposition = 0U;
    
    /* 省略的其他初始化代码 */
    
    /* 配置 GPIO 引脚,采用 16 个引脚的循环检测模式 */
    for(position = 0; position < GPIO_NUMBER; position++)
    {
        /* 省略的引脚位置计算/匹配代码 */
        
        /* 判断当前循环的引脚是否为需要配置的目标引脚 */
        if(iocurrent == ioposition)
        {
            /* --------------------- GPIO 模式配置 ------------------------ */
            /* 省略的 GPIO 模式(输入/输出/复用/模拟)配置代码 */
            
            /* --------------------- EXTI 模式配置 ------------------------ */
            /* 省略的外部中断(EXTI)模式配置代码 */
        }
    }
} 

函数描述:

此函数用于初始化 GPIO,此函数主要实现如下功能:

GPIO 功能配置。 设置 EXTI 功能。

函数参数:

第 1 个参数用于填写使用的端口号,可以是:

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)  
#define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)  
#define GPIOG               ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH               ((GPIO_TypeDef *) GPIOH_BASE)
#define GPIOI               ((GPIO_TypeDef *) GPIOI_BASE)
#define GPIOJ               ((GPIO_TypeDef *) GPIOJ_BASE)
#define GPIOK               ((GPIO_TypeDef *) GPIOK_BASE)

第 2 个形参是 GPIO_InitTypeDef 类型的结构体变量, 这个变量比较重要,要熟练掌握,定义如下:

/**
  * @brief  GPIO 初始化结构体定义
  * @note   该结构体用于配置 GPIO 引脚的模式、速度、上拉下拉、输出类型等参数
  */
typedef struct
{
  uint32_t Pin;       /* 要配置的 GPIO 引脚号,可单个或多个引脚(通过按位或组合) */
  uint32_t Mode;      /* GPIO 工作模式(输入/输出/复用/模拟/中断等) */
  uint32_t Pull;      /* 上拉/下拉电阻配置(无/上拉/下拉) */
  uint32_t Speed;     /* GPIO 输出速度(低速/中速/高速/超高速) */
  uint32_t Alternate; /* 复用功能映射(仅复用模式下有效) */
} GPIO_InitTypeDef;

下面将结构体每个成员做个说明:

成员 Pin 用于配置选择的引脚,范围 GPIO_PIN_0 到 GPIO_PIN_15,额外还可以选择

GPIO_PIN_All 和 GPIO_PIN_MASK。

成员 Mode 可以选择:

GPIO_MODE_INPUT /* 输入模式 */
GPIO_MODE_OUTPUT_PP /* 推挽输出 */
GPIO_MODE_OUTPUT_OD /* 开漏输出 */
GPIO_MODE_AF_PP /* 复用推挽 */
GPIO_MODE_AF_OD /* 复用开漏 */
GPIO_MODE_ANALOG /* 模拟模式 */
GPIO_MODE_IT_RISING /* 外部中断,上升沿触发检测 */
GPIO_MODE_IT_FALLING /* 外部中断,下降沿触发检测 */
GPIO_MODE_IT_RISING_FALLING /* 外部中断,双沿触发检测 */
GPIO_MODE_EVT_RISING /* 外部事件模式,上升沿触发检测 */
GPIO_MODE_EVT_FALLING /* 外部事件模式,下降沿触发检测 */
GPIO_MODE_EVT_RISING_FALLING /* 外部事件模式,双沿触发检测 */

成员 Pull 用于配置上拉下拉电阻:

GPIO_NOPULL /* 无上拉和下拉电阻 */  
GPIO_PULLUP /* 带上拉电阻 */  
GPIO_PULLDOWN /* 带下拉电阻 */   

成员 Speed 用于配置 GPIO 速度等级,有下面四种可选:

GPIO_SPEED_FREQ_LOW /* 低速 */
GPIO_SPEED_FREQ_MEDIUM /* 中等速度 */
GPIO_SPEED_FREQ_HIGH /* 快速 */
GPIO_SPEED_FREQ_VERY_HIGH /* 高速 */  

成员 Alternate 用于配置引脚复用,可选择的复用方式在文件 stm32h7xx_hal_gpio_ex.h 里面进行了定义,比如串口复用:

GPIO_AF7_USART1 
GPIO_AF7_USART2
GPIO_AF7_USART3 
GPIO_AF7_USART6 
GPIO_AF7_UART7  

2.HAL_GPIO_DeInit

函数原型:

/**
  * @brief  将指定的 GPIO 引脚恢复到默认(复位)状态
  * @param  GPIOx: 指向 GPIO 外设结构体的指针 (GPIOA/GPIOB/...)
  * @param  GPIO_Pin: 要复位的引脚(如 GPIO_PIN_0、GPIO_PIN_ALL 等)
  * @retval 无
  */
void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin)
{
    uint32_t position = 0U;    // 循环变量:遍历0~15号引脚
    uint32_t iocurrent = 0U;   // 当前检测的引脚位
    uint32_t ioposition = 0U;  // 目标复位的引脚位(由GPIO_Pin确定)
    
    // 遍历所有16个GPIO引脚(GPIO_NUMBER=16)
    for(position = 0; position < GPIO_NUMBER; position++)
    {
        /* 省略:计算iocurrent/ioposition,匹配需要复位的引脚 */
        
        // 找到目标引脚,执行复位配置
        if(iocurrent == ioposition)
        {
            /*------------------------- GPIO 模式复位配置 --------------------*/
            // 1. 配置为模拟模式(复位默认状态)
            // MODER寄存器:每个引脚占2位,00=输入 01=输出 10=复用 11=模拟
            GPIOx->MODER |= (GPIO_MODER_MODER0 << (position * 2));
            
            // 2. 复用功能复位为AF0(通用IO,无复用)
            // AFR[0]对应引脚0~7,AFR[1]对应引脚8~15;每个引脚占4位,AF0=0000
            GPIOx->AFR[position >> 3] &= ~((uint32_t)0xF << ((uint32_t)(position & (uint32_t)0x07) * 4)) ;
            
            // 3. 输出速度复位为最低速
            // OSPEEDR寄存器:每个引脚占2位,00=低速 01=中速 10=快速 11=高速
            GPIOx->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0 << (position * 2));
            
            // 4. 输出类型复位为推挽(模拟模式下无影响,仅复位寄存器)
            // OTYPER寄存器:每个引脚占1位,0=推挽 1=开漏
            GPIOx->OTYPER &= ~(GPIO_OTYPER_OT_0 << position) ;
            
            // 5. 上下拉电阻复位为“无”
            // PUPDR寄存器:每个引脚占2位,00=无 01=上拉 10=下拉 11=保留
            GPIOx->PUPDR &= ~(GPIO_PUPDR_PUPDR0 << (position * 2));
            
            /*------------------------- EXTI 模式复位配置 --------------------*/
            // 省略:复位外部中断/事件相关配置(如EXTI寄存器清零)
        }
    }
}

函数描述:

此函数用于复位 IO 到初始化状态, 具体状态看函数原型中的注释即可。

函数参数:

第 1 个参数用于填写使用的端口号,可以是:

#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)  
#define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)  
#define GPIOI ((GPIO_TypeDef *) GPIOI_BASE)  
#define GPIOJ ((GPIO_TypeDef *) GPIOJ_BASE)  
#define GPIOK ((GPIO_TypeDef *) GPIOK_BASE)  

第 2 个参数是配置选择的引脚,范围 GPIO_PIN_0 到 GPIO_PIN_15。

使用举例: 此函数的使用比较简单, 需要调用的时候直接调用即可。

3.HAL_GPIO_ReadPin

函数原型:

/**
  * @brief  读取指定 GPIO 引脚的电平状态
  * @param  GPIOx: 指向 GPIO 外设结构体的指针 (GPIOA/GPIOB/.../GPIOK)
  * @param  GPIO_Pin: 要读取的引脚号(如 GPIO_PIN_0、GPIO_PIN_1 等)
  * @retval GPIO_PinState: 引脚状态(GPIO_PIN_SET=高电平 / GPIO_PIN_RESET=低电平)
  */
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    GPIO_PinState bitstatus;  // 定义变量存储引脚状态
    
    /* 1. 参数合法性检查(断言):确保传入的引脚号有效 */
    assert_param(IS_GPIO_PIN(GPIO_Pin));
    
    /* 2. 读取IDR寄存器,判断引脚电平 */
    // IDR:GPIO输入数据寄存器,每一位对应一个引脚的电平(1=高,0=低)
    if((GPIOx->IDR & GPIO_Pin) != (uint32_t)GPIO_PIN_RESET)
    {
        bitstatus = GPIO_PIN_SET;    // 引脚为高电平
    }
    else
    {
        bitstatus = GPIO_PIN_RESET;  // 引脚为低电平
    }
    
    /* 3. 返回引脚状态 */
    return bitstatus;
}

函数描述:

此函数用于读取引脚状态, 通过 GPIO 的 IDR 寄存器读取。

函数参数:

第 1 个参数用于填写使用的端口号, 从 GPIOA 到 GPIOK。

第 2 个参数是配置选择的引脚,范围 GPIO_PIN_0 到 GPIO_PIN_15。

使用举例:

此函数的使用比较简单, 需要调用的时候直接调用即可 。

4.HAL_GPIO_WritePin

函数原型:

/**
  * @brief  设置指定 GPIO 引脚的输出电平
  * @param  GPIOx: 指向 GPIO 外设结构体的指针 (GPIOA/GPIOB/.../GPIOK)
  * @param  GPIO_Pin: 要设置的引脚号(如 GPIO_PIN_0、GPIO_PIN_1 等)
  * @param  PinState: 要设置的电平状态(GPIO_PIN_SET=高电平 / GPIO_PIN_RESET=低电平)
  * @retval 无
  */
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
    /* 1. 参数合法性检查(断言) */
    assert_param(IS_GPIO_PIN(GPIO_Pin));       // 检查引脚号是否有效
    assert_param(IS_GPIO_PIN_ACTION(PinState));// 检查电平状态是否合法

    /* 2. 根据目标电平,操作BSRR寄存器设置引脚状态 */
    if(PinState != GPIO_PIN_RESET)
    {
        // 设置引脚为高电平:向BSRRL(置位寄存器)写入引脚号
        GPIOx->BSRRL = GPIO_Pin;
    }
    else
    {
        // 设置引脚为低电平:向BSRRH(复位寄存器)写入引脚号
        GPIOx->BSRRH = GPIO_Pin;
    }
}

函数描述:

此函数用于设置引脚输出高电平或者低电平。使用 GPIO 的 BSRR 寄存器进行设置,使用这个寄存器的好处是支持原子操作,由硬件支持的。 原子操作的含义是操作过程不会被中断打断, 而我们使用 GPIO 中另一个设置输出的寄存 ODR 是会被中断打断的。

函数参数:

第 1 个参数用于填写使用的端口号, 从 GPIOA 到 GPIAK。

第 2 个参数是配置选择的引脚,范围 GPIO_PIN_0 到 GPIO_PIN_15。

第 3 个参数用于设置引脚输出高电平还是低电平, GPIO_PIN_RESET 表示低电平, GPIO_PIN_SET

表示高电平。

使用举例:

此函数的使用比较简单, 需要调用的时候直接调用即可。

5.HAL_GPIO_TogglePin

函数原型:

/**
  * @brief  翻转指定 GPIO 引脚的输出电平(高→低 / 低→高)
  * @param  GPIOx: 指向 GPIO 外设结构体的指针 (GPIOA/GPIOB/.../GPIOK)
  * @param  GPIO_Pin: 要翻转的引脚号(如 GPIO_PIN_0、GPIO_PIN_1 等)
  * @retval 无
  */
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    /* 参数合法性检查(断言):确保传入的引脚号有效 */
    assert_param(IS_GPIO_PIN(GPIO_Pin));

    /* 核心操作:异或ODR寄存器,翻转目标引脚的电平 */
    GPIOx->ODR ^= GPIO_Pin;
}

函数描述:

此函数用于设置引脚的电平翻转,使用 GPIO 的 ODR 寄存器进行设置。

函数参数:

第 1 个参数用于填写使用的端口号, 从 GPIOA 到 GPIAK。

第 2 个参数是配置选择的引脚,范围 GPIO_PIN_0 到 GPIO_PIN_15。

使用举例:

此函数的使用比较简单, 需要调用的时候直接调用即可。

6. HAL_GPIO_LockPin

函数原型:

/**
  * @brief  锁定指定 GPIO 引脚的配置(锁定后无法修改,仅复位可解锁)
  * @param  GPIOx: 指向 GPIO 外设结构体的指针 (GPIOA/GPIOB/.../GPIOK)
  * @param  GPIO_Pin: 要锁定的引脚号(如 GPIO_PIN_0、GPIO_PIN_1 等)
  * @retval HAL_StatusTypeDef: 操作状态(HAL_OK=锁定成功 / HAL_ERROR=锁定失败)
  */
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    __IO uint32_t tmp = GPIO_LCKR_LCKK;  // 初始化tmp,包含LCKK锁定键位(bit16)
    uint32_t status = HAL_ERROR;         // 默认状态为锁定失败

    /* 参数合法性检查 */
    assert_param(IS_GPIO_LOCK_INSTANCE(GPIOx));  // 检查GPIOx是否有效
    assert_param(IS_GPIO_PIN(GPIO_Pin));         // 检查引脚号是否有效

    /* ------------------- 执行 GPIO 锁定的特殊写入时序 ------------------- */
    // 1. 组合锁定键(LCKK)和要锁定的引脚
    tmp |= GPIO_Pin;

    // 2. 第一步写:设置 LCKK='1' + 要锁定的引脚位
    GPIOx->LCKR = tmp;

    // 3. 第二步写:复位 LCKK='0',仅保留要锁定的引脚位
    GPIOx->LCKR = GPIO_Pin;

    // 4. 第三步写:再次设置 LCKK='1' + 要锁定的引脚位
    GPIOx->LCKR = tmp;

    // 5. 读取LCKR寄存器,完成锁定时序(读操作是时序的一部分)
    tmp = GPIOx->LCKR;

    /* ------------------- 检查锁定是否成功 ------------------- */
    // 若LCKK位仍为1,说明锁定成功;否则失败
    if((GPIOx->LCKR & GPIO_LCKR_LCKK) != RESET)
    {
        status = HAL_OK;
    }

    return status;
}

函数描述:

此函数用于锁住 GPIO 引脚所涉及到的寄存器,这些寄存器包括 GPIOx_MODER, GPIOx_OTYPER, GPIOx_OSPEEDR, GPIOx_PUPDR, GPIOx_AFRL 和 GPIOx_AFRH。

函数参数:

第 1 个参数用于填写使用的端口号, 从 GPIOA 到 GPIAK。

第 2 个参数是配置选择的引脚,范围 GPIO_PIN_0 到 GPIO_PIN_15。

注意事项:

  1. 此函数是锁住用户设置的引脚所对应的寄存器某些位,并不是把整个寄存器都锁住了。

  2. 一旦锁住后, 就不能再修改,只有复位后才可以重新配置。

使用举例:

此函数的使用比较简单, 需要调用的时候直接调用即可。