长按与短按按键检测程序与及用回调形式写个LED闪烁程序:

长按与短按按键检测程序与及用回调形式写个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();
        }	 	
    }
}

上述代码是之前的笔试题,未经过上机测试,如有错误请指出,谢谢大家

  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值