2. 外部中断唤醒与按键消抖

任务定义

本案例实现一个基于外部中断的按键唤醒系统:MCU进入深度睡眠模式后,按键产生的下降沿通过EXTI唤醒MCU;唤醒后在中断服务程序中启动软件定时器消抖,消抖确认有效按键后执行一次任务(翻转LED状态),随后重新进入低功耗模式。目标芯片为STM32L0系列,代码基于HAL库。

硬件连接

STM32L0 Cortex-M0+ PA0 PB5 EXTI0 KEY VCC(3.3V) 10kΩ GND PB5 LED 330Ω VCC 低功耗模式 STOP Mode (RTC off) 按键唤醒

软件消抖原理

按键按下时会产生约5–20ms的机械抖动,若在EXTI中断中直接读取电平并执行任务,极易误触发。本案例采用“中断唤醒+定时器确认”的两步消抖策略:EXTI下降沿唤醒MCU后,先关闭该引脚中断,启动一个10ms的软件定时器;定时器到期后再次读取引脚电平,若仍为低电平则判定为有效按键,执行任务;否则视为抖动,直接重新进入低功耗。该方案避免了阻塞式延时,使MCU在消抖等待期间仍处于低功耗状态。

代码实现


// main.c — STM32L0 外部中断唤醒按键消抖示例
#include "stm32l0xx_hal.h"

/* 引脚定义 */
#define KEY_PIN       GPIO_PIN_0
#define KEY_PORT      GPIOA
#define LED_PIN       GPIO_PIN_5
#define LED_PORT      GPIOB
#define KEY_EXTI_IRQ  EXTI0_1_IRQn

/* 状态标志 */
static volatile uint8_t key_pressed = 0;   // 消抖确认标志
static volatile uint8_t debounce_started = 0;

/* 软件定时器计数(SysTick 1ms中断累加) */
static volatile uint32_t tick_ms = 0;
static uint32_t debounce_timeout = 0;

void SystemClock_Config(void);
static void GPIO_Init(void);
static void EnterLowPower(void);
static void ExecuteTask(void);

/* SysTick 1ms 中断 */
void SysTick_Handler(void) {
    tick_ms++;
}

/* EXTI0中断服务函数 */
void EXTI0_1_IRQHandler(void) {
    if (__HAL_GPIO_EXTI_GET_IT(KEY_PIN) != RESET) {
        __HAL_GPIO_EXTI_CLEAR_IT(KEY_PIN);
        
        if (!debounce_started) {
            /* 关闭EXTI中断,防止消抖期间重复触发 */
            HAL_NVIC_DisableIRQ(KEY_EXTI_IRQ);
            /* 记录消抖起始时间 */
            debounce_timeout = tick_ms + 10;  // 10ms后确认
            debounce_started = 1;
        }
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    GPIO_Init();
    
    /* 配置SysTick 1ms中断 */
    HAL_SYSTICK_Config(SystemCoreClock / 1000);
    
    while (1) {
        /* 消抖确认逻辑(主循环中非阻塞检查) */
        if (debounce_started) {
            if ((int32_t)(tick_ms - debounce_timeout) >= 0) {
                debounce_started = 0;
                
                /* 再次读取引脚电平确认 */
                if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) {
                    key_pressed = 1;
                }
                
                /* 重新使能EXTI中断 */
                HAL_NVIC_EnableIRQ(KEY_EXTI_IRQ);
            }
        }
        
        /* 执行任务 */
        if (key_pressed) {
            key_pressed = 0;
            ExecuteTask();
        }
        
        /* 进入低功耗模式 */
        EnterLowPower();
    }
}

static void ExecuteTask(void) {
    /* 翻转LED状态 */
    HAL_GPIO_TogglePin(LED_PORT, LED_PIN);
}

static void EnterLowPower(void) {
    /* 进入STOP模式,仅保留EXTI唤醒 */
    HAL_SuspendTick();  // 暂停SysTick,降低功耗
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    /* 唤醒后恢复 */
    HAL_ResumeTick();
    SystemClock_Config();  // 重新配置时钟
}

static void GPIO_Init(void) {
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    
    GPIO_InitTypeDef gpio = {0};
    
    /* PA0 按键输入,下拉电阻 */
    gpio.Pin = KEY_PIN;
    gpio.Mode = GPIO_MODE_IT_FALLING;
    gpio.Pull = GPIO_PULLDOWN;
    HAL_GPIO_Init(KEY_PORT, &gpio);
    
    /* PB5 LED输出 */
    gpio.Pin = LED_PIN;
    gpio.Mode = GPIO_MODE_OUTPUT_PP;
    gpio.Pull = GPIO_NOPULL;
    gpio.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(LED_PORT, &gpio);
    
    /* 使能EXTI中断 */
    HAL_NVIC_SetPriority(KEY_EXTI_IRQ, 2, 0);
    HAL_NVIC_EnableIRQ(KEY_EXTI_IRQ);
}

void SystemClock_Config(void) {
    /* 使用MSI 2.1MHz,最低功耗配置 */
    RCC_OscInitTypeDef osc = {0};
    RCC_ClkInitTypeDef clk = {0};
    
    osc.OscillatorType = RCC_OSCILLATORTYPE_MSI;
    osc.MSIState = RCC_MSI_ON;
    osc.MSIClockRange = RCC_MSIRANGE_5;  // 2.097MHz
    osc.MSICalibrationValue = 0;
    HAL_RCC_OscConfig(&osc);
    
    clk.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
                    RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    clk.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
    clk.AHBCLKDivider = RCC_SYSCLK_DIV1;
    clk.APB1CLKDivider = RCC_HCLK_DIV1;
    clk.APB2CLKDivider = RCC_HCLK_DIV1;
    HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_0);
}

编译与烧录


# 使用STM32CubeIDE或Makefile工程编译
# 依赖:STM32CubeL0 HAL库
arm-none-eabi-gcc -mcpu=cortex-m0plus -mthumb \
  -DSTM32L072xx -IInc \
  -c main.c -o main.o
# 链接生成hex后通过ST-Link烧录
st-flash write firmware.hex 0x08000000

验证方法

测试步骤:

  1. 烧录程序后,观察LED初始状态(默认灭);
  2. 用示波器探头连接PA0引脚,按下按键并释放,观察波形存在抖动;
  3. 快速点击按键(<10ms间隔),LED不应翻转——验证消抖有效;
  4. 正常按下按键(保持>50ms后释放),LED状态翻转——验证任务执行;
  5. 用电流表串联供电端,待机时测量电流应处于μA级(STOP模式典型值约3μA);
  6. 在按键按下瞬间观察电流波形出现短暂上升,随后恢复低电平——验证唤醒后重新入睡。

判定标准:快速抖动不触发LED翻转,正常按键每次可靠翻转一次;待机电流低于5μA;每次按键唤醒后MCU能自动重新进入低功耗状态。