全功能按键非阻塞式实现 按键单击 双击 长按

标志位定义.png

设置标志位.png

状态转移图.png
代码移植流程:
Key.h中
确定KEY_COUNT 按键的个数;
定义每个按键的名称+ 搜引号.
Key.c中
实现Key_Init 按键引脚初始化的代码;
实现Key_GetState 读取按键引脚状态的代码.
main.c中
实现一个1ms定时中断,并在中断中调用Key_Tick函数;
调用Key_Check函数,若指定按键的指定时间发生,者返回1,否则返回0.
注意事项:
- 在一轮主循环中,只能检查一次指定按键的指定事件(KEY_HOLD除外),若确实需要检查多次,则可先调用一次Key_Check函数并用变量存储返回值,后续多次判断此变量即可.
- 双击事件的存在,使得单击事件响应有一些延迟,若程序中没有使用到双击,则可将双击时间阈值改为0,这样可以消除单击事件的延迟.
- 按键产生了事件,对应的标志位就会一直置1,直到检查了此事件,才会自动清0,这在模式切换时可能会导致误动作(例如:模式1中没有检查过某个标志位,但是按下过按键,此标志位已经置1,随后切换为模式2,开始检查此标志位,那么一旦进入模式2,此标志位的动作就会立刻响应),解决办法是在切换模式时,统一将所有的Key_Flag清0,避免上一个模式的按键标志位对这个模式产生影响.
代码部分
//Key.h
#ifndef __KEY_H
#define __KEY_H
//最多四个按键.按键数量
#define KEY_COUNT 4
//索引号0~3, 宏定义
#define KEY_1 0
#define KEY_2 1
#define KEY_3 2
#define KEY_4 3
//定义位的掩码.
#define KEY_HOLD 0x01
#define KEY_DOWN 0x02
#define KEY_UP 0x04
#define KEY_SINGLE 0x08
#define KEY_DOUBLE 0x10
#define KEY_LONG 0x20
#define KEY_REPEAT 0x40
void Key_Init(void);
uint8_t Key_Check(uint8_t n, uint8_t Flag);
void Key_Tick(void);
#endif
//Key.c
#include "stm32f10x.h" // Device header
#include "Key.h"
#define KEY_PRESSED 1
#define KEY_UNPRESSED 0
#define KEY_TIME_DOUBLE 200 //双击间隔阈值
#define KEY_TIME_LONG 2000//长按触发时间.
#define KEY_TIME_REPEAT 100 //长按间隔中的多次触发间隔.决定数据增加速度.
uint8_t Key_Flag[KEY_COUNT];//按键数量4.
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//下接按键(常用)(上拉输入):按下为低电平0,松开为高电平1
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//上接按键(下拉输入):按下为高电平1,松开为低电平0.
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;//下拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//n代表具体的按键编号0~3,与数组的索引号一致.
//返回目标按键是否按下了.
uint8_t Key_GetState(uint8_t n)//按键的按下状态.
{
if (n == KEY_1)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
return KEY_PRESSED;
}
}
else if (n == KEY_2)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
{
return KEY_PRESSED;
}
}
else if (n == KEY_3)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 1)
{
return KEY_PRESSED;
}
}
else if (n == KEY_4)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15) == 1)
{
return KEY_PRESSED;
}
}
return KEY_UNPRESSED;
}
uint8_t Key_Check(uint8_t n, uint8_t Flag)
{
if (Key_Flag[n] & Flag)
{
if (Flag != KEY_HOLD)
{
Key_Flag[n] &= ~Flag;//指定位自动清零
}
return 1;//指定标志位
}
return 0;
}
void Key_Tick(void)//事件监测,1ms执行一次.
{
static uint8_t Count, i;
static uint8_t CurrState[KEY_COUNT], PrevState[KEY_COUNT];
static uint8_t S[KEY_COUNT];
//同时实现 定时计数/时间到标志位 定时完成自动关闭这些任务.
static uint16_t Time[KEY_COUNT];
for (i = 0; i < KEY_COUNT; i ++)
{
if (Time[i] > 0)
{
Time[i] --;
}
}
Count ++;
if (Count >= 20)//过滤抖动
{
Count = 0;
for (i = 0; i < KEY_COUNT; i ++)
{
PrevState[i] = CurrState[i]; //前一状态
CurrState[i] = Key_GetState(i);
if (CurrState[i] == KEY_PRESSED)
{
Key_Flag[i] |= KEY_HOLD;//最低位置1, KEY_HOLD=0x01
}
else
{
Key_Flag[i] &= ~KEY_HOLD;//最低位清零
}
if (CurrState[i] == KEY_PRESSED && PrevState[i] == KEY_UNPRESSED)
{
Key_Flag[i] |= KEY_DOWN;//0x02
}
if (CurrState[i] == KEY_UNPRESSED && PrevState[i] == KEY_PRESSED)
{
Key_Flag[i] |= KEY_UP;//0x04
}
if (S[i] == 0)//状态机检测.
{
if (CurrState[i] == KEY_PRESSED)
{
Time[i] = KEY_TIME_LONG;
S[i] = 1;//转入状态机1
}
}
else if (S[i] == 1)
{
if (CurrState[i] == KEY_UNPRESSED)
{ //递减计数器赋初值,双击时间200ms.
Time[i] = KEY_TIME_DOUBLE;
S[i] = 2;//
}
else if (Time[i] == 0)
{ //长按时间到,进入状态4,按键已长按.
Time[i] = KEY_TIME_REPEAT;
Key_Flag[i] |= KEY_LONG;//长按
S[i] = 4;
}
}
else if (S[i] == 2)
{
if (CurrState[i] == KEY_PRESSED)
{ //设定
Key_Flag[i] |= KEY_DOUBLE;//双击
S[i] = 3;
}
else if (Time[i] == 0)
{
Key_Flag[i] |= KEY_SINGLE;
S[i] = 0;
}
}
else if (S[i] == 3)
{
if (CurrState[i] == KEY_UNPRESSED)
{//检测按键松开,回到状态0
S[i] = 0;
}
}
else if (S[i] == 4)
{
if (CurrState[i] == KEY_UNPRESSED)
{//按键松开
S[i] = 0;
}
else if (Time[i] == 0)
{//重设重复时间
Time[i] = KEY_TIME_REPEAT;
Key_Flag[i] |= KEY_REPEAT;//重复按键.
S[i] = 4;
}
}
}
}
}
//main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "Timer.h"
uint16_t Num1;
uint16_t Num2;
int main(void)
{
OLED_Init();
Key_Init();
Timer_Init();
OLED_ShowString(1, 1, "Num1:");
OLED_ShowString(2, 1, "Num2:");
while (1)
{
/*示例1*/
// if (Key_Check(KEY_1, KEY_HOLD))
// { //key1按住
// Num1 = 1;
// }
// else
// { //key1释放
// Num1 = 0;
// }
// if (Key_Check(KEY_2, KEY_HOLD))
// {
// Num2 = 1;
// }
// else
// {
// Num2 = 0;
// }
/*示例2*/
// if (Key_Check(KEY_1, KEY_DOWN))
// { //按下key1瞬间,num1自增
// Num1 ++;
// }
// if (Key_Check(KEY_2, KEY_UP))
// { //松开key2瞬间,num2增加.
// Num2 ++;
// }
/*示例3*/
// if (Key_Check(KEY_1, KEY_SINGLE))
// { //按键单击
// Num1 ++;
// }
// if (Key_Check(KEY_1, KEY_DOUBLE))
// { //双击, 按键双击
// Num1 += 100;
// }
// if (Key_Check(KEY_2, KEY_SINGLE))
// {
// Num1 --;
// }
// if (Key_Check(KEY_2, KEY_DOUBLE))
// {
// Num1 -= 100;
// }
// if (Key_Check(KEY_1, KEY_LONG) || Key_Check(KEY_2, KEY_LONG))
// { //长按时间,清零
// Num1 = 0;
// }
/*示例4*/
// if (Key_Check(KEY_1, KEY_SINGLE) || Key_Check(KEY_1, KEY_REPEAT))
// { //按键单击或 长按重复触发(长按快速自增).
// Num1 ++;
// }
// if (Key_Check(KEY_2, KEY_SINGLE) || Key_Check(KEY_2, KEY_REPEAT))
// {
// Num1 --;
// }
// if (Key_Check(KEY_3, KEY_SINGLE))
// {
// Num1 = 0;
// }
// if (Key_Check(KEY_3, KEY_LONG))
// {
// Num1 = 9999;
// }
/*示例5: 组合按键,类似Ctrl+C */
// uint8_t K1_UP = Key_Check(KEY_1, KEY_UP);
// uint8_t K2_UP = Key_Check(KEY_2, KEY_UP);
// if (K1_UP && Key_Check(KEY_3, KEY_HOLD))
// {
// Num1 ++;
// }
// if (K2_UP && Key_Check(KEY_3, KEY_HOLD))
// {
// Num1 --;
// }
// if (K1_UP && Key_Check(KEY_4, KEY_HOLD))
// {
// Num2 ++;
// }
// if (K2_UP && Key_Check(KEY_4, KEY_HOLD))
// {
// Num2 --;
// }
OLED_ShowNum(1, 6, Num1, 5);
OLED_ShowNum(2, 6, Num2, 5);
}
}
//定时器中断,1ms触发一次.
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Key_Tick();//1ms定时中断
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}