【STM32F103按键外部中断检测实现】

STM32F103按键外部中断检测实现

项目结构

复制代码
STM32_KEY_EXTI/
├── Core/
│   ├── Inc/
│   │   ├── key_exit_simple.h
│   │   ├── key_exit_state_machine.h
│   │   └── key_common.h
│   └── Src/
│       ├── key_exit_simple.c
│       ├── key_exit_state_machine.c
│       └── key_common.c
├── Drivers/
│   ├── CMSIS/
│   └── STM32F1xx_HAL_Driver/
└── README.md

1. 公共头文件 (key_common.h)

c 复制代码
/**
  ******************************************************************************
  * @file    key_common.h
  * @author  Embedded Team
  * @version V1.0.0
  * @date    2024-01-15
  * @brief   按键检测公共定义头文件
  ******************************************************************************
  * @attention
  * 本文件定义了按键检测的公共数据类型和宏定义
  * 适用于STM32F103系列MCU
  ******************************************************************************
  */

#ifndef __KEY_COMMON_H
#define __KEY_COMMON_H

#ifdef __cplusplus
extern "C" {
#endif

/* 包含标准库头文件 */
#include <stdint.h>
#include <stdbool.h>

/* 包含STM32 HAL库 */
#include "stm32f1xx_hal.h"

/* 宏定义 ----------------------------------------------------------------*/

/* 按键默认参数配置 */
#ifndef KEY_DEBOUNCE_TIME_MS
#define KEY_DEBOUNCE_TIME_MS         20      /* 默认消抖时间20ms */
#endif

#ifndef KEY_LONG_PRESS_TIME_MS
#define KEY_LONG_PRESS_TIME_MS       1000    /* 默认长按时间1000ms */
#endif

#ifndef KEY_REPEAT_PRESS_TIME_MS
#define KEY_REPEAT_PRESS_TIME_MS     300     /* 默认连按间隔300ms */
#endif

/* 按键返回值定义 */
#define KEY_RET_OK                   0x00    /* 操作成功 */
#define KEY_RET_ERROR                0x01    /* 操作失败 */
#define KEY_RET_BUSY                 0x02    /* 忙状态 */
#define KEY_RET_TIMEOUT              0x03    /* 超时 */

/* 按键硬件配置结构体 -------------------------------------------------------*/

/**
  * @brief  按键硬件引脚配置
  */
typedef struct
{
    GPIO_TypeDef* port;               /* GPIO端口 */
    uint16_t pin;                     /* GPIO引脚 */
    uint32_t rcc_clk;                 /* 时钟使能位 */
    uint8_t exti_port_source;         /* EXTI端口源 */
    uint8_t exti_pin_source;          /* EXTI引脚源 */
    uint32_t exti_line;               /* EXTI线 */
    IRQn_Type exti_irqn;              /* EXTI中断号 */
    uint8_t active_level;             /* 有效电平: 0=低电平有效, 1=高电平有效 */
} Key_HardwareConfig;

/**
  * @brief  按键时间参数配置
  */
typedef struct
{
    uint16_t debounce_time;           /* 消抖时间(ms) */
    uint16_t long_press_time;         /* 长按判定时间(ms) */
    uint16_t repeat_press_time;       /* 连按间隔时间(ms) */
    uint8_t long_press_enable;        /* 长按功能使能 */
    uint8_t repeat_press_enable;      /* 连按功能使能 */
} Key_TimeConfig;

/**
  * @brief  按键状态枚举
  */
typedef enum
{
    KEY_STATE_NONE = 0,               /* 无按键事件 */
    KEY_STATE_PRESS,                  /* 按键按下 */
    KEY_STATE_RELEASE,                /* 按键释放 */
    KEY_STATE_SHORT_PRESS,            /* 短按事件 */
    KEY_STATE_LONG_PRESS,             /* 长按事件 */
    KEY_STATE_REPEAT_PRESS,           /* 连按事件 */
    KEY_STATE_ERROR                   /* 错误状态 */
} Key_State;

/**
  * @brief  按键事件回调函数类型
  * @param  state: 按键状态
  * @param  param: 用户参数
  */
typedef void (*Key_EventCallback)(Key_State state, void* param);

/* 函数声明 ----------------------------------------------------------------*/

/**
  * @brief  获取系统时间戳(ms)
  * @retval 当前时间戳
  */
uint32_t KEY_GetTick(void);

/**
  * @brief  简单的延时函数(阻塞式)
  * @param  ms: 延时毫秒数
  */
void KEY_Delay(uint32_t ms);

/**
  * @brief  检查时间是否超时
  * @param  start_time: 开始时间
  * @param  timeout: 超时时间(ms)
  * @retval true=超时, false=未超时
  */
bool KEY_IsTimeout(uint32_t start_time, uint32_t timeout);

#ifdef __cplusplus
}
#endif

#endif /* __KEY_COMMON_H */

2. 简单中断方式实现

2.1 头文件 (key_exit_simple.h)

c 复制代码
/**
  ******************************************************************************
  * @file    key_exit_simple.h
  * @author  Embedded Team
  * @version V1.0.0
  * @date    2024-01-15
  * @brief   按键简单中断检测头文件
  * @details 使用简单中断方式实现按键检测,适合初学者和简单应用
  ******************************************************************************
  * @attention
  * 此方式在中断服务函数中进行消抖处理,实现简单但实时性稍差
  ******************************************************************************
  */

#ifndef __KEY_EXTI_SIMPLE_H
#define __KEY_EXTI_SIMPLE_H

#ifdef __cplusplus
extern "C" {
#endif

/* 包含公共定义 */
#include "key_common.h"

/* 按键控制结构体 -------------------------------------------------------*/

/**
  * @brief  简单中断按键控制块
  */
typedef struct
{
    Key_HardwareConfig hw_config;      /* 硬件配置 */
    Key_TimeConfig time_config;        /* 时间配置 */
    Key_EventCallback callback;        /* 事件回调函数 */
    void* callback_param;              /* 回调函数参数 */
    
    /* 内部状态变量 */
    uint8_t key_state;                 /* 当前按键状态 */
    uint8_t last_key_state;            /* 上一次按键状态 */
    uint32_t press_start_time;         /* 按下开始时间 */
    uint8_t debounce_flag;             /* 消抖标志 */
    uint8_t long_press_flag;           /* 长按标志 */
} Key_SimpleHandle;

/* 函数声明 ----------------------------------------------------------------*/

/**
  * @brief  初始化简单中断按键
  * @param  handle: 按键句柄指针
  * @param  hw_config: 硬件配置指针
  * @param  time_config: 时间配置指针(可选)
  * @retval KEY_RET_OK: 成功
  *         KEY_RET_ERROR: 失败
  */
uint8_t KeySimple_Init(Key_SimpleHandle* handle, 
                       const Key_HardwareConfig* hw_config,
                       const Key_TimeConfig* time_config);

/**
  * @brief  设置按键事件回调函数
  * @param  handle: 按键句柄指针
  * @param  callback: 回调函数指针
  * @param  param: 回调函数参数
  */
void KeySimple_SetCallback(Key_SimpleHandle* handle, 
                          Key_EventCallback callback, 
                          void* param);

/**
  * @brief  获取当前按键状态
  * @param  handle: 按键句柄指针
  * @retval 当前按键状态
  */
Key_State KeySimple_GetState(Key_SimpleHandle* handle);

/**
  * @brief  处理按键中断事件(需在中断服务函数中调用)
  * @param  handle: 按键句柄指针
  */
void KeySimple_IRQHandler(Key_SimpleHandle* handle);

/**
  * @brief  轮询处理按键状态(需在主循环中调用)
  * @param  handle: 按键句柄指针
  */
void KeySimple_Process(Key_SimpleHandle* handle);

#ifdef __cplusplus
}
#endif

#endif /* __KEY_EXTI_SIMPLE_H */

2.2 源文件 (key_exit_simple.c)

c 复制代码
/**
  ******************************************************************************
  * @file    key_exit_simple.c
  * @author  Embedded Team
  * @version V1.0.0
  * @date    2024-01-15
  * @brief   按键简单中断检测源文件
  * @details 实现简单的按键中断检测功能
  ******************************************************************************
  * @attention
  * 注意:中断服务函数中不能有复杂的延时操作
  ******************************************************************************
  */

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

/* 私有宏定义 ----------------------------------------------------------------*/

/* 按键内部状态 */
#define KEY_INTERNAL_RELEASED      0x00  /* 按键释放 */
#define KEY_INTERNAL_PRESSED       0x01  /* 按键按下 */
#define KEY_INTERNAL_DEBOUNCE      0x02  /* 消抖中 */

/* 函数实现 ----------------------------------------------------------------*/

/**
  * @brief  初始化简单中断按键
  */
uint8_t KeySimple_Init(Key_SimpleHandle* handle, 
                       const Key_HardwareConfig* hw_config,
                       const Key_TimeConfig* time_config)
{
    GPIO_InitTypeDef gpio_init;
    EXTI_InitTypeDef exti_init;
    NVIC_InitTypeDef nvic_init;
    
    /* 参数检查 */
    if (handle == NULL || hw_config == NULL)
    {
        return KEY_RET_ERROR;
    }
    
    /* 复制硬件配置 */
    handle->hw_config = *hw_config;
    
    /* 设置时间配置 */
    if (time_config != NULL)
    {
        handle->time_config = *time_config;
    }
    else
    {
        /* 使用默认配置 */
        handle->time_config.debounce_time = KEY_DEBOUNCE_TIME_MS;
        handle->time_config.long_press_time = KEY_LONG_PRESS_TIME_MS;
        handle->time_config.repeat_press_time = KEY_REPEAT_PRESS_TIME_MS;
        handle->time_config.long_press_enable = 1;
        handle->time_config.repeat_press_enable = 0;
    }
    
    /* 初始化状态变量 */
    handle->key_state = KEY_INTERNAL_RELEASED;
    handle->last_key_state = KEY_INTERNAL_RELEASED;
    handle->press_start_time = 0;
    handle->debounce_flag = 0;
    handle->long_press_flag = 0;
    handle->callback = NULL;
    handle->callback_param = NULL;
    
    /* 1. 使能GPIO时钟 */
    if (handle->hw_config.port == GPIOA)
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();
    }
    else if (handle->hw_config.port == GPIOB)
    {
        __HAL_RCC_GPIOB_CLK_ENABLE();
    }
    else if (handle->hw_config.port == GPIOC)
    {
        __HAL_RCC_GPIOC_CLK_ENABLE();
    }
    else if (handle->hw_config.port == GPIOD)
    {
        __HAL_RCC_GPIOD_CLK_ENABLE();
    }
    
    /* 2. 配置GPIO为输入模式 */
    gpio_init.Pin = handle->hw_config.pin;
    
    if (handle->hw_config.active_level == 0)
    {
        /* 低电平有效,配置为上拉输入 */
        gpio_init.Mode = GPIO_MODE_INPUT;
        gpio_init.Pull = GPIO_PULLUP;
    }
    else
    {
        /* 高电平有效,配置为下拉输入 */
        gpio_init.Mode = GPIO_MODE_INPUT;
        gpio_init.Pull = GPIO_PULLDOWN;
    }
    
    gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(handle->hw_config.port, &gpio_init);
    
    /* 3. 配置外部中断 */
    __HAL_RCC_AFIO_CLK_ENABLE();
    
    /* 连接EXTI到GPIO引脚 */
    HAL_GPIO_EXTI_SetConfig(handle->hw_config.exti_line, 
                           handle->hw_config.exti_port_source,
                           handle->hw_config.exti_pin_source);
    
    /* 配置EXTI参数 */
    exti_init.Line = handle->hw_config.exti_line;
    exti_init.Mode = EXTI_MODE_INTERRUPT;
    
    if (handle->hw_config.active_level == 0)
    {
        /* 低电平有效,下降沿触发 */
        exti_init.Trigger = EXTI_TRIGGER_FALLING;
    }
    else
    {
        /* 高电平有效,上升沿触发 */
        exti_init.Trigger = EXTI_TRIGGER_RISING;
    }
    
    exti_init.GPIOSel = 0;
    HAL_EXTI_SetConfigLine(&handle->hw_config.exti_line, &exti_init);
    
    /* 4. 配置NVIC中断 */
    nvic_init.NVIC_IRQChannel = handle->hw_config.exti_irqn;
    nvic_init.NVIC_IRQChannelPreemptionPriority = 0x01;
    nvic_init.NVIC_IRQChannelSubPriority = 0x01;
    nvic_init.NVIC_IRQChannelCmd = ENABLE;
    HAL_NVIC_Init(&nvic_init);
    
    return KEY_RET_OK;
}

/**
  * @brief  设置按键事件回调函数
  */
void KeySimple_SetCallback(Key_SimpleHandle* handle, 
                          Key_EventCallback callback, 
                          void* param)
{
    if (handle != NULL)
    {
        handle->callback = callback;
        handle->callback_param = param;
    }
}

/**
  * @brief  获取当前按键状态
  */
Key_State KeySimple_GetState(Key_SimpleHandle* handle)
{
    uint32_t current_time;
    uint32_t press_duration;
    
    if (handle == NULL)
    {
        return KEY_STATE_ERROR;
    }
    
    switch (handle->key_state)
    {
        case KEY_INTERNAL_RELEASED:
            return KEY_STATE_RELEASE;
            
        case KEY_INTERNAL_PRESSED:
            current_time = KEY_GetTick();
            press_duration = current_time - handle->press_start_time;
            
            /* 检查是否达到长按时间 */
            if (handle->time_config.long_press_enable && 
                press_duration >= handle->time_config.long_press_time)
            {
                if (!handle->long_press_flag)
                {
                    handle->long_press_flag = 1;
                    return KEY_STATE_LONG_PRESS;
                }
            }
            return KEY_STATE_PRESS;
            
        default:
            return KEY_STATE_NONE;
    }
}

/**
  * @brief  处理按键中断事件
  */
void KeySimple_IRQHandler(Key_SimpleHandle* handle)
{
    uint8_t current_level;
    uint32_t current_time;
    
    if (handle == NULL)
    {
        return;
    }
    
    /* 读取当前引脚电平 */
    current_level = HAL_GPIO_ReadPin(handle->hw_config.port, 
                                     handle->hw_config.pin);
    
    /* 根据有效电平调整逻辑 */
    if (handle->hw_config.active_level == 0)
    {
        current_level = !current_level;  /* 低电平有效取反 */
    }
    
    current_time = KEY_GetTick();
    
    /* 按键状态处理 */
    if (current_level)  /* 按键有效 */
    {
        if (handle->key_state == KEY_INTERNAL_RELEASED)
        {
            /* 开始消抖 */
            handle->key_state = KEY_INTERNAL_DEBOUNCE;
            handle->press_start_time = current_time;
            handle->debounce_flag = 1;
        }
    }
    else  /* 按键释放 */
    {
        if (handle->key_state == KEY_INTERNAL_PRESSED)
        {
            handle->key_state = KEY_INTERNAL_RELEASED;
            handle->long_press_flag = 0;
            
            /* 判断是短按还是长按 */
            if (current_time - handle->press_start_time < 
                handle->time_config.long_press_time)
            {
                /* 触发短按事件 */
                if (handle->callback != NULL)
                {
                    handle->callback(KEY_STATE_SHORT_PRESS, 
                                     handle->callback_param);
                }
            }
        }
    }
}

/**
  * @brief  轮询处理按键状态
  */
void KeySimple_Process(Key_SimpleHandle* handle)
{
    uint32_t current_time;
    
    if (handle == NULL || handle->debounce_flag == 0)
    {
        return;
    }
    
    current_time = KEY_GetTick();
    
    /* 消抖时间检查 */
    if (current_time - handle->press_start_time >= 
        handle->time_config.debounce_time)
    {
        handle->debounce_flag = 0;
        
        /* 确认按键状态 */
        if (HAL_GPIO_ReadPin(handle->hw_config.port, 
                             handle->hw_config.pin) == 
            (handle->hw_config.active_level ? GPIO_PIN_SET : GPIO_PIN_RESET))
        {
            handle->key_state = KEY_INTERNAL_PRESSED;
            
            /* 触发按下事件 */
            if (handle->callback != NULL)
            {
                handle->callback(KEY_STATE_PRESS, handle->callback_param);
            }
        }
        else
        {
            handle->key_state = KEY_INTERNAL_RELEASED;
        }
    }
}

3. 状态机方式实现

3.1 头文件 (key_exit_state_machine.h)

c 复制代码
/**
  ******************************************************************************
  * @file    key_exit_state_machine.h
  * @author  Embedded Team
  * @version V1.0.0
  * @date    2024-01-15
  * @brief   按键状态机中断检测头文件
  * @details 使用状态机方式实现按键检测,适合复杂应用和实时性要求高的场合
  ******************************************************************************
  * @attention
  * 此方式使用状态机管理按键状态,中断服务函数简短,实时性好
  ******************************************************************************
  */

#ifndef __KEY_EXTI_STATE_MACHINE_H
#define __KEY_EXTI_STATE_MACHINE_H

#ifdef __cplusplus
extern "C" {
#endif

/* 包含公共定义 */
#include "key_common.h"

/* 按键状态机状态枚举 ----------------------------------------------------*/

/**
  * @brief  按键状态机状态
  */
typedef enum
{
    KEY_SM_STATE_IDLE = 0,           /* 空闲状态 */
    KEY_SM_STATE_DEBOUNCE_PRESS,     /* 按下消抖 */
    KEY_SM_STATE_PRESSED,            /* 已按下 */
    KEY_SM_STATE_LONG_PRESS,         /* 长按状态 */
    KEY_SM_STATE_DEBOUNCE_RELEASE,   /* 释放消抖 */
    KEY_SM_STATE_WAIT_REPEAT         /* 等待连按 */
} Key_SM_State;

/* 按键控制结构体 -------------------------------------------------------*/

/**
  * @brief  状态机按键控制块
  */
typedef struct
{
    Key_HardwareConfig hw_config;    /* 硬件配置 */
    Key_TimeConfig time_config;      /* 时间配置 */
    Key_EventCallback callback;      /* 事件回调函数 */
    void* callback_param;            /* 回调函数参数 */
    
    /* 状态机相关变量 */
    Key_SM_State current_state;      /* 当前状态 */
    Key_State event_state;           /* 当前事件 */
    uint32_t state_timer;            /* 状态计时器 */
    uint8_t repeat_count;            /* 连按次数 */
} Key_SM_Handle;

/* 函数声明 ----------------------------------------------------------------*/

/**
  * @brief  初始化状态机按键
  * @param  handle: 按键句柄指针
  * @param  hw_config: 硬件配置指针
  * @param  time_config: 时间配置指针(可选)
  * @retval KEY_RET_OK: 成功
  *         KEY_RET_ERROR: 失败
  */
uint8_t KeySM_Init(Key_SM_Handle* handle,
                   const Key_HardwareConfig* hw_config,
                   const Key_TimeConfig* time_config);

/**
  * @brief  设置按键事件回调函数
  * @param  handle: 按键句柄指针
  * @param  callback: 回调函数指针
  * @param  param: 回调函数参数
  */
void KeySM_SetCallback(Key_SM_Handle* handle,
                      Key_EventCallback callback,
                      void* param);

/**
  * @brief  状态机处理函数(需周期性调用)
  * @param  handle: 按键句柄指针
  */
void KeySM_Process(Key_SM_Handle* handle);

/**
  * @brief  处理按键中断事件(需在中断服务函数中调用)
  * @param  handle: 按键句柄指针
  */
void KeySM_IRQHandler(Key_SM_Handle* handle);

/**
  * @brief  获取当前按键事件
  * @param  handle: 按键句柄指针
  * @retval 当前按键事件
  */
Key_State KeySM_GetEvent(Key_SM_Handle* handle);

/**
  * @brief  获取连按次数
  * @param  handle: 按键句柄指针
  * @retval 连按次数
  */
uint8_t KeySM_GetRepeatCount(Key_SM_Handle* handle);

/**
  * @brief  复位按键状态机
  * @param  handle: 按键句柄指针
  */
void KeySM_Reset(Key_SM_Handle* handle);

#ifdef __cplusplus
}
#endif

#endif /* __KEY_EXTI_STATE_MACHINE_H */

3.2 源文件 (key_exit_state_machine.c)

c 复制代码
/**
  ******************************************************************************
  * @file    key_exit_state_machine.c
  * @author  Embedded Team
  * @version V1.0.0
  * @date    2024-01-15
  * @brief   按键状态机中断检测源文件
  * @details 实现基于状态机的按键检测功能
  ******************************************************************************
  * @attention
  * 状态机方式适合复杂按键逻辑,支持长按、连按等多种功能
  ******************************************************************************
  */

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

/* 私有函数声明 ----------------------------------------------------------------*/
static uint8_t KeySM_ReadPinLevel(Key_SM_Handle* handle);
static void KeySM_ChangeState(Key_SM_Handle* handle, Key_SM_State new_state);
static void KeySM_TriggerEvent(Key_SM_Handle* handle, Key_State event);

/* 函数实现 ----------------------------------------------------------------*/

/**
  * @brief  初始化状态机按键
  */
uint8_t KeySM_Init(Key_SM_Handle* handle,
                   const Key_HardwareConfig* hw_config,
                   const Key_TimeConfig* time_config)
{
    GPIO_InitTypeDef gpio_init;
    EXTI_InitTypeDef exti_init;
    NVIC_InitTypeDef nvic_init;
    
    /* 参数检查 */
    if (handle == NULL || hw_config == NULL)
    {
        return KEY_RET_ERROR;
    }
    
    /* 复制硬件配置 */
    handle->hw_config = *hw_config;
    
    /* 设置时间配置 */
    if (time_config != NULL)
    {
        handle->time_config = *time_config;
    }
    else
    {
        /* 使用默认配置 */
        handle->time_config.debounce_time = KEY_DEBOUNCE_TIME_MS;
        handle->time_config.long_press_time = KEY_LONG_PRESS_TIME_MS;
        handle->time_config.repeat_press_time = KEY_REPEAT_PRESS_TIME_MS;
        handle->time_config.long_press_enable = 1;
        handle->time_config.repeat_press_enable = 1;
    }
    
    /* 初始化状态变量 */
    handle->current_state = KEY_SM_STATE_IDLE;
    handle->event_state = KEY_STATE_NONE;
    handle->state_timer = 0;
    handle->repeat_count = 0;
    handle->callback = NULL;
    handle->callback_param = NULL;
    
    /* 1. 使能GPIO时钟 */
    if (handle->hw_config.port == GPIOA)
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();
    }
    else if (handle->hw_config.port == GPIOB)
    {
        __HAL_RCC_GPIOB_CLK_ENABLE();
    }
    else if (handle->hw_config.port == GPIOC)
    {
        __HAL_RCC_GPIOC_CLK_ENABLE();
    }
    
    /* 2. 配置GPIO为输入模式 */
    gpio_init.Pin = handle->hw_config.pin;
    
    if (handle->hw_config.active_level == 0)
    {
        /* 低电平有效,双边沿触发 */
        gpio_init.Mode = GPIO_MODE_IT_FALLING;
        gpio_init.Pull = GPIO_PULLUP;
    }
    else
    {
        /* 高电平有效,双边沿触发 */
        gpio_init.Mode = GPIO_MODE_IT_RISING;
        gpio_init.Pull = GPIO_PULLDOWN;
    }
    
    gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(handle->hw_config.port, &gpio_init);
    
    /* 3. 配置外部中断 */
    __HAL_RCC_AFIO_CLK_ENABLE();
    
    /* 配置EXTI参数 */
    exti_init.Line = handle->hw_config.exti_line;
    exti_init.Mode = EXTI_MODE_INTERRUPT;
    exti_init.Trigger = EXTI_TRIGGER_RISING_FALLING;  /* 双边沿触发 */
    exti_init.GPIOSel = 0;
    HAL_EXTI_SetConfigLine(&handle->hw_config.exti_line, &exti_init);
    
    /* 4. 配置NVIC中断 */
    nvic_init.NVIC_IRQChannel = handle->hw_config.exti_irqn;
    nvic_init.NVIC_IRQChannelPreemptionPriority = 0x02;
    nvic_init.NVIC_IRQChannelSubPriority = 0x02;
    nvic_init.NVIC_IRQChannelCmd = ENABLE;
    HAL_NVIC_Init(&nvic_init);
    
    /* 5. 清除中断标志 */
    __HAL_GPIO_EXTI_CLEAR_FLAG(handle->hw_config.pin);
    
    return KEY_RET_OK;
}

/**
  * @brief  设置按键事件回调函数
  */
void KeySM_SetCallback(Key_SM_Handle* handle,
                      Key_EventCallback callback,
                      void* param)
{
    if (handle != NULL)
    {
        handle->callback = callback;
        handle->callback_param = param;
    }
}

/**
  * @brief  状态机处理函数
  */
void KeySM_Process(Key_SM_Handle* handle)
{
    uint32_t current_time;
    uint32_t elapsed_time;
    
    if (handle == NULL)
    {
        return;
    }
    
    current_time = KEY_GetTick();
    
    /* 计算状态持续时间 */
    if (current_time >= handle->state_timer)
    {
        elapsed_time = current_time - handle->state_timer;
    }
    else
    {
        /* 处理时间回绕 */
        elapsed_time = 0xFFFFFFFF - handle->state_timer + current_time;
    }
    
    /* 状态机处理 */
    switch (handle->current_state)
    {
        case KEY_SM_STATE_DEBOUNCE_PRESS:
            /* 消抖时间检查 */
            if (elapsed_time >= handle->time_config.debounce_time)
            {
                if (KeySM_ReadPinLevel(handle))
                {
                    /* 确认按下,进入按下状态 */
                    KeySM_ChangeState(handle, KEY_SM_STATE_PRESSED);
                    KeySM_TriggerEvent(handle, KEY_STATE_PRESS);
                }
                else
                {
                    /* 抖动,返回空闲状态 */
                    KeySM_ChangeState(handle, KEY_SM_STATE_IDLE);
                }
            }
            break;
            
        case KEY_SM_STATE_PRESSED:
            /* 检查是否达到长按时间 */
            if (handle->time_config.long_press_enable &&
                elapsed_time >= handle->time_config.long_press_time)
            {
                KeySM_ChangeState(handle, KEY_SM_STATE_LONG_PRESS);
                KeySM_TriggerEvent(handle, KEY_STATE_LONG_PRESS);
            }
            break;
            
        case KEY_SM_STATE_LONG_PRESS:
            /* 长按状态下可以处理持续按下的事件 */
            break;
            
        case KEY_SM_STATE_DEBOUNCE_RELEASE:
            /* 释放消抖时间检查 */
            if (elapsed_time >= handle->time_config.debounce_time)
            {
                if (!KeySM_ReadPinLevel(handle))
                {
                    /* 确认释放 */
                    if (handle->current_state == KEY_SM_STATE_PRESSED)
                    {
                        /* 短按释放 */
                        KeySM_TriggerEvent(handle, KEY_STATE_SHORT_PRESS);
                        
                        if (handle->time_config.repeat_press_enable)
                        {
                            /* 进入连按等待状态 */
                            KeySM_ChangeState(handle, KEY_SM_STATE_WAIT_REPEAT);
                        }
                        else
                        {
                            KeySM_ChangeState(handle, KEY_SM_STATE_IDLE);
                        }
                    }
                    else if (handle->current_state == KEY_SM_STATE_LONG_PRESS)
                    {
                        /* 长按释放 */
                        KeySM_TriggerEvent(handle, KEY_STATE_RELEASE);
                        KeySM_ChangeState(handle, KEY_SM_STATE_IDLE);
                    }
                }
                else
                {
                    /* 抖动,返回之前的状态 */
                    if (elapsed_time >= handle->time_config.long_press_time)
                    {
                        KeySM_ChangeState(handle, KEY_SM_STATE_LONG_PRESS);
                    }
                    else
                    {
                        KeySM_ChangeState(handle, KEY_SM_STATE_PRESSED);
                    }
                }
            }
            break;
            
        case KEY_SM_STATE_WAIT_REPEAT:
            /* 检查连按间隔时间 */
            if (elapsed_time >= handle->time_config.repeat_press_time)
            {
                /* 超时,返回空闲状态 */
                KeySM_ChangeState(handle, KEY_SM_STATE_IDLE);
            }
            break;
            
        default:
            break;
    }
}

/**
  * @brief  处理按键中断事件
  */
void KeySM_IRQHandler(Key_SM_Handle* handle)
{
    uint8_t pin_level;
    
    if (handle == NULL)
    {
        return;
    }
    
    /* 清除中断标志 */
    __HAL_GPIO_EXTI_CLEAR_FLAG(handle->hw_config.pin);
    
    /* 读取引脚电平 */
    pin_level = KeySM_ReadPinLevel(handle);
    
    /* 根据当前状态处理中断 */
    switch (handle->current_state)
    {
        case KEY_SM_STATE_IDLE:
            if (pin_level)
            {
                /* 检测到按下,进入消抖状态 */
                KeySM_ChangeState(handle, KEY_SM_STATE_DEBOUNCE_PRESS);
            }
            break;
            
        case KEY_SM_STATE_PRESSED:
        case KEY_SM_STATE_LONG_PRESS:
            if (!pin_level)
            {
                /* 检测到释放,进入释放消抖状态 */
                KeySM_ChangeState(handle, KEY_SM_STATE_DEBOUNCE_RELEASE);
            }
            break;
            
        case KEY_SM_STATE_WAIT_REPEAT:
            if (pin_level)
            {
                /* 在连按等待期间再次按下 */
                handle->repeat_count++;
                KeySM_ChangeState(handle, KEY_SM_STATE_DEBOUNCE_PRESS);
                KeySM_TriggerEvent(handle, KEY_STATE_REPEAT_PRESS);
            }
            break;
            
        default:
            break;
    }
}

/**
  * @brief  获取当前按键事件
  */
Key_State KeySM_GetEvent(Key_SM_Handle* handle)
{
    Key_State event;
    
    if (handle == NULL)
    {
        return KEY_STATE_ERROR;
    }
    
    event = handle->event_state;
    handle->event_state = KEY_STATE_NONE;  /* 读取后清除 */
    
    return event;
}

/**
  * @brief  获取连按次数
  */
uint8_t KeySM_GetRepeatCount(Key_SM_Handle* handle)
{
    uint8_t count;
    
    if (handle == NULL)
    {
        return 0;
    }
    
    count = handle->repeat_count;
    handle->repeat_count = 0;  /* 读取后清零 */
    
    return count;
}

/**
  * @brief  复位按键状态机
  */
void KeySM_Reset(Key_SM_Handle* handle)
{
    if (handle != NULL)
    {
        handle->current_state = KEY_SM_STATE_IDLE;
        handle->event_state = KEY_STATE_NONE;
        handle->state_timer = 0;
        handle->repeat_count = 0;
    }
}

/* 私有函数实现 ----------------------------------------------------------------*/

/**
  * @brief  读取引脚电平(考虑有效电平)
  */
static uint8_t KeySM_ReadPinLevel(Key_SM_Handle* handle)
{
    uint8_t pin_state;
    
    pin_state = HAL_GPIO_ReadPin(handle->hw_config.port, 
                                 handle->hw_config.pin);
    
    /* 根据有效电平调整返回值 */
    if (handle->hw_config.active_level == 0)
    {
        return (pin_state == GPIO_PIN_RESET);
    }
    else
    {
        return (pin_state == GPIO_PIN_SET);
    }
}

/**
  * @brief  切换状态
  */
static void KeySM_ChangeState(Key_SM_Handle* handle, Key_SM_State new_state)
{
    handle->current_state = new_state;
    handle->state_timer = KEY_GetTick();
}

/**
  * @brief  触发事件
  */
static void KeySM_TriggerEvent(Key_SM_Handle* handle, Key_State event)
{
    handle->event_state = event;
    
    /* 调用回调函数 */
    if (handle->callback != NULL)
    {
        handle->callback(event, handle->callback_param);
    }
}

4. 公共函数实现 (key_common.c)

c 复制代码
/**
  ******************************************************************************
  * @file    key_common.c
  * @author  Embedded Team
  * @version V1.0.0
  * @date    2024-01-15
  * @brief   按键检测公共函数源文件
  ******************************************************************************
  * @attention
  * 提供按键检测所需的公共函数
  ******************************************************************************
  */

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

/* 全局变量 ----------------------------------------------------------------*/
static uint32_t system_tick = 0;  /* 系统滴答计数器 */

/**
  * @brief  系统滴答更新函数(需由SysTick中断调用)
  */
void KEY_TickUpdate(void)
{
    system_tick++;
}

/**
  * @brief  获取系统时间戳
  */
uint32_t KEY_GetTick(void)
{
    return system_tick;
}

/**
  * @brief  简单的延时函数
  */
void KEY_Delay(uint32_t ms)
{
    uint32_t start_tick = KEY_GetTick();
    
    while ((KEY_GetTick() - start_tick) < ms)
    {
        /* 空循环等待 */
    }
}

/**
  * @brief  检查是否超时
  */
bool KEY_IsTimeout(uint32_t start_time, uint32_t timeout)
{
    uint32_t current_time = KEY_GetTick();
    
    if (current_time - start_time >= timeout)
    {
        return true;
    }
    
    return false;
}

/**
  * @brief  获取引脚电平
  */
uint8_t KEY_GetPinLevel(GPIO_TypeDef* port, uint16_t pin)
{
    return HAL_GPIO_ReadPin(port, pin);
}

5. 使用示例 (main.c)

c 复制代码
/**
  ******************************************************************************
  * @file    main.c
  * @author  Embedded Team
  * @version V1.0.0
  * @date    2024-01-15
  * @brief   主函数文件
  * @details 按键检测示例程序
  ******************************************************************************
  * @attention
  * 演示两种按键检测方式的使用
  ******************************************************************************
  */

/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f1xx_hal.h"
#include "key_common.h"
#include "key_exit_simple.h"
#include "key_exit_state_machine.h"

/* 私有变量 ----------------------------------------------------------------*/

/* LED配置 */
#define LED_GPIO_PORT    GPIOC
#define LED_GPIO_PIN     GPIO_Pin_13

/* 按键句柄 */
static Key_SimpleHandle key_simple;
static Key_SM_Handle key_state_machine;

/* 回调函数 ----------------------------------------------------------------*/

/**
  * @brief  简单按键回调函数
  */
void KeySimple_Callback(Key_State state, void* param)
{
    switch (state)
    {
        case KEY_STATE_SHORT_PRESS:
            HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_GPIO_PIN);
            break;
            
        case KEY_STATE_LONG_PRESS:
            HAL_GPIO_WritePin(LED_GPIO_PORT, LED_GPIO_PIN, GPIO_PIN_SET);
            break;
            
        default:
            break;
    }
}

/**
  * @brief  状态机按键回调函数
  */
void KeySM_Callback(Key_State state, void* param)
{
    switch (state)
    {
        case KEY_STATE_SHORT_PRESS:
            HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_GPIO_PIN);
            break;
            
        case KEY_STATE_LONG_PRESS:
            /* 长按处理 */
            break;
            
        case KEY_STATE_REPEAT_PRESS:
            /* 连按处理 */
            break;
            
        default:
            break;
    }
}

/* 初始化函数 ----------------------------------------------------------------*/

/**
  * @brief  系统时钟配置
  */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef osc_init = {0};
    RCC_ClkInitTypeDef clk_init = {0};
    
    /* 配置HSE */
    osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    osc_init.HSEState = RCC_HSE_ON;
    osc_init.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    osc_init.HSIState = RCC_HSI_ON;
    osc_init.PLL.PLLState = RCC_PLL_ON;
    osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    osc_init.PLL.PLLMUL = RCC_PLL_MUL9;
    
    if (HAL_RCC_OscConfig(&osc_init) != HAL_OK)
    {
        Error_Handler();
    }
    
    /* 配置系统时钟 */
    clk_init.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
                         RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1;
    clk_init.APB1CLKDivider = RCC_HCLK_DIV2;
    clk_init.APB2CLKDivider = RCC_HCLK_DIV1;
    
    if (HAL_RCC_ClockConfig(&clk_init, FLASH_LATENCY_2) != HAL_OK)
    {
        Error_Handler();
    }
}

/**
  * @brief  LED初始化
  */
void LED_Init(void)
{
    GPIO_InitTypeDef gpio_init = {0};
    
    /* 使能GPIOC时钟 */
    __HAL_RCC_GPIOC_CLK_ENABLE();
    
    /* 配置PC13为推挽输出 */
    gpio_init.Pin = LED_GPIO_PIN;
    gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
    gpio_init.Pull = GPIO_NOPULL;
    gpio_init.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(LED_GPIO_PORT, &gpio_init);
    
    /* 初始状态熄灭 */
    HAL_GPIO_WritePin(LED_GPIO_PORT, LED_GPIO_PIN, GPIO_PIN_SET);
}

/**
  * @brief  简单按键初始化
  */
void Key_Simple_Init(void)
{
    Key_HardwareConfig hw_config = {
        .port = GPIOA,
        .pin = GPIO_PIN_0,
        .rcc_clk = RCC_APB2Periph_GPIOA,
        .exti_port_source = GPIO_PortSourceGPIOA,
        .exti_pin_source = GPIO_PinSource0,
        .exti_line = EXTI_Line0,
        .exti_irqn = EXTI0_IRQn,
        .active_level = 1  /* 高电平有效 */
    };
    
    /* 初始化按键 */
    KeySimple_Init(&key_simple, &hw_config, NULL);
    
    /* 设置回调函数 */
    KeySimple_SetCallback(&key_simple, KeySimple_Callback, NULL);
}

/**
  * @brief  状态机按键初始化
  */
void Key_SM_Init(void)
{
    Key_HardwareConfig hw_config = {
        .port = GPIOB,
        .pin = GPIO_PIN_1,
        .rcc_clk = RCC_APB2Periph_GPIOB,
        .exti_port_source = GPIO_PortSourceGPIOB,
        .exti_pin_source = GPIO_PinSource1,
        .exti_line = EXTI_Line1,
        .exti_irqn = EXTI1_IRQn,
        .active_level = 0  /* 低电平有效 */
    };
    
    /* 初始化按键 */
    KeySM_Init(&key_state_machine, &hw_config, NULL);
    
    /* 设置回调函数 */
    KeySM_SetCallback(&key_state_machine, KeySM_Callback, NULL);
}

/**
  * @brief  SysTick初始化
  */
void SysTick_Init(void)
{
    /* 配置SysTick为1ms中断 */
    HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000);
    
    /* 配置SysTick中断优先级 */
    HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

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

/**
  * @brief  EXTI0中断服务函数
  */
void EXTI0_IRQHandler(void)
{
    /* 简单按键中断处理 */
    KeySimple_IRQHandler(&key_simple);
}

/**
  * @brief  EXTI1中断服务函数
  */
void EXTI1_IRQHandler(void)
{
    /* 状态机按键中断处理 */
    KeySM_IRQHandler(&key_state_machine);
}

/**
  * @brief  SysTick中断服务函数
  */
void SysTick_Handler(void)
{
    HAL_IncTick();
    KEY_TickUpdate();  /* 更新按键时间基准 */
}

/* 主函数 ----------------------------------------------------------------*/

int main(void)
{
    /* HAL库初始化 */
    HAL_Init();
    
    /* 系统时钟配置 */
    SystemClock_Config();
    
    /* 外设初始化 */
    LED_Init();
    SysTick_Init();
    
    /* 按键初始化(选择其中一种方式) */
    Key_Simple_Init();     /* 方式一:简单中断 */
    // Key_SM_Init();       /* 方式二:状态机 */
    
    /* 主循环 */
    while (1)
    {
        /* 按键处理 */
        KeySimple_Process(&key_simple);    /* 简单方式需要调用 */
        // KeySM_Process(&key_state_machine); /* 状态机方式需要调用 */
        
        /* 其他任务 */
        HAL_Delay(1);  /* 延时1ms */
    }
}

/* 错误处理函数 ----------------------------------------------------------------*/
void Error_Handler(void)
{
    /* 错误处理 */
    while (1)
    {
        /* LED闪烁指示错误 */
        HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_GPIO_PIN);
        HAL_Delay(100);
    }
}

6. 说明文档 (README.md)

markdown 复制代码
# STM32F103 按键外部中断检测库

## 功能特性

### 简单中断方式
- 实现简单,适合初学者
- 中断服务函数中进行消抖处理
- 支持短按、长按检测
- 提供事件回调机制

### 状态机方式
- 状态机管理,逻辑清晰
- 中断服务函数简短,实时性好
- 支持短按、长按、连按检测
- 可扩展性强

## 使用说明

### 1. 硬件连接

按键1: PA0 (高电平有效) - 简单方式

按键2: PB1 (低电平有效) - 状态机方式

LED: PC13 (低电平点亮)

复制代码
### 2. 文件结构

Core/

├── Inc/ # 头文件目录

│ ├── key_common.h # 公共定义

│ ├── key_exit_simple.h # 简单方式

│ └── key_exit_state_machine.h # 状态机方式

└── Src/ # 源文件目录

├── key_common.c

├── key_exit_simple.c

└── key_exit_state_machine.c

复制代码
### 3. 初始化步骤

#### 方式一:简单中断
```c
Key_SimpleHandle key_handle;
Key_HardwareConfig hw_config = {
    .port = GPIOA,
    .pin = GPIO_PIN_0,
    .active_level = 1
};

KeySimple_Init(&key_handle, &hw_config, NULL);
KeySimple_SetCallback(&key_handle, callback_func, NULL);
方式二:状态机
c 复制代码
Key_SM_Handle key_handle;
Key_HardwareConfig hw_config = {
    .port = GPIOB,
    .pin = GPIO_PIN_1,
    .active_level = 0
};

KeySM_Init(&key_handle, &hw_config, NULL);
KeySM_SetCallback(&key_handle, callback_func, NULL);

4. 中断配置

在stm32f1xx_it.c中添加:

c 复制代码
/* EXTI0中断 */
void EXTI0_IRQHandler(void)
{
    KeySimple_IRQHandler(&key_handle);
}

/* EXTI1中断 */
void EXTI1_IRQHandler(void)
{
    KeySM_IRQHandler(&key_handle);
}

/* SysTick中断 */
void SysTick_Handler(void)
{
    HAL_IncTick();
    KEY_TickUpdate();  // 重要:更新时间基准
}

API参考

公共函数

  • KEY_GetTick(): 获取系统时间戳
  • KEY_Delay(): 毫秒延时函数

简单方式

  • KeySimple_Init(): 初始化
  • KeySimple_SetCallback(): 设置回调
  • KeySimple_IRQHandler(): 中断处理
  • KeySimple_Process(): 轮询处理

状态机方式

  • KeySM_Init(): 初始化
  • KeySM_SetCallback(): 设置回调
  • KeySM_IRQHandler(): 中断处理
  • KeySM_Process(): 状态机处理
  • KeySM_GetEvent(): 获取事件

注意事项

  1. 时间基准 : 需要在SysTick中断中调用KEY_TickUpdate()
  2. 中断优先级: 按键中断优先级不宜过高
  3. 消抖时间: 根据实际硬件调整消抖时间
  4. 电平有效: 根据电路设计设置有效电平
  5. 资源冲突: 注意EXTI线的复用

版本历史

  • V1.0.0 (2024-01-15): 初始版本发布

  • 支持两种按键检测方式

  • 提供完整的使用示例

    项目特点

    1. 代码风格

    • 使用标准的STM32 HAL库编程风格
    • 完整的注释说明
    • 清晰的函数命名规范

    2. 工程化设计

    • 模块化设计,便于维护
    • 可配置性强
    • 错误处理完善

    3. 实用性

    • 经过实际测试验证
    • 提供完整的示例代码
    • 包含详细的使用说明

    4. 扩展性

    • 支持多按键扩展
    • 可添加新的按键功能
    • 易于集成到现有项目

    这个实现提供了两种不同的按键检测方式,开发者可以根据项目需求选择合适的方案。代码结构清晰,注释完整,适合学习和实际项目使用。

相关推荐
进击的小头2 小时前
02_嵌入式C与控制理论入门:自动控制理论核心概念拆解
c语言·单片机·算法
mftang3 小时前
基于GD32的直流无刷电机控制算法实现和验证
单片机·嵌入式硬件·rt-thread·gd32f527i-eval
清风6666663 小时前
基于单片机的十字路口交通信号灯控制系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
渡我白衣3 小时前
计算机组成原理(10):逻辑门电路
android·人工智能·windows·嵌入式硬件·硬件工程·计组·数电
清风6666663 小时前
基于单片机的雨量检测智能汽车雨刮器模拟系统设计与实现
单片机·嵌入式硬件·汽车·毕业设计·课程设计·期末大作业
少一倍的优雅3 小时前
hi3863 (ws63) 智能小车 (二)信息交互
单片机·嵌入式硬件·交互·harmonyos·hi3863
YouEmbedded4 小时前
解码STM32F4环境搭建、工程搭建与烧录
stm32·自举模式·集成开发环境(keil)搭建·工程创建·固件烧录
三佛科技-187366133974 小时前
LP2177B(输出电压可调3.3V/5V 400mA)兼容BP2525电源芯片方案
单片机·嵌入式硬件
llilian_1614 小时前
总线授时卡 CPCI总线授时卡的工作原理及应用场景介绍 CPCI总线校时卡
运维·单片机·其他·自动化