一、简介
本文主要介绍在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 #######=================================================*/
五、测试验证
通过使用串口助手进行打印验证,观察按键的检测状况,然后通过更改检测的时间,来检测实际的使用效果,通过使用效果来优化检测的时间。测试效果如下: