HAL STM32通用定时器+EXTI实现单击/双击/长按功能

HAL STM32通用定时器+EXTI实现单击/双击/长按功能


  • ✨在使用USB功率计的时候,发现上面的一个按键实现多画面功能切换,于是探索了一下是如何实现的,将其实现的基本思路以及综合网上收集的相关实现方法,粗陋的整理了一下,将其基本功能实现了一下,具体的功能没有做,只是做了一个简单的框架。
    在这里插入图片描述
  • 👉🏻功能实现,并不能保证每次处理的按键结果,都如预期效果,主要的影响因数是单击和双击,之间的间隔时间,每个人的手感或者每次按下的时间差异并不能像计算器那么准确和标准。

🛠HAL STM32CubeMX工程配置

  • 🌿基于STM32F103芯片。

  • 🌿主要配置一个外部中断按键引脚。
    在这里插入图片描述

  • 🔖GPIO引脚按键外部使用电路参考:
    在这里插入图片描述

  • 🌿配置一个定时器
    在这里插入图片描述

  • 🌿NVIC中断使能以及优先级配置。
    在这里插入图片描述

  • 🌿其他串口和I2C配置用于显示和调试输出。(这里根据个人需求配置,个人推荐还是使用软件I2C实现比较好,硬件I2C如果程序比较复杂,cpu处理各种中断,容易导致硬件I2C出错,导致显示画面异常的情况。)
    在这里插入图片描述
    在这里插入图片描述

  • 🌿其他时钟配置和工程生成就不展示了,根据个人情况配置。

⛳功能实现

  • 🌿完善按键中断回调内容
//重写回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{

    /*判断是中断引脚*/
    if(GPIO_Pin == KEY_Pin)
    {
        Trg++;
        HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);//状态翻转
    }
}
  • 🌿定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
{

    static uint32_t click = 0;
    static char keyStatus = 0;
    static char LongPress = 0;
    if(htim ->Instance == TIM6)
    {
        if(++cnt_1ms >= 1000)
        {
            cnt_1ms = 0;
            HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);//状态翻转
        }
        if(Trg > 0)
        {
            keyStatus = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);//读取按键状态
            click++;
            if((click > _debounce_ms) && (keyStatus == 1) && (LongPress == 0)) //1000
            {
                if((Trg == 1) || (Trg > 2)) //单击
                {
                    Trg = 0;
                    funIndex ++;
                    printf("_click_ms,%d\r\n", funIndex);
                    click = 0;//清零
                }
                if(Trg == 2) //双击
                {
                    Trg = 0;
                    funIndex += 2;
                    printf("_doubleClick %d\r\n", funIndex);
                    click = 0;//清零
                }
            }
            if((click > _press_ms) && (keyStatus == 0) && (LongPress == 0)) //1000
            {
                printf("_longPressStart\r\n");
                LongPress = 1;
            }
            if((LongPress == 1) && (keyStatus == 1))
            {
                LongPress = 0;
                Trg = 0;
                funIndex += 3;//松开才赋值
                click = 0;
                printf("_longPressStop %d\r\n", funIndex);
            }
        }
        if(funIndex > 8)funIndex = 0;

    }
}

  • 🌿oled驱动显示内容,可以根据个人使用的屏幕规格型号自行完善,个人使用的是SH1106 1.3“寸的OLED屏幕作为显示。
  • 🌿按键菜单显示内容参考网上的内容。
unsigned char funIndex = 0;//9和0->1-1
void menu11(void);
void menu12(void);
void menu21(void);
void menu22(void);
void menu23(void);
void menu31(void);
void menu32(void);
void menu33(void);
void menu34(void);
//定义按键操作数据
KEY_TABLE table[9] =
{
    {0, 0, 1, 0, 2, (*menu11)},
    {1, 0, 1, 1, 4, (*menu12)},
    {2, 2, 3, 0, 5, (*menu21)},
    {3, 2, 3, 0, 7, (*menu22)},
    {4, 4, 4, 1, 4, (*menu23)},
    {5, 5, 6, 2, 5, (*menu31)},
    {6, 5, 6, 2, 6, (*menu32)},
    {7, 7, 8, 3, 7, (*menu33)},
    {8, 7, 8, 3, 8, (*menu34)},
};

void ShowMenu(int16_t x, int16_t y,char *text)
{
    OLED_ShowString(x, y, text, 12);//x,y,字符串,字体大小
}

//一级菜单1
void menu11(void)
{

//    OLED_Clear();
    OLED_ShowString(36, 6, "menu1-1", 16);
}

//一级菜单2
void menu12(void)
{
//    OLED_Clear();
    OLED_ShowString(36, 6, "menu1-2", 16);
}

//二级菜单1
void menu21(void)
{
//    OLED_Clear();
    OLED_ShowString(36, 6, "menu2-1", 16);
}

//二级菜单2
void menu22(void)
{
//    OLED_Clear();
    OLED_ShowString(36, 6, "menu2-2", 16);
}

//二级菜单3
void menu23(void)
{
//    OLED_Clear();
    OLED_ShowString(36, 6, "menu2-3", 16);

}

//三级菜单1
void menu31(void)
{
//    OLED_Clear();
    OLED_ShowString(36, 6, "menu3-1", 16);
}

//三级菜单2
void menu32(void)
{
//    OLED_Clear();
    OLED_ShowString(36, 6, "menu3-2", 16);
}

//三级菜单3
void menu33(void)
{
//    OLED_Clear();
    OLED_ShowString(36, 6, "menu3-3", 16);
}

//三级菜单4
void menu34(void)
{
//    OLED_Clear();
    OLED_ShowString(36, 6, "menu3-4", 16);

}
  • 📝main主函数
int main(void)
{
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_I2C1_Init();
    MX_USART1_UART_Init();
    MX_I2C2_Init();
    MX_TIM6_Init();
    /* USER CODE BEGIN 2 */
    OLED_Init();
    HAL_TIM_Base_Start_IT(&htim6);
    ShowMenu(2, 0, "OneKey Mul-Menu");
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while(1)
    {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
        current = table[funIndex].operation;//根据需要获取对应需要执行的函数
        (*current)();//执行获取到的函数

    }
    /* USER CODE END 3 */
}
📚工程源码
链接:https://pan.baidu.com/s/1eR14Lpfjqm7nA0lvwbv5xg 
提取码:vs7f

🌼利用状态机的思想实现方法

  • 📝定时器更新中断回调函数
// 按键状态定义
typedef enum {
    KEY_IDLE = 0, // 按键空闲状态
    KEY_DOWN, // 按键按下状态
    KEY_UP, // 按键释放状态
    KEY_WAIT, // 等待双击状态
    KEY_DOUBLE, // 双击状态
    KEY_LONG // 长按状态
} key_status_t;

// 按键状态变量
key_status_t key_status = KEY_IDLE;
// 按键按下时间
uint32_t key_down_time = 0;
// 按键释放时间
uint32_t key_up_time = 0;

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{

    static uint32_t msCount = 0;
    if(htim->Instance == htim4.Instance) {
        // 获取按键状态
        uint8_t key = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);

        // 处理按键事件
        switch(key_status) {
            case KEY_IDLE:
                if(key == GPIO_PIN_RESET) {
                    // 按键按下,进入按下状态
                    key_status = KEY_DOWN;
                    key_down_time = HAL_GetTick();
                }
                break;
            case KEY_DOWN:
                if(key == GPIO_PIN_SET) {
                    // 按键释放,进入释放状态
                    key_status = KEY_UP;
                    key_up_time = HAL_GetTick();
                } else if(HAL_GetTick() - key_down_time > 1200) {
                    // 按键长按,进入长按状态
                    key_status = KEY_LONG;
                    // HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
                    printf("Key Press in LongState \r\n"); //输出执行信息
                }
                break;
            case KEY_UP:
                if(key == GPIO_PIN_RESET) {
                    // 按键再次按下,进入等待双击状态
                    key_status = KEY_WAIT;
                } else if(HAL_GetTick() - key_up_time > 300) {
                    // 按键单击
                    printf("Key Press ClickKey \r\n"); //输出执行信息
                    key_status = KEY_IDLE;
                }
                break;
            case KEY_WAIT:
                if(key == GPIO_PIN_SET) {
                    // 等待超时,进入单击状态
                    key_status = KEY_IDLE;
                } else if(HAL_GetTick() - key_up_time > 300) {
                    // 双击事件
                    printf("Key Press DoubleKey \r\n"); //输出执行信息
                    key_status = KEY_DOUBLE;
                }
                break;
            case KEY_DOUBLE:
                if(key == GPIO_PIN_SET) {
                    // 双击事件结束
                    printf("Key Press DoubleKey \r\n"); //输出执行信息
                    key_status = KEY_IDLE;
                }
                break;
            case KEY_LONG:
                if(key == GPIO_PIN_SET) {
                    // 长按事件结束
                    // HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
                    printf("Key Press LongKey \r\n"); //输出执行信息
                    key_status = KEY_IDLE;
                }
                break;
            default:
                break;
        }
        if(++msCount > 500) {
            HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_8); //翻转电平,LED翻转
            msCount = 0;
        }

    }

}
  • 📓源码
链接:https://pan.baidu.com/s/1OdGuxL24ls6-iazaPHUlfw?pwd=vzc1 
提取码:vzc1

🌾其他实现方法

  • 🔖同样是通过定时器中断来实现计时和统计。

// 按键状态定义
uint8_t key_status = 0;

// 按键按下时间
uint32_t key_down_time = 0;

// 按键释放时间
uint32_t key_up_time = 0;
uint8_t KeyPress = 0;
uint8_t Key_Press_Count = 0;
uint8_t Key_Release_Time = 0;
uint16_t Key_Long_Press_Count = 0;
uint8_t LongKey_State = 0;
/* 更新中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{

    static uint32_t msCount = 0;
    if(htim->Instance == htim4.Instance) {
        //	__HAL_TIM_CLEAR_IT(htim,TIM_IT_UPDATE);//清楚标志位
        // 获取按键状态
        KeyPress = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);
        if(!KeyPress) { //按键被按下
            //	KeyPress = 0;//清除按键标志位
            if(key_status == 0) {
                key_status = 1;
                if(Key_Press_Count < 255)Key_Press_Count++;//记录按键按的次数。
                Key_Release_Time = 150;//刷新检测松开的时间:300
            }
            if(++Key_Long_Press_Count > 750) {//1.5s
                Key_Press_Count = 0;//计时清零
//							 printf("Key Press into LongState \r\n");               
                	LongKey_State = 1;
            }
        } else {

            key_status = 0;
            Key_Long_Press_Count = 0;
            if(Key_Release_Time)Key_Release_Time--;	//松开时计时
        }
        if(Key_Release_Time == 0) {
					 if(LongKey_State==1 && KeyPress==1){
						 printf("Key Press LongKey \r\n");
						 LongKey_State =0;
					 }
            if(Key_Press_Count == 1) {	//单击
                printf("Key Press ClickKey \r\n");
            } else if(Key_Press_Count == 2) {	//双击
                printf("Key Press DoubleKey \r\n");
            }
            // ……连续3、4、5次按键操作,可在下面继续添加
            Key_Press_Count = 0;

        }

        if(++msCount > 500) {
            HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); //翻转电平,LED翻转
            msCount = 0;
        }

    }

}
  • 📘程序源码:
链接:https://pan.baidu.com/s/1e92kJ8WtmISinxTOPbUDaQ?pwd=o4j2 
提取码:o4j2
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用STM32CubeMX实现按键输入的单击双击功能时,需要先配置GPIO口为输入模式并使能中断。然后在中断服务函数中判断按键事件类型,根据不同的事件类型执行相应的操作。 下面是一个示例代码,实现了PA0口的单击双击按事件: ``` #include "main.h" #define DEBOUNCE_TIME 20 // 消抖时间,单位ms #define LONG_PRESS_TIME 1000 // 按时间,单位ms GPIO_TypeDef* KEY_PORT = GPIOA; uint16_t KEY_PIN = GPIO_PIN_0; volatile uint32_t press_time = 0; // 按键按下时间 volatile uint32_t release_time = 0; // 按键释放时间 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == KEY_PIN) { if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) { // 按键按下 press_time = HAL_GetTick(); } else { // 按键释放 release_time = HAL_GetTick(); uint32_t duration = release_time - press_time; if (duration < DEBOUNCE_TIME) { // 消抖处理 return; } if (duration < LONG_PRESS_TIME) { // 短按 // 执行单击双击操作 // 双击的判断方法是在两次单击之间的时间小于一定值 } else { // 按 // 执行操作 } } } } int main(void) { // 初始化代码省略 // ... // 配置PA0为输入模式 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = KEY_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(KEY_PORT, &GPIO_InitStruct); // 使能PA0中断 HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn); while (1) { // 主循环代码省略 // ... } } void EXTI0_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(KEY_PIN); } ``` 需要注意的是,在使用HAL库的情况下,需要在`stm32xx_it.c`文件中手动添加`EXTI0_IRQHandler`函数的实现

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值