V7新版的按键FIFO驱动扩展和移植更简单,组合键也更好用
具体代码和移植方法等下周教程发布后,直接看教程即可。static/image/hrline/4.gif
改成下面的方式,扩展更加方便:
#define HARD_KEY_NUM 8 /* 实体按键个数 */
#define KEY_COUNT (HARD_KEY_NUM + 2) /* 8个独立建 + 2个组合按键 */
/* 使能GPIO时钟 */
#define ALL_KEY_GPIO_CLK_ENABLE() { \
__HAL_RCC_GPIOB_CLK_ENABLE(); \
__HAL_RCC_GPIOC_CLK_ENABLE(); \
__HAL_RCC_GPIOG_CLK_ENABLE(); \
__HAL_RCC_GPIOH_CLK_ENABLE(); \
__HAL_RCC_GPIOI_CLK_ENABLE(); \
};
/* 依次定义GPIO */
typedef struct
{
GPIO_TypeDef* gpio;
uint16_t pin;
uint8_t ActiveLevel; /* 激活电平 */
}X_GPIO_T;
/* GPIO和PIN定义 */
static const X_GPIO_T s_gpio_list = {
{GPIOI, GPIO_PIN_8, 0}, /* K1 */
{GPIOC, GPIO_PIN_13, 0}, /* K2 */
{GPIOH, GPIO_PIN_4, 0}, /* K3 */
{GPIOG, GPIO_PIN_2, 0}, /* JOY_U */
{GPIOB, GPIO_PIN_0, 0}, /* JOY_D */
{GPIOG, GPIO_PIN_3, 0}, /* JOY_L */
{GPIOG, GPIO_PIN_7, 0}, /* JOY_R */
{GPIOI, GPIO_PIN_11, 0}, /* JOY_OK */
};
/* 定义一个宏函数简化后续代码
判断GPIO引脚是否有效按下
*/
static KEY_T s_tBtn = {0};
static KEY_FIFO_T s_tKey; /* 按键FIFO变量,结构体 */
static void bsp_InitKeyVar(void);
static void bsp_InitKeyHard(void);
static void bsp_DetectKey(uint8_t i);
#define KEY_PIN_ACTIVE(id)
/*
*********************************************************************************************************
* 函 数 名: KeyPinActive
* 功能说明: 判断按键是否按下
* 形 参: 无
* 返 回 值: 返回值1 表示按下(导通),0表示未按下(释放)
*********************************************************************************************************
*/
static uint8_t KeyPinActive(uint8_t _id)
{
uint8_t level;
if ((s_gpio_list.gpio->IDR & s_gpio_list.pin) == 0)
{
level = 0;
}
else
{
level = 1;
}
if (level == s_gpio_list.ActiveLevel)
{
return 1;
}
else
{
return 0;
}
}
/*
*********************************************************************************************************
* 函 数 名: IsKeyDownFunc
* 功能说明: 判断按键是否按下。单键和组合键区分。单键事件不允许有其他键按下。
* 形 参: 无
* 返 回 值: 返回值1 表示按下(导通),0表示未按下(释放)
*********************************************************************************************************
*/
static uint8_t IsKeyDownFunc(uint8_t _id)
{
/* 实体单键 */
if (_id < HARD_KEY_NUM)
{
uint8_t i;
uint8_t count = 0;
uint8_t save = 255;
/* 判断有几个键按下 */
for (i = 0; i < HARD_KEY_NUM; i++)
{
if (KeyPinActive(i))
{
count++;
save = i;
}
}
if (count == 1 && save == _id)
{
return 1; /* 只有1个键按下时才有效 */
}
return 0;
}
/* 组合键 K1K2 */
if (_id == HARD_KEY_NUM + 0)
{
if (KeyPinActive(KID_K1) && KeyPinActive(KID_K2))
{
return 1;
}
else
{
return 0;
}
}
/* 组合键 K2K3 */
if (_id == HARD_KEY_NUM + 1)
{
if (KeyPinActive(KID_K2) && KeyPinActive(KID_K3))
{
return 1;
}
else
{
return 0;
}
}
return 0;
}
多次组合键不会出错了:
我的板子到了,回头来个V5,V6,V7全家福:lol 扫描按键?为什么不用中断触发呢? diiiiiii 发表于 2019-3-23 14:20
扫描按键?为什么不用中断触发呢?
从裸机的角度分析
中断方式:中断方式可以快速地检测到按键按下的,并执行相应的按键程序,但实际情况是由于按键的机械抖动特性,在程序进入中断后必须进行滤波处理才能判定是有效的按键事件。如果每个按键都是独立的接一个IO引脚,需要我们给每个IO都设置一个中断,程序中过多的中断会影响系统的稳定性,中断方式跨平台移植困难。
查询方式:查询方式有一个最大的缺点就是需要程序定期的去执行查询,耗费一定的系统资源。实际上耗费不了多大的系统资源,因为这种查询方式也只是查询按键是否按下,按键事件的执行还是在主程序里面实现。
从OS的角度分析
中断方式:在OS中要尽可能少用中断方式,因为在RTOS中过多的使用中断会影响系统的稳定性和可预见性(抢占式调度的OS基本没有可预见性)。只有比较重要的事件处理需要用中断的方式。
查询方式:对于用户按键推荐使用这种查询方式来实现,现在的OS基本都带有CPU利用率的功能,这个按键FIFO占用的还是很小的,基本都在%1以下 eric2013 发表于 2019-3-23 14:54
从裸机的角度分析
中断方式:中断方式可以快速地检测到按键按下的,并执行相应的按键程序,但实际情 ...
谢谢回复。我之前认为在大多数时间里按键都是空闲的,即没有按下。再占用定时器去不断扫描会浪费资源。 这个按键扫描方法,支持 单个按键 双击,3击 扫描检测不?
如果不支持,该如何修改啊 ?? 工程上基本都是定时扫描,放后台或者某个优先级低的定时里面做就行。 hpdell 发表于 2019-3-24 19:55
这个按键扫描方法,支持 单个按键 双击,3击 扫描检测不?
如果不支持,该如何修改啊 ??
这种要求的话,程序无需修改,仅需在按键按下后记录当前时刻,下次再按下时求个差值就行。 eric2013 发表于 2019-3-25 02:41
这种要求的话,程序无需修改,仅需在按键按下后记录当前时刻,下次再按下时求个差值就行。
好的,我试试看 我把按键个数改成2, GPIO和PIN定义那里改成我的按键,然后打印键值。两个按键键值相同,也不知道咋回事? 晚起的鸟 发表于 2020-9-25 09:40
我把按键个数改成2, GPIO和PIN定义那里改成我的按键,然后打印键值。两个按键键值相同,也不知道咋回事?
上修改后的代码看看。 晚起的鸟 发表于 2020-9-25 09:40
我把按键个数改成2, GPIO和PIN定义那里改成我的按键,然后打印键值。两个按键键值相同,也不知道咋回事?
这个问题就是下面这段代码的问题:
除了组合键之外,其他按键同时只能检测一个按键
static uint8_t IsKeyDownFunc(uint8_t _id)
{
/* 实体单键 */
if (_id < HARD_KEY_NUM)
{
uint8_t i;
uint8_t count = 0;
uint8_t save = 255;
/* 判断有几个键按下 */
for (i = 0; i < HARD_KEY_NUM; i++)
{
if (KeyPinActive(i))
{
count++;
save = i;
}
}
if (count == 1 && save == _id)
{
return 1; /* 只有1个键按下时才有效 */
}
return 0;
} gallop020142 发表于 2020-9-28 09:51
这个问题就是下面这段代码的问题:
除了组合键之外,其他按键同时只能检测一个按键
static uint8_t IsK ...
他说的是检查到的两个按键的键值相同,跟这个没关系。 有BUG,长按一个按键的时候,去单击另一个按键会识别成长按的按键单击 核弹 发表于 2022-2-24 09:53
有BUG,长按一个按键的时候,去单击另一个按键会识别成长按的按键单击
自己的程序里面加个简单的变量处理即可,检测到长按后设置变量,其它按键检查到次变量设置读取出来的消息不错处理,
本质原因是这个按键FIFO没有做长按+另一个键的消息处理。
本帖最后由 mojinpan 于 2022-2-25 11:20 编辑
我提供一个更精简的按键驱动,支持各种单击,双击,长按,shift按键等多种功能,而且支持到最多64个按键全并发处理
https://github.com/mojinpan/keykey.h
/** ****************************************************************************
* @file key.h
* @author mojinpan
* @copyright (c) 2018 - 2020 mojinpan
* @brief 按键驱动
*
* @par 功能说明
* @details
* 1.支持最大64个按键(数量可配置)
* 2.支持按键消抖处理(可滤掉小于2个扫描周期的毛刺)
* 3.支持按键环形缓冲区(长度可配置)
* 4.支持4种不同按键模式:A:短按键 B:shift键 C:长按单发 D:长按连发
* 5.短按键 :短按直接输出键值
* 6.shift键:点击后除了输出一次短按键外,还将后续所有其他按键的键值改为,shift键+按键值(包括长短按键)
* 7.长按连发:长按一定时间后连续输出按键键值
* 8.长按单发:长按一定时间后反码输出键值(仅一次)
* 9.4种不同按键模式可相互叠加使用,所有按键功能都能并发输出
*
* @par 移植说明
* @details
* 1.根据具体情况编写KeyIOread()和KeyInit().
* 2.每20ms~50ms调用KeyScan()扫描按键,调用KeyGetBufLen()判断是否有按键,调用KeyGet()获得按键值.
*******************************************************************************/
#ifndef __KEY__
#define __KEY__
/*******************************************************************************
Header Files 头文件
*******************************************************************************/
#include "bsp.h"
#include "stdbool.h"
#include "stdint.h"
/*******************************************************************************
Key Scancode 按键扫描码
*******************************************************************************/
///* @brief 按键键值,每个按键占1bit,不得重复
typedef enum
{
KEY_UP = 0x0001,
KEY_DN = 0x0002,
KEY_LT = 0x0004,
KEY_RT = 0x0008,
KEY_ENT = 0x0010,
KEY_ESC = 0x0020,
KEY_INC = 0x0040,
KEY_DEC = 0x0080,
KEY_FUN = 0x0100
KEY_UP_L = 0xFFFE,
KEY_DN_L = 0xFFFD,
KEY_UP_SF = 0x0101,
KEY_DN_SF = 0x0102,
KEY_LT_SF = 0x0104,
KEY_RT_SF = 0x0108,
KEY_ENT_SF = 0x0110,
KEY_ESC_SF = 0x0120,
KEY_INC_SF = 0x0140,
KEY_DEC_SF = 0x0180
} KEY_value;
/*******************************************************************************
Drive Config 驱动配置
*******************************************************************************/
#define KEY_MAX_NUM 11 //定义最大按键数量(1~64)
#define KEY_BUF_SIZE 8 //定义按键缓冲区大小(2,4,8,16,32,64,128,256)
#define KEY_LONG_EN 1 //定义是否支持长按键(1 or 0)
#define KEY_SHORT_SHIFT KEY_FUN //定义shift键,短按有效(0 or 按键扫描码)
#if KEY_LONG_EN > 0
#define KEY_PRESS_DLY 40 //定义长按键延时n个扫描周期开始判定有效值(1~256)
#define KEY_PRESS_TMR 5 //定义长按键n个扫描周期为1次有效扫描值(1~256)
#define KEY_LONG_SHIFT KEY_UP|KEY_DN//长按键反码输出(仅一次),未定义的长按连续输出(0 or 按键扫描码)
#endif
/*******************************************************************************
Function declaration 函数声明
*******************************************************************************/
void KeyInit(void); //按键初始化函数
void KeyScan(void); //按键扫描函数,20ms~50ms定时调用
uint8_t KeyGetBufLen(void); //按键判断函数,判断是否有按键按下
uint32_t KeyGet(void); //按键获得函数,获取具体按键值,无按键时返回0
void KeyFlush(void); //清空按键buf
#endif
key.c
/** ****************************************************************************
* @file key.c
* @author mojinpan
* @copyright (c) 2018 - 2020 mojinpan
* @brief 按键驱动
*
* @version V0.1
* @date 2011-3-2
* @details
* 1.支持矩阵键盘和IO按键两种
* 2.支持长按键和短按键两种触发方式。
*
* @version V0.2
* @date 2011-3-5
* @details
* 1.最大按键数量16个,可继续扩充至64个.
* 2.支持长按键和短按键并行触发.
* 3.增加短按键部分改为按键释放时返回.
*
* @version V0.3
* @date 2013-11-4
* @details
* 1.重写按键获取的逻辑,长按键算法不成熟,暂时去掉
* 2.矩阵按键没用到,暂时去掉
* 3.增加按键消抖算法
*
* @version V0.4
* @date 2013-11-6
* @details
* 1.增加环形buf
* 2.重写接口函数,便于在OS环境中调用,当然裸奔也支持.
* c_cpp_properties.json
* @version V0.5
* @date 2013-11-6
* @details
* 1.增加长按键的支持
* 2.增加shift键的支持
* 3.增加按键数量的定义,定义后根据按键数量选择合适的数据类型,从而节省ram和rom
* 4.处于兼容性的考虑,将最大按键支持数量由64个缩减至32个
*
* @version V0.6
* @date 2013-11-24
* @details
* 1.修正长按键输出后,还输出短按键的bug
* 2.修改的KeyScan()的逻辑,将长按键细分为长按键连续输出和长按键反码输出(仅一次)两种方式
* 3.shift键按下时也提供按键值输出,方便应用程序判断shift键的状态
* 4.由于第3点的修改,短按键和shift键不在冲突,即长按键/短按键/shift键相互独立
* 5.将无按键返回0xff,改成返回0
* 6.增加一个KeyFlush()函数,用于清空buf
*
* @version V0.7
* @date 2019-04-26
* @details
* 1.参照liunx上kfifo的代码优化环形buf的算法.
* 2.将KeyHit()函数重命名为KeyBufLen()函数,将返回有无按键改为返回按键缓冲数量.
* 3.KEY_TYPE 改为使用stdint.h中的数据类型定义,并重新启用64个按键的支持
*******************************************************************************/
#include "bsp.h"
#include "key.h"
/*******************************************************************************
Macro Definition 宏定义
*******************************************************************************/
#if KEY_MAX_NUM > 32
#define KEY_TYPE uint64_t
#elif KEY_MAX_NUM > 16
#define KEY_TYPE uint32_t
#elif KEY_MAX_NUM > 8
#define KEY_TYPE uint16_t
#else
#define KEY_TYPE uint8_t
#endif
/*******************************************************************************
Global Variables 全局变量
*******************************************************************************/
staticKEY_TYPE PreScanKey= 0; //前次按键扫描值
staticKEY_TYPE PreReadKey= 0; //前次按键读取值
staticKEY_TYPE KeyShift = 0; //shift按键记录
staticKEY_TYPE KeyMask = 0; //按键掩码
#if LONG_KEY_EN > 0
staticuint8_tKeyPressTmr = 0; //长按键判断周期
#endif
static KEY_TYPE KeyBuf; //环形buf
static uint8_tKeyBufInIdx= 0; //buf入指针
static uint8_tKeyBufOutIdx = 0; //buf出指针
/** ****************************************************************************
@brief按键扫描函数,需根据实际硬件情况进行移植
@note 所有按键均必须高电平有效
*******************************************************************************/
__weak static KEY_TYPEKeyIOread( void )
{
KEY_TYPE KeyScanCode=0;
KeyScanCode |= GPIO_IN(K_UP ) ? 0 : KEY_UP ;
KeyScanCode |= GPIO_IN(K_DN ) ? 0 : KEY_DN ;
KeyScanCode |= GPIO_IN(K_LT ) ? 0 : KEY_LT ;
KeyScanCode |= GPIO_IN(K_RT ) ? 0 : KEY_RT ;
KeyScanCode |= GPIO_IN(K_ENT) ? 0 : KEY_ENT;
KeyScanCode |= GPIO_IN(K_ESC) ? 0 : KEY_ESC;
KeyScanCode |= GPIO_IN(K_INC) ? 0 : KEY_INC;
KeyScanCode |= GPIO_IN(K_DEC) ? 0 : KEY_DEC;
KeyScanCode |= GPIO_IN(K_FUN) ? 0 : KEY_FUN;
return KeyScanCode;
}
/** ****************************************************************************
@brief从buf中获得一个按键值
@return 返回按键值,无按键返回0xFF
*******************************************************************************/
static KEY_TYPE KeyBufOut (void)
{
if(KeyBufOutIdx == KeyBufInIdx) return 0; //buf空
return KeyBuf; //返回按键值
}
/** ****************************************************************************
@brief将按键值放入buf中
@paramcode: 需放入buf的按键值
*******************************************************************************/
static voidKeyBufIn (KEY_TYPE code)
{
if(KeyBufInIdx - KeyBufOutIdx == KEY_BUF_SIZE)
{
KeyBufOutIdx++;//buf满则放弃最早的一个按键值
}
KeyBuf = code ;
}
/** ****************************************************************************
@brief按键扫描
@details
1.功能说明
a.按键消抖
b.捕捉长按键和短按键
c.根据设置将按键分成短按键/长按连续建/短按shift键/长按shift键4种方式写入buf
2.算法说明
a.按键获取算法
1).NowKey & PreKey : 电平触发
2).NowKey ^ PreKey : 边缘触发
3).NowKey & (NowKey ^ PreKey)或(~PreKey) & NowKey : 上升沿触发
4).PreKey & (NowKey ^ PreKey)或PreKey & (~NowKey) : 下降沿触发
b.滤波算法
1).PreScanKey & NowScanKey : 电平触发
2).PreReadKey & (PreScanKey ^ NowScanKey) : 采样保持
3).NowReadKey = 1) | 2) : 带采样保持的电平触发
3.调用说明
a.对下调用的KeyIOread()中,有效按键必须为高电平,且每个bit表示一个按键值
b.应用调用该函数的间隔应该在20ms~50ms,在调用间隔内的毛刺均可滤除。
*******************************************************************************/
void KeyScan(void)
{
KEY_TYPE NowScanKey = 0; //当前按键值扫描值
KEY_TYPE NowReadKey = 0; //当前按键值
//KEY_TYPE KeyPressDown = 0; //按键按下
KEY_TYPE KeyRelease = 0; //按键释放
NowScanKey= KeyIOread();
NowReadKey= (PreScanKey&NowScanKey)| //电平触发
PreReadKey&(PreScanKey^NowScanKey); //采样保持(即消抖)
//KeyPressDown= NowReadKey & (NowReadKey ^ PreReadKey); //上升沿触发
KeyRelease = PreReadKey & (NowReadKey ^ PreReadKey); //下降沿触发
#if LONG_KEY_EN > 0
if(NowReadKey == PreReadKey && NowReadKey) //用电平触发做长按键的有效判据
{
KeyPressTmr++;
if(KeyPressTmr >= KEY_PRESS_TMR) //长按判断周期到,保存相应长按键值
{
if(NowReadKey & ~(KEY_LONG_SHIFT)) //长按键模式一
{
KeyBufIn(NowReadKey | KeyShift); //长按键重复输出
}
else if(NowReadKey & (KEY_LONG_SHIFT) & ~KeyMask ) //长按键模式二
{
KeyBufIn(~(NowReadKey | KeyShift)); //长按键反码输出作为第二功能键
}
KeyPressTmr = 0; //重置连按周期,准备获取下1个长按键
KeyMask = NowReadKey;
}
}
else{
KeyPressTmr = KEY_PRESS_DLY; //按键变化,重置按键判断周期
}
#endif
if(KeyRelease)
{ //短按键判断
if(KeyRelease &(~KeyMask)&& !NowReadKey)
{
KeyShift ^= (KeyRelease & (KEY_SHORT_SHIFT)); //shift按键码(边缘触发)
KeyBufIn(KeyRelease | KeyShift | PreReadKey);
}
else
{
KeyMask = 0;
}
}
PreScanKey = NowScanKey;
PreReadKey = NowReadKey;
}
/** ****************************************************************************
@brief按键初始化
*******************************************************************************/
__weak void KeyInit( void )
{
//按键初始化,输入,浮空,低速
GpioInit2Input(K_UP,GPIO_NOPULL,GPIO_SPEED_FREQ_LOW);
GpioInit2Input(K_DN,GPIO_NOPULL,GPIO_SPEED_FREQ_LOW);
GpioInit2Input(K_LT,GPIO_NOPULL,GPIO_SPEED_FREQ_LOW);
GpioInit2Input(K_RT,GPIO_NOPULL,GPIO_SPEED_FREQ_LOW);
GpioInit2Input(K_ENT ,GPIO_NOPULL,GPIO_SPEED_FREQ_LOW);
GpioInit2Input(K_ESC ,GPIO_NOPULL,GPIO_SPEED_FREQ_LOW);
GpioInit2Input(K_INC ,GPIO_NOPULL,GPIO_SPEED_FREQ_LOW);
GpioInit2Input(K_DEC ,GPIO_NOPULL,GPIO_SPEED_FREQ_LOW);
GpioInit2Input(K_FUN ,GPIO_NOPULL,GPIO_SPEED_FREQ_LOW);
}
/** ****************************************************************************
@brief从buf中获得一个按键值
@return 回按键值,无按键返回0xFF
*******************************************************************************/
uint32_tKeyGet(void)
{
return (uint32_t)KeyBufOut();
}
/** ****************************************************************************
@brief判断是否有按键按下
@return buf中按键的数量
*******************************************************************************/
uint8_tKeyGetBufLen (void)
{
return KeyBufInIdx - KeyBufOutIdx;
}
/** ****************************************************************************
@brief清空buff中所有按键值
*******************************************************************************/
void KeyFlush(void)
{
KeyBufOutIdx = KeyBufInIdx;
}
eric2013 发表于 2022-2-24 10:32
自己的程序里面加个简单的变量处理即可,检测到长按后设置变量,其它按键检查到次变量设置读取出来的消息 ...
这个功能是否可以规范到现在的按键处理函数中。因为用的按键都是不能自恢复的,会使用同时几个按键按下的情况处理 wgco98 发表于 2024-2-21 18:59
这个功能是否可以规范到现在的按键处理函数中。因为用的按键都是不能自恢复的,会使用同时几个按键按下的 ...
好的,如果后面还有机会的话,升级个新版本。 请问如果按键按下时,是低电平,即按键按下时,io口接地,这个程序要怎么改?
/* GPIO和PIN定义 */
static const X_GPIO_T s_gpio_list = {
{GPIOA, GPIO_PIN_0, 1}, /* K1 */
{GPIOC, GPIO_PIN_13, 1}, /* K2 */
{GPIOG, GPIO_PIN_13, 0}, /* K3 */
{GPIOG, GPIO_PIN_14, 0}, /* K4 */
};
是只需要将这里的激活状态设置为0嘛? 桃子不逃 发表于 2024-3-21 20:58
请问如果按键按下时,是低电平,即按键按下时,io口接地,这个程序要怎么改?
/* GPIO和PIN定义 */
stati ...
对,就是这个。 FREETROS系统下的按键FIFO八个串口FIFO例程有吗?
页:
[1]