https://blog.csdn.net/mygod2008ok/article/details/106954917
/**
* @file BSP_key.c
* @author jzhou
* @version V1.0
* @date 11-Nov-2019
* @copyright Chileaf LTD
* @brief 按键扫描模块
* 实现单按键或多按键的短按,长按,短按松开,长按松开等键值
*/
#include "BSP_key.h"
#include "BSP_pwm.h"
//###############################################################################################################################
/** @file
*
* @defgroup BSP_key file:BSP_key文件
* @{
* @ingroup BSP_key file
* @brief 按键扫描模块
* @details 按键功能处理,主要是产生按键键值
*/
//###############################################################################################################################
//-----------------------以下为按键引脚配置及初时化--------------------------
/**
*
* @brief 按键引脚配置信息初时化
*/
static const SCAN_KEY_INFO scan_key[] = {
{GPIOA,GPIO_PIN_12},
{GPIOA,GPIO_PIN_11},
};
/**
* @brief 获取键值
*/
static KEY_TYPE_VAR get_key_value(void)
{
KEY_TYPE_VAR KeyTemp = 0;
// 读按键IO电平状态
for(KEY_TYPE_VAR i=0; iLONG_ON_DITHERING_COUNTER) //长按时间大于2秒,此时间可以重定义
{
KeySampleCnt = LONG_ON_DITHERING_COUNTER-SYS_TICK_COUNTER/8; // 重复触发长按事件阀值
KeyBuffer |= KEY_LONG_ON;
return KeyTemp | KEY_LONG_ON ; //返回长按事件
}
}
else if(KeyTemp ==0)
{
KeyEventCnt = CHECK_HAS_KEY_DOWN;
if((KeyBuffer & KEY_OFF) != KEY_OFF)
return 0; //无效键
#if LONG_KEY_OFF_EVENT_ENABLED
return KeyBuffer & ~KEY_BLOCK_FLAG; //返回短按松开和长按松开事件
#else
return KeyBuffer & ~(KEY_BLOCK_FLAG | KEY_LONG_ON); //返回松开事件
#endif
}
else if(((KeyBuffer & KEY_LONG_ON)==0) &&(KeySampleCnt < 5)) //多键同时按下,允许的按下时间,超过此时间检测到多键将无效
{
KeyEventCnt = CHECK_HAS_KEY_DOWN;
}
break;
}
return 0;
}
void DisLongKeyContinueResponse(void)
{
KeyBuffer |= KEY_BLOCK_EVENT; //长按键仅响应一次事件
}
//#####################################################################################################
/**
* @brief 按键事件注册
*/
void key_init(keyFunc fun)
{
key_pin_init();
keyFunctionHandler = fun;
}
/**
* @brief 按键事件处理,此函数中定时事件中调用,此例程在25Hz中调用
*/
void keyProcessHandler(void)
{
KEY_TYPE_VAR event = OSReadKey();
if(event == NO_KEY_EVENT)
return;
// NRF_LOG_INFO("KEY=%x",event);
if(keyFunctionHandler != NULL)
{
keyFunctionHandler((E_KEY_VALUE)event);
}
}
/**@}*/
#ifndef BSP_KEY__H
#define BSP_KEY__H
#include "sdk_config.h"
#include "stm32f0xx.h"
/** @file
*
* @defgroup BSP_key file:BSP_key文件
* @{
* @ingroup BSP_key file
* @brief 按键扫描模块
* @details 按键功能处理,主要是产生按键键值
*/
#ifdef __cplusplus
extern "C" {
#endif
#define LONG_KEY_OFF_EVENT_ENABLED 1 //区分长按松开事件,否则短按松开和长按松开事件统一为松开事件
/**
* @brief 按键引脚配置结构体
*
*/
typedef struct
{
GPIO_TypeDef *control_port; //!< IO端口地址
uint16_t enable_pin; //!< 引脚序号
}SCAN_KEY_INFO;
/************************************************************************/
#define TOTAL_KEY_NUM sizeof(scan_key)/sizeof(SCAN_KEY_INFO) //!< 按键总个数
#define LONG_ON_DITHERING_COUNTER (SYS_TICK_COUNTER*2) //!< 定义长按按下确认需要的时间,如果是每40毫秒调用一次OSReadKey()
//-------选择支持按键最大的键个数,默认支持5个按键-----------
//#define KEY_NUM_MAX_13
#ifdef KEY_NUM_MAX_13
#define KEY_TYPE_VAR uint16_t
#define LONG_KEY_FLAG 0X8000
#define OFF_KEY_FLAG 0X4000
#define BLOCK_LONG_FLAG 0X2000
#else
#define KEY_TYPE_VAR uint8_t
#define LONG_KEY_FLAG 0X80
#define OFF_KEY_FLAG 0X40
#define BLOCK_LONG_FLAG 0X20
#endif
//##################################################################################
/**
* @brief 按键键值定义
*/
typedef enum
{
NO_KEY_EVENT = 0,
KEY_LONG_ON = LONG_KEY_FLAG, //!< 长按MASK值
KEY_OFF = OFF_KEY_FLAG, //!< 松开MASK值
KEY_BLOCK_FLAG = BLOCK_LONG_FLAG, // !<阻塞标记>
KEY_BLOCK_EVENT = KEY_LONG_ON | KEY_OFF | BLOCK_LONG_FLAG, //!< 阻塞长按键
//-----------------------------------------------------------------------------------------
UP_KEY_SHORT = 0x01, // KEY1短按键值
UP_KEY_LONG = UP_KEY_SHORT | KEY_LONG_ON, // KEY1长按键值
UP_KEY_OFF = UP_KEY_SHORT | KEY_OFF, // KEY1短按松开键值
UP_KEY_LONG_OFF = UP_KEY_LONG | KEY_OFF, // KEY1长按松开键值
DOWN_KEY_SHORT = 0x02, // KEY2短按键值
DOWN_KEY_LONG = DOWN_KEY_SHORT | KEY_LONG_ON, // KEY2长按键值
DOWN_KEY_OFF = DOWN_KEY_SHORT | KEY_OFF, // KEY2短按松开键值
DOWN_KEY_LONG_OFF = DOWN_KEY_LONG | KEY_OFF, // KEY2长按松开键值
UP_DOWN_KEY_SHORT = UP_KEY_SHORT | DOWN_KEY_SHORT, // KEY1+KEY2短按键值
UP_DOWN_KEY_LONG = UP_DOWN_KEY_SHORT | KEY_LONG_ON, // KEY1+KEY2长按键值
UP_DOWN_KEY_OFF = UP_DOWN_KEY_SHORT | KEY_OFF, // KEY1+KEY2短按松开键值
UP_DOWN_KEY_LONG_OFF = UP_DOWN_KEY_LONG | KEY_OFF, // KEY1+KEY2长按松开键值
}E_KEY_VALUE;
typedef enum
{
CHECK_HAS_KEY_DOWN,
KEY_EVENT_CHECK,
}E_KEY_SCAN_STATUS;
/**
* @brief 按键处理功能函数指针
*/
typedef void (*keyFunc)(E_KEY_VALUE keyValue);
//#####################################################################################
/**
* @defgroup BSP_KEY_API 按键功能API
* @{
*/
//############################ API ####################################################
/**
* @brief 按键引脚初时化
* @param [in] keyFunc fun 按键处理函函数指针
* @return
* - None
*/
void key_init(keyFunc fun);
/**
* @brief 按键扫描处理
* @param None
* @return
* - None
* @note 此函数需要在系统嘀嗒事件中调用
*/
void keyProcessHandler(void);
/**
* @brief 长按键阻止
* Routine Name: DisLongKeyContinueResponse
* Form: void DisLongKeyContinueResponse(void)
* Parameters: void
* Return Value: void
* Description: if call this function in the key process function,so it keep
* continue long key msg to generate
*
* @param None
* @return None
*/
void DisLongKeyContinueResponse(void);
//####################################################################################
/**@}*/
#ifdef __cplusplus
}
#endif
/**@}*/
#endif
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
RTT_INIT();
HAL_Init();
SystemClock_Config();
BSP_adc_init();
BSP_wdt_init(IWDG_OVER_TIME);
delay_init();
//--------初时化串口------------------------------------
BSP_uart_init();
MX_RTC_Init();
BSP_start_adc_count(8);
start_buzzer_beep_sound();
reply_stm32_version();
//-------------按键初时化-------------------------------
key_init(key_event_handler);
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
if(s_wakeup_flag) //任务处理模式
{
BSP_wdt_feed();
tick_25hz_handler();
tick_1hz_handler();
uart_data_handler();
}
else // 省电模式
{
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
}
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief 按键功能处理
* 分配ID及获取历史数据按键功能响应处理
* @param [in] E_KEY_VALUE 按键键值
* @retval
* - None
*/
static void key_event_handler(E_KEY_VALUE keyValue)
{
switch(keyValue)
{
case UP_KEY_SHORT:
NRF_LOG_INFO("UP_KEY_SHORT=%x",keyValue);
break;
case DOWN_KEY_SHORT:
NRF_LOG_INFO("DOWN_KEY_SHORT=%x",keyValue);
break;
case UP_DOWN_KEY_SHORT:
NRF_LOG_INFO("UP_DOWN_KEY_SHORT=%x",keyValue);
break;
case UP_KEY_OFF:
NRF_LOG_INFO("UP_KEY_OFF=%x",keyValue);
break;
case DOWN_KEY_OFF:
NRF_LOG_INFO("DOWN_KEY_OFF=%x",keyValue);
break;
case UP_DOWN_KEY_OFF:
NRF_LOG_INFO("UP_DOWN_KEY_OFF=%x",keyValue);
break;
case UP_KEY_LONG: //此事件未调用阻止长按键,此事件会不停触发,直到按键松开
NRF_LOG_INFO("UP_KEY_LONG=%x",keyValue);
break;
case UP_KEY_LONG_OFF:
NRF_LOG_INFO("UP_KEY_LONG_OFF=%x",keyValue);
break;
case DOWN_KEY_LONG:
NRF_LOG_INFO("DOWN_KEY_LONG=%x",keyValue);
DisLongKeyContinueResponse(); //阻止长按键,仅触发一次
break;
case DOWN_KEY_LONG_OFF:
NRF_LOG_INFO("DOWN_KEY_LONG_OFF=%x",keyValue);
break;
case UP_DOWN_KEY_LONG:
NRF_LOG_INFO("UP_DOWN_KEY_LONG=%x",keyValue);
DisLongKeyContinueResponse(); //阻止长按键,仅触发一次
break;
case UP_DOWN_KEY_LONG_OFF:
NRF_LOG_INFO("UP_DOWN_KEY_LONG_OFF=%x",keyValue);
break;
default:
break;
}
}
/**
* @brief 25Hz handler
*
*/
static void tick_25hz_handler(void)
{
if((s_wakeup_flag & TICK_FOR_25HZ) == 0)
return;
s_wakeup_flag &= CLEAR_TICK_FOR_25HZ;
//####################################################################################
//---------TODO this to add 25hz event handler-----------
BSP_adc_convert_handler();
buzzer_beep_sound_handler();
uart1_rec_timeout();
keyProcessHandler();
}
1. 按键列表变量,此例程只使用了2个IO作为按键,实现更多按键只需在scan_key数组中添加IO信息即可
/**
* @brief 按键引脚配置结构体
*
*/
typedef struct
{
GPIO_TypeDef *control_port; //!< IO端口地址
uint16_t enable_pin; //!< 引脚序号
}SCAN_KEY_INFO;
//-----------------------以下为按键引脚配置及初时化--------------------------
/**
*
* @brief 按键引脚配置信息初时化
*/
static const SCAN_KEY_INFO scan_key[] = {
{GPIOA,GPIO_PIN_12},
{GPIOA,GPIO_PIN_11},
};
2.按键初时化函数,对scan_key数组中的IO配置成输入上拉,即按键低电平为按下
/**
* @brief 按键引脚初时化配置
*/
static void key_pin_init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct;
/* -2- Configure IOs in output push-pull mode to drive external LEDs */
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
for(uint8_t i=0; i
3.get_key_value函数获取IO电平值,对scan_key数组中的IO从下标0开始读取IO电平值,如有按键按下,将相应位置1,例如下标0的IO被按下,则键值的第0位置1,依此类推,下标1的IO被按下,则键值的第1位置1,如果没有任何键按下,则键值为0,也就是说,可以通过判断键值不为0说明有键按下
/**
* @brief 获取键值
*/
static KEY_TYPE_VAR get_key_value(void)
{
KEY_TYPE_VAR KeyTemp = 0;
// 读按键IO电平状态
for(KEY_TYPE_VAR i=0; i
4.OSReadKey函数实现键值事件,此函数是按键扫描的核心,按键去抖,短按,长按,短按松开,长按松开,多键等都是由此函数实现
/**
* @brief 按键键值扫描
* 扫描按键状态返回对应的键值
* @param None
* @retval uint8_t
* - 返回按键键值
*/
static KEY_TYPE_VAR OSReadKey(void)
{
static E_KEY_SCAN_STATUS KeyEventCnt;
static KEY_TYPE_VAR KeySampleCnt;
KEY_TYPE_VAR KeyTemp = get_key_value();
switch(KeyEventCnt )
{
case CHECK_HAS_KEY_DOWN: // 有键按下
if(KeyTemp != 0)
{
KeySampleCnt=0;
KeyBuffer=KeyTemp;
KeyEventCnt = KEY_EVENT_CHECK;
}
break;
case KEY_EVENT_CHECK: //按键事件检测
if(KeyTemp == KeyBuffer )
{
KeyBuffer |= KEY_OFF;
return KeyTemp ; //返回短按事件
}
else if(KeyTemp == (KeyBuffer & ~(KEY_OFF | KEY_LONG_ON)))
{
if(++KeySampleCnt>LONG_ON_DITHERING_COUNTER) //长按时间大于2秒,此时间可以重定义
{
KeySampleCnt = LONG_ON_DITHERING_COUNTER-SYS_TICK_COUNTER/8; // 重复触发长按事件阀值
KeyBuffer |= KEY_LONG_ON;
return KeyTemp | KEY_LONG_ON ; //返回长按事件
}
}
else if(KeyTemp ==0)
{
KeyEventCnt = CHECK_HAS_KEY_DOWN;
if((KeyBuffer & KEY_OFF) != KEY_OFF)
return 0; //无效键
#if LONG_KEY_OFF_EVENT_ENABLED
return KeyBuffer & ~KEY_BLOCK_FLAG; //返回短按松开和长按松开事件
#else
return KeyBuffer & ~(KEY_BLOCK_FLAG | KEY_LONG_ON); //返回松开事件
#endif
}
else if(((KeyBuffer & KEY_LONG_ON)==0) &&(KeySampleCnt < 5)) //多键同时按下,允许的按下时间,超过此时间检测到多键将无效
{
KeyEventCnt = CHECK_HAS_KEY_DOWN;
}
break;
}
return 0;
}
4.1 以第1个按键(scan_key[0]对应的IO)为例
4.11 短按事件
a. 按住第1个按键,OSReadKey函数每25Hz调用一次,函数中调用了 get_key_value获取IO键值,第1个键对应的BIT0会置位,其它的位为0,则IO键值为1
KEY_TYPE_VAR KeyTemp = get_key_value();
b. KeyTemp的值为1,KeyEventCnt状态机初时值为CHECK_HAS_KEY_DOWN,则进入分支CHECK_HAS_KEY_DOWN,
由于KeyTemp为1,条件if(KeyTemp != 0)满足,执行按键计数清0,暂存键值到KeyBuffer,KeyBuffer值变成1,状态机
KeyEventCnt迁移到KEY_EVENT_CHECK状态
c. 第2次25Hz时间到,再次调用 OSReadKey函数,再次获取IO键值,如果IO还是被按下,那么BIT0仍然保持为1,否则为0
KEY_TYPE_VAR KeyTemp = get_key_value();
c.1 先看第2次按键仍然按下的情况,KeyTemp为1,KeyEventCnt状态值为KEY_EVENT_CHECK,则进入此分支执行,KeyBuffer的值第1次扫描的时候预存为1,那么if(KeyTemp == KeyBuffer )条件满足,则将KeyBuffer的值与KEY_OFF的值按位或
运算,最后返回KeyTemp的值,此值为1,正好是短按键键值
c.2 如果第2次扫描到按键为松开,则 KEY_TYPE_VAR KeyTemp = get_key_value(); KeyTemp的值为0,那么返回无效键值0
5.短按松开,产生短按事件后,第3次或2秒以内扫描按键是松开,KeyTemp为0,KeyBuffer有KEY_OFF标志,则返回松开键值
KeyBuffer & ~KEY_BLOCK_FLAG;
6.长按事件,如果第1个按键一直按下且时间超过2秒,由于KeyBuffer在短按事件产生时被加上了KEY_OFF标记,所以此条件不成立,这条分KeyTemp == (KeyBuffer & ~(KEY_OFF | KEY_LONG_ON))会成立,原因是将KEY_OFF,KEY_LONG_ON标志屏蔽了,由于按键按住时间超过LONG_ON_DITHERING_COUNTER的次数(2秒),则返回长按键值KeyTemp | KEY_LONG_ON,如果一直按下,会每隔LONG_ON_DITHERING_COUNTER-SYS_TICK_COUNTER/8时间再次触发长按事件
7.长按松开事件,如果产生了长按事件后,按键松开后,则KeyBuffer值中有长按键值和KEY_OFF标志,返回了长按事件
8.多键事件,如果第1个和第2个按键都被按下,第0位和第1位都被置1,则KeyTemp 的值为3,
KEY_TYPE_VAR KeyTemp = get_key_value();
那么其短按事件为
其它键值和单键产生类似,多键的允许接收时间如下条件
9.长按键阻塞,即仅触发一次长按事件,在长按事件中调用DisLongKeyContinueResponse函数会阻赛长按事件被重复触发
void DisLongKeyContinueResponse(void)
{
KeyBuffer |= KEY_BLOCK_EVENT; //长按键仅响应一次事件
}
函数调用后,由于KeyBuffer被添加了KEY_BLOCK_EVENT标志,则以下
(((KeyBuffer & KEY_LONG_ON)==0) &&(KeySampleCnt < 5))分支将不再满足条件,即进行了长按阻塞
/**
* @brief 按键事件处理,此函数中定时事件中调用,此例程在25Hz中调用
*/
void keyProcessHandler(void)
{
KEY_TYPE_VAR event = OSReadKey();
if(event == NO_KEY_EVENT)
return;
// NRF_LOG_INFO("KEY=%x",event);
if(keyFunctionHandler != NULL)
{
keyFunctionHandler((E_KEY_VALUE)event);
}
}
此函数在25Hz函数中调用(40毫秒调用一次),event为NO_KEY_EVENT将不将任何处理,如果有键值产生,则调用函数指针keyFunctionHandler进行事件回调
/**
* @brief 按键功能处理
* 分配ID及获取历史数据按键功能响应处理
* @param [in] E_KEY_VALUE 按键键值
* @retval
* - None
*/
static void key_event_handler(E_KEY_VALUE keyValue)
{
switch(keyValue)
{
case UP_KEY_SHORT: // UP 短按
NRF_LOG_INFO("UP_KEY_SHORT=%x",keyValue);
break;
case DOWN_KEY_SHORT: // DOWN 短按
NRF_LOG_INFO("DOWN_KEY_SHORT=%x",keyValue);
break;
case UP_DOWN_KEY_SHORT: // UP+DOWN 短按
NRF_LOG_INFO("UP_DOWN_KEY_SHORT=%x",keyValue);
break;
case UP_KEY_OFF: // UP 短按松开
NRF_LOG_INFO("UP_KEY_OFF=%x",keyValue);
break;
case DOWN_KEY_OFF: // DWON 短按松开
NRF_LOG_INFO("DOWN_KEY_OFF=%x",keyValue);
break;
case UP_DOWN_KEY_OFF: // UP + DOWN 短按松开
NRF_LOG_INFO("UP_DOWN_KEY_OFF=%x",keyValue);
break;
case UP_KEY_LONG: //UP长按,此事件未调用阻止长按键,此事件会不停触发,直到按键松开
NRF_LOG_INFO("UP_KEY_LONG=%x",keyValue);
break;
case UP_KEY_LONG_OFF: //UP长按松开
NRF_LOG_INFO("UP_KEY_LONG_OFF=%x",keyValue);
break;
case DOWN_KEY_LONG: //DOWN长按
NRF_LOG_INFO("DOWN_KEY_LONG=%x",keyValue);
DisLongKeyContinueResponse(); //阻止长按键,仅触发一次
break;
case DOWN_KEY_LONG_OFF: // DOWN长按松开
NRF_LOG_INFO("DOWN_KEY_LONG_OFF=%x",keyValue);
break;
case UP_DOWN_KEY_LONG: // UP+DOWN长按
NRF_LOG_INFO("UP_DOWN_KEY_LONG=%x",keyValue);
DisLongKeyContinueResponse(); //阻止长按键,仅触发一次
break;
case UP_DOWN_KEY_LONG_OFF: // UP+DOWN长按松开
NRF_LOG_INFO("UP_DOWN_KEY_LONG_OFF=%x",keyValue);
break;
default:
break;
}
}