STM32 GPIO 基础

GPIO(General Purpose Input/Output)是微控制器最基本的外设。STM32 的 GPIO 功能强大,支持多种模式和配置。

GPIO 引脚特性

  • 多种模式:输入、输出、复用、模拟
  • 速度配置:2MHz 到 200MHz+
  • 上下拉电阻:内置可配置
  • 驱动能力:可配置输出强度
  • 中断支持:外部中断/事件

GPIO 工作模式

1. 输入模式

模式 说明 应用
浮空输入 无上拉下拉 按键(外部有电阻)
上拉输入 内置上拉电阻 按键(默认高电平)
下拉输入 内置下拉电阻 按键(默认低电平)
模拟输入 ADC 采集 传感器、电位器

2. 输出模式

模式 说明 应用
推挽输出 高低电平驱动 LED、继电器
开漏输出 需要上拉电阻 I2C、电平转换

寄存器编程(裸机)

GPIO 寄存器

// GPIO 寄存器结构
typedef struct {
    volatile uint32_t MODER;    // 模式寄存器
    volatile uint32_t OTYPER;   // 输出类型
    volatile uint32_t OSPEEDR;  // 输出速度
    volatile uint32_t PUPDR;    // 上下拉
    volatile uint32_t IDR;      // 输入数据
    volatile uint32_t ODR;      // 输出数据
    volatile uint32_t BSRR;     // 置位/复位
    volatile uint32_t LCKR;     // 锁定
    volatile uint32_t AFR[2];   // 复用功能
} GPIO_TypeDef;

LED 控制示例

// 配置 PA5 为推挽输出
void gpio_init(void) {
    // 1. 使能 GPIOA 时钟
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    
    // 2. 配置 PA5 为输出模式 (01)
    GPIOA->MODER &= ~(3 << 10);
    GPIOA->MODER |= (1 << 10);
    
    // 3. 配置推挽输出
    GPIOA->OTYPER &= ~(1 << 5);
    
    // 4. 配置高速
    GPIOA->OSPEEDR |= (3 << 10);
    
    // 5. 无上下拉
    GPIOA->PUPDR &= ~(3 << 10);
}

// LED 开关
void led_on(void) {
    GPIOA->BSRR = (1 << 5);  // 置位
}

void led_off(void) {
    GPIOA->BSRR = (1 << 21); // 复位 (5+16=21)
}

void led_toggle(void) {
    GPIOA->ODR ^= (1 << 5);  // 翻转
}

HAL 库编程

初始化代码

#include "stm32f4xx_hal.h"

GPIO_InitTypeDef GPIO_InitStruct = {0};

void MX_GPIO_Init(void) {
    // GPIO 时钟使能
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 配置 PA5
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// 使用
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);   // 开
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // 关
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);                // 翻转

GPIO 中断

外部中断配置

// 配置 PA0 为中断输入
void gpio_interrupt_init(void) {
    // 1. 使能时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_SYSCFG_CLK_ENABLE();
    __HAL_RCC_GPIO_EXTI_CLK_ENABLE();
    
    // 2. 配置 PA0 为上拉输入
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 3. 配置 NVIC
    HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}

// 中断处理函数
void EXTI0_IRQHandler(void) {
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}

// 回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == GPIO_PIN_0) {
        // 处理中断
        led_toggle();
    }
}

高级功能

复用功能

GPIO 可映射到外设(UART、SPI、I2C 等):

// 配置 PA9/PA10 为 USART1_TX/RX
GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;  // 复用功能 7
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

锁定功能

防止意外修改 GPIO 配置:

// 锁定 PA5 配置
GPIOA->LCKR = (1 << 16) | (1 << 5);  // LCKK + LCK5
GPIOA->LCKR = (1 << 5);              // LCK5
GPIOA->LCKR = (1 << 16) | (1 << 5);  // LCKK + LCK5

最佳实践

✅ 推荐做法

  1. 初始化前使能时钟
  2. 使用 BSRR 代替 ODR(原子操作)
  3. 中断中快速处理(置标志,主循环处理)
  4. 添加去抖动(按键)
  5. 使用 HAL 库提高可移植性

❌ 避免的错误

  1. 忘记使能时钟(最常见)
  2. 直接写 ODR 影响其他引脚
  3. 中断处理时间过长
  4. 忽略引脚电气特性
  5. 未配置上下拉导致浮空

调试技巧

问题排查

问题 可能原因 解决方法
引脚无输出 时钟未使能 检查 RCC 配置
电平不对 上下拉配置错误 检查 PUPDR
速度不够 速度配置过低 调整 OSPEEDR
中断不触发 NVIC 未配置 检查中断优先级

测量工具

  • 万用表:测量电平
  • 示波器:观察波形
  • 逻辑分析仪:多通道时序分析

结语

GPIO 是 STM32 编程的基础。掌握 GPIO 编程后,你可以控制 LED、读取按键、连接传感器,为学习更复杂的外设打下基础。


参考资料:STM32 参考手册、STM32CubeMX 用户指南、Arm Cortex-M 技术文档