2. GPIO控制与按键中断

任务目标

在嵌入式AI MCU上,通过GPIO读取按键状态,并利用外部中断机制实现按键触发时翻转LED的亮灭状态。本案例以STM32F407系列MCU为例,使用标准外设库实现,核心目标是掌握GPIO输入配置、外部中断线映射、中断服务函数编写及中断优先级的设置方法。

硬件连接与原理

  • LED正极通过限流电阻接至PA6(GPIOA Pin6),阴极接地;
  • 按键一端接PB12(GPIOB Pin12),另一端接地,按键按下为低电平;
  • PA6配置为推挽输出,PB12配置为上拉输入并映射到EXTI12外部中断线。

完整实现代码

#include "stm32f4xx.h"

// 简单延时函数(约1ms @168MHz,实际值略有偏差)
void Delay_MS(uint32_t ms) {
    for (uint32_t i = 0; i < ms * 4000; i++) {
        __NOP();
    }
}

// GPIO初始化:PA6输出,PB12输入
void GPIO_Init_Config(void) {
    GPIO_InitTypeDef GPIO_InitStruct;

    // 使能GPIOA和GPIOB时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB, ENABLE);

    // PA6 推挽输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // PB12 上拉输入
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
}

// EXTI配置:PB12映射到EXTI12
void EXTI_Init_Config(void) {
    EXTI_InitTypeDef EXTI_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    // 使能SYSCFG时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

    // 将PB12连接到EXTI12
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource12);

    // 配置EXTI12:下降沿触发(按键按下)
    EXTI_InitStruct.EXTI_Line = EXTI_Line12;
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStruct);

    // 配置NVIC中断优先级
    NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x02;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
}

// 主函数
int main(void) {
    GPIO_Init_Config();
    EXTI_Init_Config();

    // 初始状态:LED灭
    GPIO_ResetBits(GPIOA, GPIO_Pin_6);

    while (1) {
        // 主循环无任务,等待中断
    }
}

// EXTI15_10中断服务函数
void EXTI15_10_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line12) != RESET) {
        // 翻转LED状态
        if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_6) == Bit_SET) {
            GPIO_ResetBits(GPIOA, GPIO_Pin_6);
        } else {
            GPIO_SetBits(GPIOA, GPIO_Pin_6);
        }

        // 清除中断标志位
        EXTI_ClearITPendingBit(EXTI_Line12);
    }
}

编译与运行

  1. 使用Keil MDK或IAR创建STM32F407工程,将上述代码保存为main.c并加入工程;
  2. 工程配置中需定义STM32F40_41xxx宏,并添加标准外设库头文件路径;
  3. 编译无误后,通过ST-Link或J-Link下载至开发板。

验证方法

下载程序后,观察以下现象以确认功能正确:

  • 上电后LED保持熄灭状态;
  • 每次按下按键并释放,LED状态翻转一次(亮→灭或灭→亮);
  • 长按按键不会导致LED连续翻转,只有下降沿触发一次中断。

若LED状态与按键动作不一致,可通过调试器在EXTI15_10_IRQHandler处设置断点,检查中断是否进入以及GPIO翻转逻辑是否正确。

关键说明

  • EXTI12属于EXTI15_10中断通道,所有10~15号线共享同一个中断服务函数,因此必须在函数内判断具体中断标志位;
  • 按键未按下时PB12被内部上拉电阻拉高,按下时接地产生下降沿,故配置为下降沿触发;
  • 中断服务函数中必须清除中断标志位,否则中断会持续触发导致程序卡死。