【RTT-Studio】详细使用教程六:按键检测实现

一、简介

本文主要介绍在RT-Thread Studio中,如何对按键进行检测,分为两种方式进行检测:

  • 状态机方式:可以检测按键的单击、双击和长按,并且可以设置三个状态的检测时间长短,主要分为五个状态:未按、按下结束、按下抖动、松开抖动和长按。

  • 常用的方式:通过检测按键的按下状态,并且还有长按的标志,可以长按,也可以不启用长按。


二、RTT时钟配置

由于使用RTT生成的工程默认使用的是系统内部时钟,便于我们对时间的控制,所以通常会使用外部时钟,因此需要对工程中的时钟进行更改,更改内容如下:

  • 打开RT-Thread Studio软件新建基于芯片的项目,并使用外部时钟系统。
  • 在drv_clk.c文件中添加时钟配置函数,并且注释内部时钟的调用。
/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    /** Configure the main internal regulator output voltage
     */
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    /** Initializes the CPU, AHB and APB busses clocks
     */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 4;
    RCC_OscInitStruct.PLL.PLLN = 168;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 4;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        Error_Handler();
    }

    /** Initializes the CPU, AHB and APB busses clocks
     */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
            | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
    {
        Error_Handler();
    }
}

void clk_init(char *clk_source, int source_freq, int target_freq)
{
//    system_clock_config(target_freq);
    SystemClock_Config();
}

三、初始化配置

1.常用的方式

  • 实现的功能:主要是检测按键是否按下,以及是否使用长按的功能
  • 使用延时函数:主要是用来去除抖动,一般去抖动的时间为:10-20ms
  • 长按功能:如果需要使用长按,则是将mode参数设置为1即可,如果设置为0则是不支持长按
/**
 * @brief 选择和启停按键处理函数
 * @param mode: 0,不支持连续按;1,支持连续按;
 * @return 返回按键值
 */
uint8_t KEY_MODE_and_ON_Scan(uint8_t mode)
{
    static uint8_t key_up = 1; //按键按松开标志

    if (mode)
    {
        key_up = 1;  //支持连按
    }

    if (key_up && (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0 || HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) == 0))
    {
        Delay_ms(20);  //去抖动
        key_up = 0;
        if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0)
        {
            return 2;
        }
        else if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) == 0)
        {
            return 1;
        }
    }
    else if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 1 && HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) == 1)
    {
        key_up = 1;
    }
    return 0;  // 无按键按下
}

2.状态机方式:
(1)按键数据清零函数

/**
 * @brief 按键数据清零
 * @param pButton 按键结构体
 */
static void clear_State(Laser_Button *pButton)
{
    pButton->first_count = 0;
    pButton->wait_count = 0;
    pButton->singleclick_count = 0;
    pButton->longclick_flag = 0;
    pButton->button_flg = 0;
    pButton->last_state = 0;
    pButton->current_state = 0;
}

(2)按键检测函数:主要分为四个状态,分别为:未按和按下结束、按下抖动期、松开抖动期、长按。

  • 实现的功能:主要是检测按键的单击、双击和长按。并且三个状态的检测事件可以自定义
  • 返回值:主要是返回检测到按键的状态,包括单击、双击、长按和长按结束
/**
 * @brief  按键检测函数
 * @param  pButton 按键结构体
 * @return 1:单击   2:双击   3:长按   4:单击和双击结束   5:长按结束
 */
static rt_uint8_t button_scan(Laser_Button *pButton)
{
    rt_uint16_t button_state;              //按键状态标志位

    // 按键按下
    if (READ_BUTTON_STATE == 1)
    {
        pButton->first_count++;
        pButton->last_state = pButton->current_state;
        pButton->current_state = 1;
    }
    else
    {
        pButton->last_state = pButton->current_state;
        pButton->current_state = 0;
    }

    button_state = (pButton->last_state << 4) + pButton->current_state;

    switch (button_state)
    {
        // 未按、按下结束
        case 0x00:
        {
            // 单击
            if (pButton->singleclick_count == 1)
            {
                pButton->wait_count++;
                pButton->button_flg = 1;

                if (pButton->wait_count > pButton->doubleClick_time)
                {
                    pButton->button_flg = 1;
                    clear_State(pButton);

                    return KEY_SINGLE_CLICK;
                }
            }

            // 双击
            else if (pButton->singleclick_count == 2)
            {
                clear_State(pButton);

                return KEY_DOUBLE_CLICK;
            }

            // 长按结束
            else if (pButton->longclick_flag)
            {
                pButton->longclick_flag = 0;
                clear_State(pButton);
                if (pButton->button_flg == 1)
                {
                    pButton->button_flg = 0;
                    return KEY_STRIKE_CANCEL;
                }

                return KEY_LONG_CLICK_CANCEL;
            }

            break;
        }

        // 处于按下抖动期
        case 0x01:
        {
            pButton->wait_count = 0;
            break;
        }

        // 处于松开抖动期
        case 0x10:
        {
            // 不是长按和其他
            if (pButton->first_count > pButton->singleClick_time && pButton->longclick_flag == 0)
            {
                pButton->singleclick_count++;
            }
            else
            {
                pButton->singleclick_count = 0;
            }

            pButton->first_count = 0;

            break;
        }

        // 处于长按状态
        case 0x11:
        {
            if (pButton->first_count > pButton->longClick_time)
            {
                pButton->longclick_flag = 1;
                pButton->singleclick_count = 0;
                if (pButton->button_flg == 1)
                {
                    return KEY_STRIKE_CANCEL;
                }

                return KEY_LONG_CLICK;
            }

            break;
        }
    }

    return 0;
}

(3)使用方式:直接通过调用该函数既可。如果需要检测不同的按键,只需要将检测函数的宏定义进行更改,或者需要添加多个按键检测,只需增加标志位,代表是哪个按键按下的既可。

(4)检测时间:检测时间通过宏定义对自定义的结构体进行初始化,具体时间如下:

Laser_Button  g_button = {3*20, 15*20, 20*20, 0, 0, 0, 0, 0, 0, 0}; // 初始按键信息, x * 20ms

四、完整代码

1.button.c文件

#include "button.h"
#include "control.h"

Laser_Button  g_button = {3*20, 15*20, 20*20, 0, 0, 0, 0, 0, 0, 0}; // 初始按键信息, x * 20ms

/*======================================================### 静态函数调用 ###==================================================*/
/**
 * @brief 按键数据清零
 * @param pButton 按键结构体
 */
static void clear_State(Laser_Button *pButton)
{
    pButton->first_count = 0;
    pButton->wait_count = 0;
    pButton->singleclick_count = 0;
    pButton->longclick_flag = 0;
    pButton->button_flg = 0;
    pButton->last_state = 0;
    pButton->current_state = 0;
}

/**
 * @brief  按键检测函数
 * @param  pButton 按键结构体
 * @return 1:单击   2:双击   3:长按   4:单击和双击结束   5:长按结束
 */
static rt_uint8_t button_scan(Laser_Button *pButton)
{
    rt_uint16_t button_state;              //按键状态标志位

    // 按键按下
    if (READ_BUTTON_STATE == 1)
    {
        pButton->first_count++;
        pButton->last_state = pButton->current_state;
        pButton->current_state = 1;
    }
    else
    {
        pButton->last_state = pButton->current_state;
        pButton->current_state = 0;
    }

    button_state = (pButton->last_state << 4) + pButton->current_state;

    switch (button_state)
    {
        // 未按、按下结束
        case 0x00:
        {
            // 单击
            if (pButton->singleclick_count == 1)
            {
                pButton->wait_count++;
                pButton->button_flg = 1;

                if (pButton->wait_count > pButton->doubleClick_time)
                {
                    pButton->button_flg = 1;
                    clear_State(pButton);

                    return KEY_SINGLE_CLICK;
                }
            }

            // 双击
            else if (pButton->singleclick_count == 2)
            {
                clear_State(pButton);

                return KEY_DOUBLE_CLICK;
            }

            // 长按结束
            else if (pButton->longclick_flag)
            {
                pButton->longclick_flag = 0;
                clear_State(pButton);
                if (pButton->button_flg == 1)
                {
                    pButton->button_flg = 0;
                    return KEY_STRIKE_CANCEL;
                }

                return KEY_LONG_CLICK_CANCEL;
            }

            break;
        }

        // 处于按下抖动期
        case 0x01:
        {
            pButton->wait_count = 0;
            break;
        }

        // 处于松开抖动期
        case 0x10:
        {
            // 不是长按和其他
            if (pButton->first_count > pButton->singleClick_time && pButton->longclick_flag == 0)
            {
                pButton->singleclick_count++;
            }
            else
            {
                pButton->singleclick_count = 0;
            }

            pButton->first_count = 0;

            break;
        }

        // 处于长按状态
        case 0x11:
        {
            if (pButton->first_count > pButton->longClick_time)
            {
                pButton->longclick_flag = 1;
                pButton->singleclick_count = 0;
                if (pButton->button_flg == 1)
                {
                    return KEY_STRIKE_CANCEL;
                }

                return KEY_LONG_CLICK;
            }

            break;
        }
    }

    return 0;
}

/**
 * @brief 电源按键检测
 * @return 0:未按下   1:按下
 */
rt_uint8_t Power_Key_Scan(void)
{
    if (READ_POWER_KEY_STATE == 0)
    {
        rt_thread_delay(20);  //去抖动
        if (READ_POWER_KEY_STATE == 0)
        {
            return 1;
        }
    }

    return 0;  // 无按键按下
}
/*=====================================================#######  END  #######=================================================*/

2.button.h文件

#include <rtthread.h>
#include <drv_common.h>

/**=====================================================###### 宏定义 ######==================================================*/
#define KEY_SINGLE_CLICK         1               // 单击
#define KEY_DOUBLE_CLICK         2               // 双击
#define KEY_LONG_CLICK           3               // 长按
#define KEY_STRIKE_CANCEL        4               // 单击和双击结束
#define KEY_LONG_CLICK_CANCEL    5               // 长按结束

#define KEY_PRESS_DOEN_TIME      3000            // 按键按下时间

#define READ_BUTTON_STATE       rt_pin_read(SW_LOCK)  // 读取扳机按键状态
#define READ_POWER_KEY_STATE    rt_pin_read(PWR_KEY)  // 读取开机按键状态

typedef struct Laser_Button
{
    rt_uint16_t singleClick_time;                // 单击按钮时间
    rt_uint16_t doubleClick_time;                // 双击时两次单击最大间隔时间
    rt_uint16_t longClick_time;                  // 长按时间

    rt_uint8_t  last_state;                      // 上一次按键的状态
    rt_uint8_t  current_state;                   // 当前按键状态

    rt_uint16_t first_count;                     // 第一次计数
    rt_uint16_t wait_count;                      // 等待计数
    rt_uint8_t singleclick_count;                // 单击计数
    rt_uint8_t longclick_flag;                   // 双击标志
    rt_uint8_t button_flg;                       // 按键标志

} Laser_Button, *PLaser_Button;
/**====================================================#######  END  #######=================================================*/

五、测试验证

通过使用串口助手进行打印验证,观察按键的检测状况,然后通过更改检测的时间,来检测实际的使用效果,通过使用效果来优化检测的时间。测试效果如下:
在这里插入图片描述

  • 23
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值