长按与短按按键检测程序与及用回调形式写个LED闪烁程序:
提示:代码未经过上机测试
代码实现了通过中断按键长按短按判定(无需延时)
并用三木运算符实现LED闪烁程序,
通过设置回调函数,可以实现类似接口的效果,
从而隐藏具体实现细节,实现代码的复用和隐藏。
#include "stm32f1xx.h"
#define BUTTON_PIN GPIO_PIN_0 // 按键引脚
#define LED_PIN GPIO_PIN_13 // LED引脚
/*
//如果func是CallbackFunction类型的函数指针,则IS_FUNCTION返回1,否则返回0
//其中_Generic是C语言中的一个泛型选择表达式。它允许根据表达式的类型在编译时选择不同的代码分支。通常,它用于实现泛型宏,以根据传入参数的类型选择不同的处理方式。
*/
#define IS_FUNCTION(func) _Generic((func), \
CallbackFunction: 1, \
default: 0 \
)
// 回调函数类型
typedef void (*CallbackFunction)();
// 回调函数指针
volatile CallbackFunction callback;
// 定义按键可能的几种状态,包括释放、可能按下等状态。
typedef enum {
RELEASE, // 按键释放状态
MAY_PRESS // 可能按下状态
} ButtonState;
// 定义按键按下可能的几种状态,短按、长按等状态。
typedef enum {
NULL_PRESS, //无操作状态
SHORT_PRESS, // 短按状态
LONG_PRESS // 长按状态
} ButtonRressState;
// 定义闪烁开关几种状态,开,关状态。
typedef enum {
ENABLE, //开启状态
DISABLE // 关闭状态
} Blink_flag ;
// 定义按键状态变量,用于记录当前按键状态,默认为释放状态。
volatile ButtonState buttonState = RELEASE;
// 定义按键按下状态变量,用于记录当前按键状态,默认为无操作状态。
volatile ButtonState buttonState = NULL_PRESS;
// 定义闪烁开关几种状态,开,关状态。默认为开启状态。
volatile Blink_flag blink_flag = ENABLE;
// 定义变量,用于记录上一次按键触发时间,初始值为0。
volatile uint32_t lastKeyPressTime = 0;
// 定义按键消抖延迟时间,单位为毫秒。
const uint32_t debounceDelay = 10;
// 定义短按阈值,单位为毫秒。
const uint32_t shortPressThreshold = 1000;
// 定义长按阈值,单位为毫秒。
const uint32_t longPressThreshold = 3000;
// 定义闪烁时间,单位为毫秒。
volatile uint32_t Blink_Time = 1000;
// LED初始化函数
void LED_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
// 使能 GPIOC 时钟
__HAL_RCC_GPIOC_CLK_ENABLE();
// 配置 LED 引脚
GPIO_InitStruct.Pin = LED_PIN; // LED引脚
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 输出模式,推挽输出
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // GPIO速度为低速
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); // 初始化GPIOC
}
// 中断配置函数
void EXTI0_IRQHandler_Config(void) {
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// 使能 GPIOA 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
// 配置按键引脚
GPIO_InitStruct.Pin = BUTTON_PIN; // 按键引脚
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING; // 输入模式,边沿触发
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉电阻
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA
// 配置外部中断线
EXTI_InitStruct.Line = EXTI_LINE_0; // 外部中断线0
EXTI_InitStruct.Mode = EXTI_MODE_INTERRUPT; // 中断模式
EXTI_InitStruct.Trigger = EXTI_TRIGGER_RISING_FALLING; // 边沿触发
EXTI_InitStruct.Pull = EXTI_PULLUP; // 上拉电阻
HAL_EXTI_SetConfigLine(&EXTI_InitStruct); // 配置外部中断线
// 配置中断优先级
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; // 中断通道
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道
HAL_NVIC_Init(&NVIC_InitStruct); // 初始化NVIC
}
// 系统时钟配置函数
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;
// 初始化 RCC 并且配置时钟源
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 时钟源为外部晶振
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 启用外部晶振
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; // 外部晶振预分频1
RCC_OscInitStruct.Prediv1Source = RCC_PREDIV1_SOURCE_PLL2; // PLL2源选择
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // PLL使能
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL时钟源选择外部晶振
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // PLL倍频因子9
HAL_RCC_OscConfig(&RCC_OscInitStruct); // 初始化时钟配置
// 初始化 CPU 时钟,AHB 时钟,APB1 时钟和 APB2 时钟
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 系统时钟源选择PLL
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB时钟分频1
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // APB1时钟分频2
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2时钟分频1
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); // 初始化时钟配置
}
// 按键中断处理函数,当按键引脚触发中断时会被调用。
void EXTI0_IRQHandler(void) {
// 长按短按判断处理
checkButtonState ();
// 清除中断标志位
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
// 长按短按判断处理
void checkButtonState () {
// 获取当前时间
uint32_t currentTime = HAL_GetTick();
// 处理状态转换和按键消抖
switch (buttonState) {
case RELEASE:
// 如果按键被按下,切换到可能按下状态,并记录按键按下时间
if (HAL_GPIO_ReadPin(GPIOA, BUTTON_PIN) == GPIO_PIN_RESET) {
buttonState = MAY_PRESS;
lastKeyPressTime = currentTime;
}
break;
case MAY_PRESS:
// 如果按键被释放,切换回释放状态
if (HAL_GPIO_ReadPin(GPIOA, BUTTON_PIN) == GPIO_PIN_SET) {
buttonState = RELEASE;
// 如果经过了按键消抖时间,并且按下时间不超过短按阈值,则ButtonRressState切换到短按状态;否则切换到长按状态
if (currentTime - lastKeyPressTime >= debounceDelay) {
if (currentTime - lastKeyPressTime < shortPressThreshold) {
ButtonRressState = SHORT_PRESS;
} else {
ButtonRressState = LONG_PRESS;
}
}
}
Break;
default:
break;
}
// 处理按键按下状态逻辑
switch (ButtonRressState) {
case SHORT_PRESS:
// 处理短按逻辑
ButtonRressState = NULL_PRESS;//恢复默认无操作状态
break;
case LONG_PRESS:
// 处理长按逻辑
ButtonRressState = NULL_PRESS;//恢复默认无操作状态
break;
default:
//处理无操作状态逻辑
break;
}
}
// 设置回调函数
void setCallback(CallbackFunction func) {
// 检查传入的函数指针是否为空
if (func != NULL) {
callback = func; // 设置回调函数为传入的函数指针
} else {
callback = default_toggle_LED; // 设置回调函数为默认的 LED 闪烁函数
}
}
// 默认的 LED 闪烁函数
void default_toggle_LED () {
// 默认操作,例如关闭 LED
HAL_GPIO_WritePin(GPIOC, LED_PIN, GPIO_PIN_RESET);
}
/* LED闪烁函数,作为回调函数,
当被定义以后就调用,不定义则执行上述默认回调函数,实现类似"重写"*/
void toggle_LED() {
static uint8_t ledState = 0; // 静态变量,用于存储 LED 的当前状态(0 或 1)
static uint32_t lastToggleTime = 0; // 静态变量,用于存储上一次 LED 状态改变的时间点
// 如果闪烁标志为 ENABLE,则表示需要执行 LED 的闪烁程序
if (blink_flag == ENABLE) {
lastToggleTime = HAL_GetTick(); // 获取当前时间,作为闪烁程序执行的起始时间
blink_flag = DISABLE; // 下次执行闪烁程序
}
// 如果当前时间与上次改变 LED 状态的时间间隔超过了设定的闪烁时间(Blink_Time)
if (HAL_GetTick() - lastToggleTime > Blink_Time) {
// 根据当前 LED 的状态,设置 GPIOC 的相应引脚状态(SET 或 RESET),实现 LED 的亮灭
HAL_GPIO_WritePin(GPIOC, LED_PIN, ledState ? GPIO_PIN_SET : GPIO_PIN_RESET);
// 切换 LED 的状态
ledState = !ledState;
// 设置闪烁标志为 ENABLE,以便下次再次触发闪烁程序
blink_flag = ENABLE;
}
}
int main(void) {
// 初始化 HAL 库
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 初始化按键中断
EXTI0_IRQHandler_Config();
// 初始化 LED
LED_Init();
// 设置回调函数
#ifdef toggle_LED
// 如果toggle_LED定义了,存在
if (IS_FUNCTION(toggle_LED)) { 判断toggle_LED是否是函数指针
setCallback(toggle_LED);//是,则设置回调函数为toggle_LED
} else {
// 如果toggle_LED不是,则设置回调函数为默认函数
setCallback(default_toggle_LED);
}
#else
// 如果toggle_LED未定义,则设置回调函数为默认函数
setCallback(default_toggle_LED);
#endif
while (1) {
// 主循环
// 调用回调函数
if (callback != NULL) {
callback();
}
}
}