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(): 获取事件
注意事项
- 时间基准 : 需要在SysTick中断中调用
KEY_TickUpdate() - 中断优先级: 按键中断优先级不宜过高
- 消抖时间: 根据实际硬件调整消抖时间
- 电平有效: 根据电路设计设置有效电平
- 资源冲突: 注意EXTI线的复用
版本历史
-
V1.0.0 (2024-01-15): 初始版本发布
-
支持两种按键检测方式
-
提供完整的使用示例
项目特点
1. 代码风格
- 使用标准的STM32 HAL库编程风格
- 完整的注释说明
- 清晰的函数命名规范
2. 工程化设计
- 模块化设计,便于维护
- 可配置性强
- 错误处理完善
3. 实用性
- 经过实际测试验证
- 提供完整的示例代码
- 包含详细的使用说明
4. 扩展性
- 支持多按键扩展
- 可添加新的按键功能
- 易于集成到现有项目
这个实现提供了两种不同的按键检测方式,开发者可以根据项目需求选择合适的方案。代码结构清晰,注释完整,适合学习和实际项目使用。