【04】基于STM32Fx的按键扫描与蜂鸣器控制简化版

【04】基于STM32Fx的按键扫描与蜂鸣器控制简化版

代码经实战检验,适合初学者,下面是独立按键扫描的详细过程:
第一步:平时没有按键被触发时,按键的自锁标志,去抖动延时计数器一直被清零。
第二步:一旦有按键被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到阀值时,如果在这期间由于受外界干扰或者按键抖动,而使IO口突然瞬间触发成高电平,这个时候马上把延时计数器清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。以后凡是用到开关感应器的时候,都可以用类似这样的方法去干扰。
第三步:如果按键按下的时间超过了阀值,则触发按键,把编号赋值。同时,马上把自锁标志置位,防止按住按键不松手后一直触发。
第四步:等按键松开后,自锁标志及时清零,为下一次自锁做准备。
第五步:以上整个过程,就是识别按键IO口下降沿触发的过程。
完整代码如下,供初学按键控制同学参考。

/*************************************************
模块名称:简化版按键扫描与蜂鸣器控制(无结构体)
硬件平台:STM32F407VET6
功能说明:
1. 使用TIM2定时中断(1ms周期)实现双独立按键扫描
2. 20ms去抖动处理,防止误触发
3. 按键触发蜂鸣器短鸣40ms
4. 代码去结构体化,适合初学者学习
**************************************************/

/*------------------------头文件包含------------------------*/
#include "main.h"
#include "stm32f4xx_hal.h"

/*------------------------全局宏定义-----------------------*/
#define KEY_SCAN_INTERVAL    1     // 按键扫描间隔1ms
#define DEBOUNCE_TIME       20     // 去抖动时间20ms
#define BEEP_DURATION       40     // 蜂鸣持续时间40ms

/*------------------------硬件引脚定义---------------------*/
#define KEY1_GPIO_PORT      GPIOA
#define KEY1_PIN            GPIO_PIN_0
#define KEY2_GPIO_PORT      GPIOA
#define KEY2_PIN            GPIO_PIN_1
#define BEEP_GPIO_PORT      GPIOA
#define BEEP_PIN            GPIO_PIN_8

/*------------------------全局变量声明---------------------*/
volatile uint8_t g_ucKeyEvent = 0;        // 按键事件标志
volatile uint16_t g_uiBeepCounter = 0;    // 蜂鸣器计时器

// 按键1相关变量
volatile uint16_t uiKey1DebounceCnt = 0;  // 按键1去抖动计数器
volatile uint8_t ucKey1Lock = 0;          // 按键1自锁标志
volatile uint8_t ucKey1PrevState = 0;     // 按键1前次状态

// 按键2相关变量
volatile uint16_t uiKey2DebounceCnt = 0;  // 按键2去抖动计数器
volatile uint8_t ucKey2Lock = 0;          // 按键2自锁标志
volatile uint8_t ucKey2PrevState = 0;     // 按键2前次状态

/*------------------------函数声明-------------------------*/
void SystemClock_Config(void);
void MX_GPIO_Init(void);
void MX_TIM2_Init(void);
void Key_Scan_Handler(void);
void Beep_Process(void);

/*------------------------主函数---------------------------*/
int main(void) {
    HAL_Init();
    SystemClock_Config();  // 配置系统时钟为168MHz
    MX_GPIO_Init();        // 初始化GPIO
    MX_TIM2_Init();        // 配置TIM2定时器
    
    HAL_TIM_Base_Start_IT(&htim2);  // 启动定时器中断

    while (1) {
        // 主循环检测按键事件
        if(g_ucKeyEvent != 0) {
            switch(g_ucKeyEvent) {
                case 1:  // 按键1触发
                case 2:  // 按键2触发
                    g_uiBeepCounter = BEEP_DURATION;  // 蜂鸣器响40ms
                    g_ucKeyEvent = 0;  // 清除事件标志
                    break;
            }
        }
    }
}

/*------------------------中断服务函数---------------------*/

/**
  * @brief TIM2定时中断回调函数
  * @note 每1ms执行按键扫描和蜂鸣器控制
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if(htim->Instance == TIM2) {
        Key_Scan_Handler();  // 执行按键扫描
        Beep_Process();      // 控制蜂鸣器
    }
}

/*------------------------模块化函数实现-------------------*/

/**
  * @brief GPIO初始化
  * @note 配置按键输入和蜂鸣器输出
  */
void MX_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    __HAL_RCC_GPIOA_CLK_ENABLE();  // 使能GPIOA时钟

    // 配置按键引脚(下拉输入模式)
    GPIO_InitStruct.Pin = KEY1_PIN | KEY2_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;  // 默认低电平,按下变高
    HAL_GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStruct);

    // 配置蜂鸣器引脚(推挽输出)
    GPIO_InitStruct.Pin = BEEP_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(BEEP_GPIO_PORT, &GPIO_InitStruct);
    HAL_GPIO_WritePin(BEEP_GPIO_PORT, BEEP_PIN, GPIO_PIN_SET);  // 初始关闭蜂鸣器
}

/**
  * @brief TIM2初始化(1ms中断)
  */
void MX_TIM2_Init(void) {
    TIM_HandleTypeDef htim2;
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 8400 - 1;   // 84MHz/8400=10kHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 10 - 1;        // 10kHz/10=1ms
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim2);
    HAL_TIM_Base_Start_IT(&htim2);     // 启动中断
    
    HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);  // 设置中断优先级
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
}

/**
  * @brief 按键扫描处理
  * @note 使用独立变量实现去抖动逻辑
  */
void Key_Scan_Handler(void) {
    //---------------- 按键1扫描 ----------------
    uint8_t ucKey1Current = HAL_GPIO_ReadPin(KEY1_GPIO_PORT, KEY1_PIN);
    
    if(ucKey1Current != ucKey1PrevState) {
        uiKey1DebounceCnt = 0;        // 状态变化时重置计数器
        ucKey1PrevState = ucKey1Current;
    } else {
        uiKey1DebounceCnt += KEY_SCAN_INTERVAL;  // 累加计数
    }

    // 检测稳定状态
    if(uiKey1DebounceCnt >= DEBOUNCE_TIME) {
        if(ucKey1Current == GPIO_PIN_SET && ucKey1Lock == 0) {
            ucKey1Lock = 1;          // 自锁防止重复触发
            g_ucKeyEvent = 1;       // 上报按键1事件
        } else if(ucKey1Current == GPIO_PIN_RESET) {
            ucKey1Lock = 0;          // 松开时清除自锁
        }
    }

    //---------------- 按键2扫描(逻辑相同) ----------------
    uint8_t ucKey2Current = HAL_GPIO_ReadPin(KEY2_GPIO_PORT, KEY2_PIN);
    if(ucKey2Current != ucKey2PrevState) {
        uiKey2DebounceCnt = 0;
        ucKey2PrevState = ucKey2Current;
    } else {
        uiKey2DebounceCnt += KEY_SCAN_INTERVAL;
    }
    
    if(uiKey2DebounceCnt >= DEBOUNCE_TIME) {
        if(ucKey2Current == GPIO_PIN_SET && ucKey2Lock == 0) {
            ucKey2Lock = 1;
            g_ucKeyEvent = 2;       // 上报按键2事件
        } else if(ucKey2Current == GPIO_PIN_RESET) {
            ucKey2Lock = 0;
        }
    }
}

/**
  * @brief 蜂鸣器控制
  */
void Beep_Process(void) {
    if(g_uiBeepCounter > 0) {
        HAL_GPIO_WritePin(BEEP_GPIO_PORT, BEEP_PIN, GPIO_PIN_RESET);  // 鸣响
        g_uiBeepCounter--;
    } else {
        HAL_GPIO_WritePin(BEEP_GPIO_PORT, BEEP_PIN, GPIO_PIN_SET);    // 关闭
    }
}

/*********************** 系统时钟配置代码(略) ********************/

代码说明(改进版)

  1. 去结构体设计

    • 为每个按键单独定义变量:uiKey1DebounceCntucKey1Lock
    • 变量命名体现归属关系(Key1/Key2),便于初学者理解
  2. 按键扫描流程

    • 状态检测:读取当前GPIO电平
    • 变化检测:若状态变化则重置计数器
    • 稳定判断:持续20ms相同状态视为有效输入
    • 事件触发:设置事件标志并自锁
  3. 初学者友好设计

    • 所有变量全局可见,便于调试观察
    • 按键1和按键2处理逻辑完全独立,避免耦合
    • 无复杂数据结构,仅使用基本变量类型
  4. 扩展建议

    • 添加新按键时,只需复制代码块并修改"Key1"为"Key3"
    • 修改DEBOUNCE_TIME可调整去抖灵敏度
    • g_ucKeyEvent处理分支添加更多功能(如LED控制)

关键逻辑图示

定时中断(1ms)
  ↓
执行Key_Scan_Handler()
  ├─ 检测按键1状态变化
  │    ├─ 是 → 重置计数器
  │    └─ 否 → 累加计数器
  ├─ 判断是否稳定20ms
  │    ├─ 是 → 触发事件并自锁
  │    └─ 否 → 维持当前状态
  └─ 按键2执行相同流程

实测提示

  1. 按键抖动观察:可临时添加调试输出,打印uiKey1DebounceCnt值观察去抖过程
  2. 事件响应测试:按下按键时应看到g_ucKeyEvent标志被正确设置
  3. 蜂鸣器验证:触发时用示波器测量BEEP_PIN应有40ms低电平脉冲

你可能感兴趣的:(【嵌语匠言】,stm32,单片机,嵌入式硬件)