4. 外部中断处理

任务目标

在MCU上配置一个外部中断引脚,连接按键。当按键按下时触发外部中断服务程序(ISR),在ISR中翻转LED的亮灭状态。本案例以STM32F103系列(标准外设库)为例,实现完整的按键中断控制LED。

硬件连接

  • 按键:PA0(接3.3V,按下时产生上升沿)
  • LED:PC13(低电平点亮,板载LED)
  • 外部中断线:EXTI0,映射到PA0

关键实现

外部中断配置分为四步:GPIO时钟使能、GPIO引脚配置为输入模式、EXTI与NVIC配置、中断服务函数编写。注意PA0需要配置为下拉输入(按键接VCC时),EXTI0触发方式选择上升沿。中断服务函数中必须清除EXTI挂起位,否则中断会反复进入。

完整代码

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_exti.h"
#include "stm32f10x_rcc.h"
#include "misc.h"

// LED状态标志,在中断中翻转
volatile uint8_t led_state = 0;

// 按键外部中断初始化
void EXTI_Key_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 1. 使能GPIOA和GPIOC时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);

    // 2. 配置PA0为下拉输入(按键接3.3V)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;  // 下拉输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 3. 配置PC13为推挽输出(LED)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    // 4. 将PA0连接到EXTI0
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

    // 5. 配置EXTI0:上升沿触发
    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    // 6. 配置NVIC:使能EXTI0中断通道
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

// 外部中断0服务函数
void EXTI0_IRQHandler(void) {
    // 检查EXTI0挂起位
    if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
        // 翻转LED状态
        led_state = !led_state;
        if (led_state) {
            GPIO_SetBits(GPIOC, GPIO_Pin_13);   // 灭
        } else {
            GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 亮
        }
        // 清除挂起位,必须执行
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

int main(void) {
    // 初始化LED为灭状态
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    GPIO_SetBits(GPIOC, GPIO_Pin_13);  // 初始灭

    // 初始化外部中断
    EXTI_Key_Init();

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

编译与烧录

  1. 使用Keil MDK或IAR创建工程,芯片选择STM32F103C8T6(或对应型号)。
  2. 添加标准外设库文件:stm32f10x_gpio.c、stm32f10x_exti.c、stm32f10x_rcc.c、misc.c。
  3. 在工程设置中定义STM32F10X_MD,并包含标准外设库头文件路径。
  4. 编译后将生成的HEX文件通过ST-Link或串口烧录到开发板。

验证方法

烧录后观察以下现象:

  • 上电时LED处于熄灭状态(PC13输出高电平)。
  • 每按一次按键(PA0接高电平),LED状态翻转一次:亮→灭→亮→灭。
  • 长按按键不会连续翻转(上升沿触发,只有按下瞬间触发一次)。

若按下按键LED无反应,检查PA0是否确实连接到按键输出、按键另一端是否接3.3V,以及EXTI0中断是否已在NVIC中使能。

常见问题

  • 按键抖动导致多次翻转:本案例未做软件消抖,若需消除抖动可在ISR中加入10~20ms延时或使用定时器消抖。
  • 中断不触发:确认GPIO时钟和AFIO时钟均已使能(EXTI需要AFIO时钟),且GPIO_EXTILineConfig已正确调用。
  • 编译报错:检查是否遗漏misc.c(NVIC配置依赖该文件)。