单片机裸机下简单的按键状态机
用状态机的方式实现按键,避免阻塞延时,影响MCU的执行效率。
我这里写的代码和思路,很大的一部分参考了 ZzJan的博客
基本思路
- 通过按键按下的时长,来判断按键是长按还是短按。
- 按下35ms之前,做消抖处理。
- 按下时间在35~800ms之间,如果按键抬起,则认为已经完成短按动作。
- 按下超过800ms,如果按键抬起,则认为已经完成长按动作。
按键检测结构体
按键结构体成员的作用和意义:
- keyShield 用来设置是否用到该按键,如果不用,可以设置为0,则不用该按键。
- keyCount 用来记录按键按下的时长。
- pinLevel 按键按下时,检测到的电平信号。0表示低电平,1表示高电平。
- virtualLevel 是按键当前的虚拟电平。不同硬件,按键按下后,按键的电平信号不一样。这里统一按下为1,不按下为0。
- keyState 表示按键的状态,一共有长按、短按、无按键按下,三种状态。
- ReadPinLevel 是函数指针,指向按键输入检测函数,方便移植。
typedef struct
{
volatile uint8_t keyShield; /* 按键屏蔽0:屏蔽,1:不屏蔽 */
volatile uint16_t keyCount; /* 按键长按计数 */
volatile uint8_t virtualLevel; /* 当前IO虚拟电平,按下1,抬起0 */
volatile uint8_t pinLevel; /* 按下时IO实际的电平 */
volatile uint8_t keyState; /* 按键状态 */
volatile uint8_t keyEvent; /* 按键事件 */
uint8_t (*ReadPinLevel) (void); /* 读IO电平函数 */
}sKeyComponent_t;
按键结构体初始化
这里是一个结构体数组来,一个结构体数组代表一个按键。如果要增加按键,可以修改 KEY_ALL_NUM 的大小,增加按键。
定义的时候,直接初始化为: 检测按键、计数值为0,没有按键按下、按键等待按键按下、读端口电平函数。
/* 按键变量初始化 */
sKeyComponent_t sKeyComponent[KEY_ALL_NUM] =
{
{1, 0, 0, 0, KEY_STATE_NULL, KEY_EVEN_WAIT, ReadKey1PinLeve},
{1, 0, 0, 0, KEY_STATE_NULL, KEY_EVEN_WAIT, ReadKey2PinLeve},
{1, 0, 0, 0, KEY_STATE_NULL, KEY_EVEN_WAIT, ReadKey3PinLeve},
{1, 0, 0, 0, KEY_STATE_NULL, KEY_EVEN_WAIT, ReadKey4PinLeve},
};
按键状态机
状态机一共分为4个阶段,这四个阶段分别用4个事件表示:
等待按键按下 | 按键消抖 | 确认按键短按 | 确认按键长按 |
---|---|---|---|
有按键按下,进入按键消抖环节 | 35m按键消抖 | 按下35ms~80ms,有按键抬起,短按 | 超过700ms,按键抬起,长按 |
-
等待按键按下
这里一直检测按键,一旦有按键按下,则进入按键消抖事件,否则一直在这个switch case 分支。
代码如下://等待按键按下 case KEY_EVEN_WAIT: if (sKeyComponent[i].virtualLevel == 1) { sKeyComponent[i].keyCount++; sKeyComponent[i].keyEvent = KEY_EVEN_DOWN; } else { sKeyComponent[i].keyEvent = KEY_EVEN_WAIT; } sKeyComponent[i].keyState = KEY_STATE_NULL; /* 无按键按下 */ break;
-
按键消抖
只要检测到按键还处于按下的状态,按下计数增加。
如果按键按下达到达到或者超过按下的时间,则下一次扫描时,进入短按判断。//有按键按下 消抖处理 case KEY_EVEN_DOWN: if (sKeyComponent[i].virtualLevel == 1) { sKeyComponent[i].keyCount++; if (sKeyComponent[i].keyCount >= KEY_SHORT_DOWN_DELAY) /* 按下超过消抖时间 */ { sKeyComponent[i].keyEvent = KEY_EVEN_SHORT; /* 进入短按松开确认 */ } } else { sKeyComponent[i].keyCount--; if (sKeyComponent[i].keyCount == 0) { sKeyComponent[i].keyEvent = KEY_EVEN_WAIT; } else { sKeyComponent[i].keyEvent = KEY_EVEN_DOWN; } } sKeyComponent[i].keyState = KEY_STATE_NULL; /* 无按键按下 */ break;
-
按键短按判断
只要检测到按键处于按下的状态,按键计数加1,按下的时长增加。
如果按键在35~80ms内松开,则认定为短按。
如果按键按下达到700ms以上,则下一次扫描,进入按键长按判断。//确认按键短按 case KEY_EVEN_SHORT: if (sKeyComponent[i].virtualLevel == 1) { sKeyComponent[i].keyCount++; if (sKeyComponent[i].keyCount >= KEY_LONG_DOWN_DELAY) /* 达到或者超出长按的时间 */ { sKeyComponent[i].keyEvent = KEY_EVEN_LONG; /* 进入长按松开确认 */ } sKeyComponent[i].keyState = KEY_STATE_NULL; /* 无按键按下 */ } else { sKeyComponent[i].keyCount = 0; sKeyComponent[i].keyState = KEY_STATE_SHORT; /* 按键短按松开 */ sKeyComponent[i].keyEvent = KEY_EVEN_WAIT; /* 进入等待按键按下 */ } break;
-
按键长按判断
如果到了这一步,只需要检测按键松开即可。按键松开,直接认为是长按。//确认按键长按 case KEY_EVEN_LONG: //按键长按 if (sKeyComponent[i].virtualLevel == 1) { sKeyComponent[i].keyCount++; sKeyComponent[i].keyState = KEY_STATE_NULL; /* 无按键按下 */ } else { sKeyComponent[i].keyCount = 0; sKeyComponent[i].keyState = KEY_STATE_LONG; /* 按键长按松开 */ sKeyComponent[i].keyEvent = KEY_EVEN_WAIT; /* 进入等待按键按下 */ } break;
使用方法
- 1 . 将 KeyScanfTickIrq 这个函数,放在1ms定时器中断中,进行按键扫描。
- 2 . 把 ButtonProces 这个函数放在while (1) 循环中。
源文件全貌
sKeyDownFlag_t gKeyDownFlag[KEY_ALL_NUM];
/* 按键变量初始化 */
sKeyComponent_t sKeyComponent[KEY_ALL_NUM] =
{
{1, 0, 0, 0, KEY_STATE_NULL, KEY_EVEN_WAIT, ReadKey1PinLeve},
{1, 0, 0, 0, KEY_STATE_NULL, KEY_EVEN_WAIT, ReadKey2PinLeve},
{1, 0, 0, 0, KEY_STATE_NULL, KEY_EVEN_WAIT, ReadKey3PinLeve},
{1, 0, 0, 0, KEY_STATE_NULL, KEY_EVEN_WAIT, ReadKey4PinLeve},
};
/******************************************************************************
* func name : ReadKey1PinLeve
* para : NULL
* return : 按键的引脚电平状态 0:低电平 1:高电平
* description : 获取按键的引脚电平状态
******************************************************************************/
uint8_t ReadKey1PinLeve(void)
{
return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8);
}
/******************************************************************************
* func name : ReadKey2PinLeve
* para : NULL
* return : 按键的引脚电平状态 0:低电平 1:高电平
* description : 获取按键的引脚电平状态
******************************************************************************/
uint8_t ReadKey2PinLeve(void)
{
return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7);
}
/******************************************************************************
* func name : ReadKey3PinLeve
* para : NULL
* return : 按键的引脚电平状态 0:低电平 1:高电平
* description : 获取按键的引脚电平状态
******************************************************************************/
uint8_t ReadKey3PinLeve(void)
{
return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6);
}
/******************************************************************************
* func name : ReadKey4PinLeve
* para : NULL
* return : 按键的引脚电平状态 0:低电平 1:高电平
* description : 获取按键的引脚电平状态
******************************************************************************/
uint8_t ReadKey4PinLeve(void)
{
return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5);
}
/******************************************************************************
* func name : KeyStateMacine
* para : NULL
* return : NULL
* description : 按键状态机
******************************************************************************/
void KeyStateMacine(void)
{
uint8_t i;
/* 获取按键引脚的虚拟电平状态 */
for (i = 0; i < KEY_ALL_NUM; i++)
{
/* 如果不需要检测此按键 */
if (sKeyComponent[i].keyShield == 0)
{
continue; /* 跳过此按键,不进行扫描 */
}
/* 按键引脚实际电平判断 */
if (sKeyComponent[i].ReadPinLevel() == sKeyComponent[i].pinLevel)
{
sKeyComponent[i].virtualLevel = 1; /* 当前IO虚拟电平1 按下 */
}
else
{
sKeyComponent[i].virtualLevel = 0; /* 当前IO虚拟电平0 没有按下 */
}
}
/* 按键状态机 */
for (i = 0; i < KEY_ALL_NUM; i++)
{
switch (sKeyComponent[i].keyEvent)
{
//等待按键按下
case KEY_EVEN_WAIT:
if (sKeyComponent[i].virtualLevel == 1)
{
sKeyComponent[i].keyCount++;
sKeyComponent[i].keyEvent = KEY_EVEN_DOWN;
}
else
{
sKeyComponent[i].keyEvent = KEY_EVEN_WAIT;
}
sKeyComponent[i].keyState = KEY_STATE_NULL; /* 无按键按下 */
break;
//有按键按下 消抖处理
case KEY_EVEN_DOWN:
if (sKeyComponent[i].virtualLevel == 1)
{
sKeyComponent[i].keyCount++;
if (sKeyComponent[i].keyCount >= KEY_SHORT_DOWN_DELAY) /* 按下超过消抖时间 */
{
sKeyComponent[i].keyEvent = KEY_EVEN_SHORT; /* 进入短按松开确认 */
}
}
else
{
sKeyComponent[i].keyCount--;
if (sKeyComponent[i].keyCount == 0)
{
sKeyComponent[i].keyEvent = KEY_EVEN_WAIT;
}
else
{
sKeyComponent[i].keyEvent = KEY_EVEN_DOWN;
}
}
sKeyComponent[i].keyState = KEY_STATE_NULL; /* 无按键按下 */
break;
//确认按键短按
case KEY_EVEN_SHORT:
if (sKeyComponent[i].virtualLevel == 1)
{
sKeyComponent[i].keyCount++;
if (sKeyComponent[i].keyCount >= KEY_LONG_DOWN_DELAY) /* 达到或者超出长按的时间 */
{
sKeyComponent[i].keyEvent = KEY_EVEN_LONG; /* 进入长按松开确认 */
}
sKeyComponent[i].keyState = KEY_STATE_NULL; /* 无按键按下 */
}
else
{
sKeyComponent[i].keyCount = 0;
sKeyComponent[i].keyState = KEY_STATE_SHORT; /* 按键短按松开 */
sKeyComponent[i].keyEvent = KEY_EVEN_WAIT; /* 进入等待按键按下 */
}
break;
//确认按键长按
case KEY_EVEN_LONG: //按键长按
if (sKeyComponent[i].virtualLevel == 1)
{
sKeyComponent[i].keyCount++;
sKeyComponent[i].keyState = KEY_STATE_NULL; /* 无按键按下 */
}
else
{
sKeyComponent[i].keyCount = 0;
sKeyComponent[i].keyState = KEY_STATE_LONG; /* 按键长按松开 */
sKeyComponent[i].keyEvent = KEY_EVEN_WAIT; /* 进入等待按键按下 */
}
break;
default:
break;
}
}
}
/******************************************************************************
* func name : KeyScanfTickIrq
* para : NULL
* return : NULL
* description : 按键扫描,获取按键状态
******************************************************************************/
void KeyScanfTickIrq(void)
{
uint8_t i;
KeyStateMacine(); /* 按键状态机 */
for (i = 0; i < KEY_ALL_NUM; i++)
{
if (sKeyComponent[i].keyState == KEY_STATE_SHORT)
{
gKeyDownFlag[i].shortDown = 1; /* 短按标志 */
}
else if (sKeyComponent[0].keyState == KEY_STATE_LONG)
{
gKeyDownFlag[i].longDown = 1; /* 长按标志 */
}
}
}
/******************************************************************************
* func name : ButtonProces
* para : NULL
* return : NULL
* description : 按键处理函数
******************************************************************************/
void ButtonProces(void)
{
if (gKeyDownFlag[0].shortDown == 1)
{
gKeyDownFlag[0].shortDown = 0;
//这里写按键按下后,要执行的功能
}
if (gKeyDownFlag[0].longDown == 1)
{
gKeyDownFlag[0].longDown = 0;
//这里写按键按下后,要执行的功能
}
if (gKeyDownFlag[1].shortDown == 1)
{
gKeyDownFlag[1].shortDown = 0;
//这里写按键按下后,要执行的功能
}
if (gKeyDownFlag[2].shortDown == 1)
{
gKeyDownFlag[2].shortDown = 0;
//这里写按键按下后,要执行的功能
}
if (gKeyDownFlag[3].shortDown == 1)
{
gKeyDownFlag[3].shortDown = 0;
//这里写按键按下后,要执行的功能
}
}
头文件全貌
#ifndef __KEY_H
#define __KEY_H
#include "stm32f10x.h"
#define KEY_ALL_NUM 4 /* 按键输入的数量 */
#define KEY_LONG_DOWN_DELAY 800
#define KEY_SHORT_DOWN_DELAY 35
/* 按键状态 */
typedef enum
{
KEY_STATE_NULL = 0, /* 无按键按下 */
KEY_STATE_SHORT, /* 按键长按 */
KEY_STATE_LONG, /* 按键短按 */
}eKeyState_t;
/* 按键事件 */
typedef enum
{
KEY_EVEN_WAIT = 0, /* 等待按键按下 */
KEY_EVEN_DOWN, /* 按键按下 */
KEY_EVEN_SHORT, /* 按键短按 */
KEY_EVEN_LONG, /* 按键长按 */
}eKeyEven_t;
/* 按键按下标志 */
typedef enum
{
KEY_DOWN_SHORT = 0, /* 按键短按 */
KEY_DOWN_LONG, /* 按键长按 */
}eKeyDownFlag_t;
typedef struct
{
uint8_t longDown;
uint8_t shortDown;
}sKeyDownFlag_t;
typedef struct
{
volatile uint8_t keyShield; /* 按键屏蔽0:屏蔽,1:不屏蔽 */
volatile uint16_t keyCount; /* 按键长按计数 */
volatile uint8_t virtualLevel; /* 当前IO虚拟电平,按下1,抬起0 */
volatile uint8_t pinLevel; /* 按下时IO实际的电平 */
volatile uint8_t keyState; /* 按键状态 */
volatile uint8_t keyEvent; /* 按键事件 */
uint8_t (*ReadPinLevel) (void); /* 读IO电平函数 */
}sKeyComponent_t;
uint8_t ReadKey1PinLeve(void);
uint8_t ReadKey2PinLeve(void);
uint8_t ReadKey3PinLeve(void);
uint8_t ReadKey4PinLeve(void);
void KeyStateMacineIrq(void);
void KeyScanfTickIrq(void);
void ButtonProces(void);
#endif