一、GPIO 功能模式分析
1.推挽输出

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

开漏端相当于 MOS 管的漏极(三极管的集电极),要得到高电平状态必须外接上拉电阻才行,因此输出高电平的驱动能力完全由外接上拉电阻决定,但是其输出低电平的驱动能力很强。开漏形式的电路有以下几个特点:
输出高电平时利用外部电路的驱动能力,减少 IC 内部的驱动。
开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平。 如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。上拉电阻的阻值决定了逻辑电平转换的速度。阻值越大,速度越低, 功耗越小。
开漏输出提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。
可以将多个开漏输出连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系,即“线与”。可以简单的理解为:在所有引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑 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。
注意事项:
此函数是锁住用户设置的引脚所对应的寄存器某些位,并不是把整个寄存器都锁住了。
一旦锁住后, 就不能再修改,只有复位后才可以重新配置。
使用举例:
此函数的使用比较简单, 需要调用的时候直接调用即可。


