☰
STM32系列1:GPIO
2026/10/7 13:14:37 网站建设 项目流程

1. 什么是GPIO?

GPIO(General Purpose Input/Output,通用输入输出)是微控制器、单片机以及树莓派等开发板上普遍提供的一类可编程引脚。我们可以通过软件把这些引脚配置为输入或输出,用来读取外部信号,或者控制外部设备。

简单来说,GPIO就是一块开发板与外界硬件“打交道”的通道:既能感知外部世界发生的变化(输入),也能对外部世界产生影响(输出)。

2. GPIO的核心概念

2.1 GPIO的8种工作模式

GPIO functional description定义了STM32中每个GPIO引脚可以通过软件配置成的8种工作模式。

2.1.1 输入模式

Input floating(浮空输入):引脚内部既不上拉也不下拉,处于高阻态,电平完全由外部信号决定。典型应用:外接上拉或下拉电阻的按键、I2C总线等在外部已有上拉的场景。

Input pull-up(上拉输入):内部通过电阻连接到VDD(3.3V),默认读到高电平。典型应用:按键一端接引脚、另一端接GND,按下时产生低电平。

Input pull-down(下拉输入):内部通过电阻连接到GND,默认读到低电平。典型应用:按键一端接引脚、另一端接VCC,按下时产生高电平。

2.1.2 模拟模式

Analog(模拟输入或输出):关闭内部的施密特触发器,让引脚直接连接到芯片内部的ADC、DAC或比较器。典型应用:ADC采集电池电压、光敏电阻分压,或DAC输出模拟波形。配置为模拟模式还能大幅降低引脚功耗,并防止数字噪声干扰模拟信号。

2.1.3 输出模式

Output push-pull(推挽输出):这是STM32 GPIO最常见的输出模式之一。引脚内部由两个晶体管配合驱动,一个是N型晶体管,另一个是P型晶体管。输出高电平时,P型晶体管导通、N型晶体管截止,引脚被拉高至VDD;输出低电平时,N型晶体管导通、P型晶体管截止,引脚被拉低至GND。因此推挽输出可以主动输出高电平和低电平,驱动能力较强。典型应用:直接驱动LED、蜂鸣器、继电器,或输出SPI、UART等高速数字信号。

Output open-drain(开漏输出):与推挽输出不同,开漏输出只有一个N型晶体管连接到引脚。输出低电平时,N型晶体管导通,引脚被拉低至GND;输出高电平时,N型晶体管截止,引脚处于高阻态,此时需要外部上拉电阻将引脚拉至高电平。因此开漏输出只能主动拉低电平,不能主动拉高电平。典型应用:I2C 通信、多个设备共享一根信号线(线与逻辑),以及 3.3V 器件驱动 5V 器件等电平转换场景。

2.1.4 复用功能模式

当引脚交给芯片内部外设(如 USART、SPI、I2C、TIM、CAN)控制时,就属于复用功能模式。

Alternate function push-pull(复用推挽):由内部外设控制,采用推挽输出。典型应用:UART_TX、SPI_SCK、PWM输出。

Alternate function open-drain(复用开漏):由内部外设控制,采用开漏输出。典型应用:I2C的SCL和SDA、部分CAN总线收发器。

2.2 输出速度(GPIO speed)

GPIO的输出速度并不是通信速率,而是指输出驱动器翻转电平时的边沿变化速度,也就是从低电平跳到高电平、或从高电平跳到低电平的转换速率(slew rate)。

常见档位如下:

  • Low speed:低速档,约2MHz,翻转较慢,噪声低。
  • Medium speed:中速档,约10-25MHz。
  • High speed:高速档,约50MHz,边沿更陡峭。
  • Very high speed:超高速档,部分型号可达100MHz以上。

速度越高,电平翻转越快,但代价是更明显的电磁干扰(EMI)、更高的瞬时功耗和更多振铃;速度越低,信号越干净,但可能无法满足高速通信需求。因此通常建议在满足信号要求的前提下,选择尽量低的速度档。

2.3 施密特触发器的作用

施密特触发器位于GPIO输入通路中,它的核心作用是把外部不够干净的信号整形为稳定、干净的数字电平。

真实环境里的输入信号通常不是完美的方波,可能带有毛刺、抖动,或者电平上升、下降非常缓慢。如果直接用这类信号驱动后续数字电路,引脚电平会在高、低阈值附近来回波动,CPU可能反复读到0和1,造成抖动或误触发。施密特触发器通过滞回特性解决这个问题,它给上升和下降过程设置了两个不同的阈值:

  • 上升判断:输入电压升高并超过较高阈值时,输出才翻转为高电平。
  • 下降判断:输入电压降低并低于较低阈值时,输出才翻转为低电平。
  • 中间区间:输入电压处在两个阈值之间时,输出保持上一次的状态,不会反复跳变。

经过施密特触发器整形后,带噪声、缓变的模拟波形会变成干净、锐利的0或1,再送入输入数据寄存器。

这就是为什么模拟模式要关闭施密特触发器:ADC或DAC需要的是原始、连续的模拟信号,施密特触发器的滞回判断会改变信号形态并引入数字干扰,因此模拟输入时通常直接旁路掉这一级。

2.4 复用功能

复用功能就像给同一个引脚分配了多张“身份卡”。前面讲过,当引脚交给USART、SPI、I2C、TIM、CAN等内部外设控制时,就进入复用功能模式。但一个引脚通常不只对应一个外设,例如 PA9可以复用为USART1_TX,也可能复用为TIM1_CH2或其他外设接口。具体轮到哪一个外设,由复用功能编号决定。

STM32用AF编号来区分同一引脚的多个外设连接。不同型号的映射表不完全相同,因此编写代码时必须查阅对应型号数据手册中的复用功能表。常见的AF选择包括:

  • AF0至AF15:不同编号通常对应不同外设,例如AF7可能对应USART1、AF5可能对应SPI1 等,具体以数据手册为准。
  • 绑定关系:同一个引脚可能支持多个AF,但同一时刻只能选择一个;例如PA9选AF7时作为USART1_TX,选其他AF时就可能变成定时器通道。
  • 特殊编号:AF0通常表示普通GPIO或系统功能,并非所有引脚都具备全部AF选项。

2.5 拉电流与灌电流能力

在GPIO输出和控制负载时,经常会提到拉电流和灌电流。它们描述的是电流相对芯片引脚的方向:

  • 拉电流:引脚输出高电平时,芯片通过引脚向外部负载“流出”的电流。例如LED正极接GPIO、负极接地时,点亮LED需要走拉电流。
  • 灌电流:引脚输出低电平时,外部电流“流入”芯片引脚、再回到地。例如LED正极接VDD、负极接GPIO时,点亮LED靠的是灌电流。

3. GPIO外设接口原理分析

ST公司提供的关于GPIO的案例代码中,调用了很多关于GPIO外设相关的函数接口以及数据类型,作为初学者必须认真分析代码,才能掌握GPIO外设的核心原理。

/** * @brief GPIO Init structure definition */ typedef struct { uint32_t Pin; /*!< Specifies the GPIO pins to be configured. This parameter can be any value of @ref GPIO_pins_define */ uint32_t Mode; /*!< Specifies the operating mode for the selected pins. This parameter can be a value of @ref GPIO_mode_define */ uint32_t Pull; /*!< Specifies the Pull-up or Pull-Down activation for the selected pins. This parameter can be a value of @ref GPIO_pull_define */ uint32_t Speed; /*!< Specifies the speed for the selected pins. This parameter can be a value of @ref GPIO_speed_define */ uint32_t Alternate; /*!< Peripheral to be connected to the selected pins. This parameter can be a value of @ref GPIO_Alternate_function_selection */ }GPIO_InitTypeDef; /** @brief GPIO Bit SET and Bit RESET enumeration */ typedef enum { GPIO_PIN_RESET = 0, GPIO_PIN_SET }GPIO_PinState; /** @defgroup GPIO_pins_define GPIO pins define @{ / #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 */ #define GPIO_PIN_MASK 0x0000FFFFU /* PIN mask for assert test */ /** @defgroup GPIO_mode_define GPIO mode define @brief GPIO Configuration Mode Elements values convention: 0x00WX00YZ - W : EXTI trigger detection on 3 bits - X : EXTI mode (IT or Event) on 2 bits - Y : Output type (Push Pull or Open Drain) on 1 bit - Z : GPIO mode (Input, Output, Alternate or Analog) on 2 bits @{ / #define GPIO_MODE_INPUT MODE_INPUT /!< Input Floating Mode / #define GPIO_MODE_OUTPUT_PP (MODE_OUTPUT | OUTPUT_PP) /!< Output Push Pull Mode / #define GPIO_MODE_OUTPUT_OD (MODE_OUTPUT | OUTPUT_OD) /!< Output Open Drain Mode / #define GPIO_MODE_AF_PP (MODE_AF | OUTPUT_PP) /!< Alternate Function Push Pull Mode / #define GPIO_MODE_AF_OD (MODE_AF | OUTPUT_OD) /!< Alternate Function Open Drain Mode */ #define GPIO_MODE_ANALOG MODE_ANALOG /*!< Analog Mode */ #define GPIO_MODE_IT_RISING (MODE_INPUT | EXTI_IT | TRIGGER_RISING) /*!< External Interrupt Mode with Rising edge trigger detection / #define GPIO_MODE_IT_FALLING (MODE_INPUT | EXTI_IT | TRIGGER_FALLING) /!< External Interrupt Mode with Falling edge trigger detection / #define GPIO_MODE_IT_RISING_FALLING (MODE_INPUT | EXTI_IT | TRIGGER_RISING | TRIGGER_FALLING) /!< External Interrupt Mode with Rising/Falling edge trigger detection */ #define GPIO_MODE_EVT_RISING (MODE_INPUT | EXTI_EVT | TRIGGER_RISING) /*!< External Event Mode with Rising edge trigger detection / #define GPIO_MODE_EVT_FALLING (MODE_INPUT | EXTI_EVT | TRIGGER_FALLING) /!< External Event Mode with Falling edge trigger detection / #define GPIO_MODE_EVT_RISING_FALLING (MODE_INPUT | EXTI_EVT | TRIGGER_RISING | TRIGGER_FALLING) /!< External Event Mode with Rising/Falling edge trigger detection */ /** @} */ /** @defgroup GPIO_speed_define GPIO speed define @brief GPIO Output Maximum frequency @{ / #define GPIO_SPEED_FREQ_LOW 0x00000000U /!< IO works at 2 MHz, please refer to the product datasheet / #define GPIO_SPEED_FREQ_MEDIUM 0x00000001U /!< range 12,5 MHz to 50 MHz, please refer to the product datasheet / #define GPIO_SPEED_FREQ_HIGH 0x00000002U /!< range 25 MHz to 100 MHz, please refer to the product datasheet / #define GPIO_SPEED_FREQ_VERY_HIGH 0x00000003U /!< range 50 MHz to 200 MHz, please refer to the product datasheet / /* @} */ /** @defgroup GPIO_pull_define GPIO pull define @brief GPIO Pull-Up or Pull-Down Activation @{ / #define GPIO_NOPULL 0x00000000U /!< No Pull-up or Pull-down activation / #define GPIO_PULLUP 0x00000001U /!< Pull-up activation / #define GPIO_PULLDOWN 0x00000002U /!< Pull-down activation */ /** @addtogroup GPIO_Exported_Functions_Group1 @{ / / Initialization and de-initialization functions *****************************/ void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init); void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin); /** @addtogroup GPIO_Exported_Functions_Group2 @{ / / IO operation functions *************************************************/ GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef GPIOx, uint16_t GPIO_Pin); void HAL_GPIO_WritePin(GPIO_TypeDef GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState); void HAL_GPIO_TogglePin(GPIO_TypeDef GPIOx, uint16_t GPIO_Pin); HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef GPIOx, uint16_t GPIO_Pin); void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin); void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);

4. 完整示例:按键控制LED翻转

下面是一个完整的按键控制LED翻转。该程序使用PA0按键控制PF9上的LED,使用PE2按键控制PF10上的LED,采用主循环轮询加软件消抖的方式,并在检测到按键按下后等待释放,避免一次按压被重复触发。

/** ****************************************************************************** * #文件: main.c * #简要描述: 通过按键来实现LED灯的翻转(轮询方式) * #详细描述: 该程序通过检测按键的状态来控制LED灯的翻转。当按下按键时,LED灯的状态会发生改变。 * #作者: 游走于孤独的码农 * #时间: 2026-10-06 ****************************************************************************** */ /* 头文件 */ #include "main.h" /* 函数声明 */ void SystemClock_Config(void); static void Key_Init(void); static void Led_Init(void); /** * #简要描述:所有程序的入口点 * #返回值: void */ int main(void) { /* MCU配置 */ /* 重启所有的外设 */ HAL_Init(); /* 配置系统时钟 */ SystemClock_Config(); /* 初始化所有配置外设 */ Key_Init(); Led_Init(); /* 死循环 */ while (1) { /* 消抖 */ HAL_Delay(10); /* 检测按键是否按下 */ if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { /* 翻转LED灯 */ HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9); while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { /* 等待按键释放 */ } } /* 检测按键是否按下 */ if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_2) == GPIO_PIN_RESET) { /* 翻转LED灯 */ HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10); while(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_2) == GPIO_PIN_RESET) { /* 等待按键释放 */ } } } } /** * #简要描述:系统时钟配置(HAL库自带) * #返回值: void */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) { Error_Handler(); } } /** * #简要描述: 按键初始化 * #详细描述: 该函数用于初始化按键引脚PA0和PE2,配置为输入模式。 * #返回值: void */ static void Key_Init(void) { /* GPIOA时钟使能 */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOE_CLK_ENABLE(); /* 配置PA0为输入模式 */ GPIO_InitTypeDef Btn_InitStruct_PA0 = { .Pin = GPIO_PIN_0, .Mode = GPIO_MODE_INPUT, .Pull = GPIO_NOPULL }; HAL_GPIO_Init(GPIOA, &Btn_InitStruct_PA0); /* 配置PE2为输入模式 */ GPIO_InitTypeDef Btn_InitStruct_PE2 = { .Pin = GPIO_PIN_2, .Mode = GPIO_MODE_INPUT, .Pull = GPIO_NOPULL }; HAL_GPIO_Init(GPIOE, &Btn_InitStruct_PE2); } /** * #简要描述: led初始化 * #详细描述: 该函数用于初始化LED引脚PF9和PF10,配置为输出模式。 * #返回值: void */ static void Led_Init(void) { /* GPIOF时钟使能 */ __HAL_RCC_GPIOF_CLK_ENABLE(); /* 配置PF9和PF10为输出模式 */ GPIO_InitTypeDef Led_InitStruct = { .Pin = GPIO_PIN_9 | GPIO_PIN_10, .Mode = GPIO_MODE_OUTPUT_PP, .Pull = GPIO_NOPULL, .Speed = GPIO_SPEED_FREQ_LOW }; HAL_GPIO_Init(GPIOF, &Led_InitStruct); } /*----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询