按键轮询在中断时触发,只扫描按键电平,不做回调,不然如果回调函数运行时间太久的话,会占用其他任务的运行时间,所以回调函数存消息队列,然后主循环空闲时再做相应的任务处理
单片机操作系统,按键与FIFO_TianYaKe-天涯客的博客-CSDN博客
原理:
按键检测原理:
这里假设按下时为低电平,平时为高电平
1.每隔1ms进入一次按键扫描
2.当按键还没按下时,每隔1ms读取一次电平,直到检测到低电平
3.当第一次检测到低电平,开始按键周期倒数(keyx->checkInterval),一般设置500ms为一个按键周期
4.第一次按下后,定时读取(keyx->timedReadInterval)按下的电平状态,若为低电平,则定时读取低电平的次数(keyx->timedReadRes)加1
5.每次按下时,当设定的读取进入状态(keyx->readKeyLevel)为低电平时,每次检测到低电平,进行软件消抖,10ms后再读取,若为低电平,则按下次数+1(keyx->keyDownTimes),
6.第5步完成后,同时设置读取进入状态(keyx->readKeyLevel)为高电平,重复第5个步骤,直到按键周期结束
7.按键周期结束,定时读取低电平的次数(keyx->timedReadRes)大于某个值,则认为是长按,否则根据按下次数计数(keyx->keyDownTimes)来确定是短按还是双击
key.h
#ifndef __KEY_H
#define __KEY_H
#include "includes.h"
typedef enum {
keyNone = 0,
keyShort,
keyDouble,
keyTriple,
keyLong
} keyState;
typedef struct key_s {
u8 keyNum;
bool bIsChecking; //正在检测
bool bTaskProcessing;
bool readKeyLevel;
keyState preState; //上一个按键状态
keyState state;
u8 timedReadInterval; //定时读取的间隔
u8 keyDownTimes; //一个检测周期按下的次数
u16 timedReadRes; //定时读取,如果在一个检测周期按下的次数为1或2,而每隔n ms读取的低电平次数大于某个值,可认为是长按
u16 shakeInterval; //抖动多少时间后进行检测
u16 checkInterval; //整个检测的持续时间
u16 keyLongInterval; //长按后隔一段时间再检测,避免检测到短按
void (*pfnKeyCallBack)(void);
} KEY_S;
extern KEY_S key1;
extern KEY_S key2;
extern KEY_S key3;
extern KEY_S key4;
extern KEY_S key5;
extern KEY_S key6;
extern KEY_S key7;
extern KEY_S key8;
extern KEY_S key9;
extern KEY_S key10;
#define keyLongTimes 40
#define timedReadIntervalMs 10
#define shakeIntervalMs 10
#define checkIntervalMs 500
#define KEY_ON 0 //按键平时为高电平,按下为低电平
#define KEY_OFF 1
#define KEY1_GPIO_PIN GPIO_Pins_4
#define KEY1_GPIO_PORT GPIOA
#define KEY1_GPIO_CLK RCC_APB2PERIPH_GPIOA
#define KEY2_GPIO_PIN GPIO_Pins_6
#define KEY2_GPIO_PORT GPIOA
#define KEY2_GPIO_CLK RCC_APB2PERIPH_GPIOA
#define KEY3_GPIO_PIN GPIO_Pins_0
#define KEY3_GPIO_PORT GPIOB
#define KEY3_GPIO_CLK RCC_APB2PERIPH_GPIOB
#define KEY4_GPIO_PIN GPIO_Pins_2
#define KEY4_GPIO_PORT GPIOB
#define KEY4_GPIO_CLK RCC_APB2PERIPH_GPIOB
#define KEY5_GPIO_PIN GPIO_Pins_11
#define KEY5_GPIO_PORT GPIOB
#define KEY5_GPIO_CLK RCC_APB2PERIPH_GPIOB
#define KEY6_GPIO_PIN GPIO_Pins_5
#define KEY6_GPIO_PORT GPIOA
#define KEY6_GPIO_CLK RCC_APB2PERIPH_GPIOA
#define KEY7_GPIO_PIN GPIO_Pins_7
#define KEY7_GPIO_PORT GPIOA
#define KEY7_GPIO_CLK RCC_APB2PERIPH_GPIOA
#define KEY8_GPIO_PIN GPIO_Pins_1
#define KEY8_GPIO_PORT GPIOB
#define KEY8_GPIO_CLK RCC_APB2PERIPH_GPIOB
#define KEY9_GPIO_PIN GPIO_Pins_10
#define KEY9_GPIO_PORT GPIOB
#define KEY9_GPIO_CLK RCC_APB2PERIPH_GPIOB
#define KEY10_GPIO_PIN GPIO_Pins_12
#define KEY10_GPIO_PORT GPIOB
#define KEY10_GPIO_CLK RCC_APB2PERIPH_GPIOB
void KeyOsInit(void);
//void KeyLoopTask(void);
void key1TaskProc(void);
void key2TaskProc(void);
void key3TaskProc(void);
void key4TaskProc(void);
void key5TaskProc(void);
void key6TaskProc(void);
void key7TaskProc(void);
void key8TaskProc(void);
void key9TaskProc(void);
void key10TaskProc(void);
#endif
key.c
#include "includes.h"
KEY_S key1;
KEY_S key2;
KEY_S key3;
KEY_S key4;
KEY_S key5;
KEY_S key6;
KEY_S key7;
KEY_S key8;
KEY_S key9;
KEY_S key10;
//按键平时为高电平,按下为低电平
static void Key_GPIO_Config(void)
{
GPIO_InitType GPIO_InitStruct;
RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK, ENABLE);
GPIO_InitStruct.GPIO_Pins = KEY1_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pins = KEY2_GPIO_PIN;
GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pins = KEY3_GPIO_PIN;
GPIO_Init(KEY3_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pins = KEY4_GPIO_PIN;
GPIO_Init(KEY4_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pins = KEY5_GPIO_PIN;
GPIO_Init(KEY5_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pins = KEY6_GPIO_PIN;
GPIO_Init(KEY6_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pins = KEY7_GPIO_PIN;
GPIO_Init(KEY7_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pins = KEY8_GPIO_PIN;
GPIO_Init(KEY8_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pins = KEY9_GPIO_PIN;
GPIO_Init(KEY9_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pins = KEY10_GPIO_PIN;
GPIO_Init(KEY10_GPIO_PORT, &GPIO_InitStruct);
}
void KeyOsInit(void)
{
Key_GPIO_Config();
key1.keyNum = 1;
key1.bIsChecking = 0;
key1.state = keyNone;
key1.checkInterval = checkIntervalMs;
key1.keyDownTimes = 0;
key1.pfnKeyCallBack = key1TaskProc;
key2.keyNum = 2;
key2.bIsChecking = 0;
key2.state = keyNone;
key2.checkInterval = checkIntervalMs;
key2.keyDownTimes = 0;
key2.pfnKeyCallBack = key2TaskProc;
key3.keyNum = 3;
key3.bIsChecking = 0;
key3.state = keyNone;
key3.checkInterval = checkIntervalMs;
key3.keyDownTimes = 0;
key3.pfnKeyCallBack = key3TaskProc;
key4.keyNum = 4;
key4.bIsChecking = 0;
key4.state = keyNone;
key4.checkInterval = checkIntervalMs;
key4.keyDownTimes = 0;
key4.pfnKeyCallBack = key4TaskProc;
key5.keyNum = 5;
key5.bIsChecking = 0;
key5.state = keyNone;
key5.checkInterval = checkIntervalMs;
key5.keyDownTimes = 0;
key5.pfnKeyCallBack = key5TaskProc;
key6.keyNum = 6;
key6.bIsChecking = 0;
key6.state = keyNone;
key6.checkInterval = checkIntervalMs;
key6.keyDownTimes = 0;
key6.pfnKeyCallBack = key6TaskProc;
key7.keyNum = 7;
key7.bIsChecking = 0;
key7.state = keyNone;
key7.checkInterval = checkIntervalMs;
key7.keyDownTimes = 0;
key7.pfnKeyCallBack = key7TaskProc;
key8.keyNum = 8;
key8.bIsChecking = 0;
key8.state = keyNone;
key8.checkInterval = checkIntervalMs;
key8.keyDownTimes = 0;
key8.pfnKeyCallBack = key8TaskProc;
key9.keyNum = 9;
key9.bIsChecking = 0;
key9.state = keyNone;
key9.checkInterval = checkIntervalMs;
key9.keyDownTimes = 0;
key9.pfnKeyCallBack = key9TaskProc;
key10.keyNum = 10;
key10.bIsChecking = 0;
key10.state = keyNone;
key10.checkInterval = checkIntervalMs;
key10.keyDownTimes = 0;
key10.pfnKeyCallBack = key10TaskProc;
}
static bool readKeyGpioLevel(u8 keyNum)
{
bool keyRes = KEY_OFF;
switch ( keyNum ){
case 1:
keyRes = GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN);
break;
case 2:
keyRes = GPIO_ReadInputDataBit(KEY2_GPIO_PORT, KEY2_GPIO_PIN);
break;
case 3:
keyRes = GPIO_ReadInputDataBit(KEY3_GPIO_PORT, KEY3_GPIO_PIN);
break;
case 4:
keyRes = GPIO_ReadInputDataBit(KEY4_GPIO_PORT, KEY4_GPIO_PIN);
break;
case 5:
keyRes = GPIO_ReadInputDataBit(KEY5_GPIO_PORT, KEY5_GPIO_PIN);
break;
case 6:
keyRes = GPIO_ReadInputDataBit(KEY6_GPIO_PORT, KEY6_GPIO_PIN);
break;
case 7:
keyRes = GPIO_ReadInputDataBit(KEY7_GPIO_PORT, KEY7_GPIO_PIN);
break;
case 8:
keyRes = GPIO_ReadInputDataBit(KEY8_GPIO_PORT, KEY8_GPIO_PIN);
break;
case 9:
keyRes = GPIO_ReadInputDataBit(KEY9_GPIO_PORT, KEY9_GPIO_PIN);
break;
case 10:
keyRes = GPIO_ReadInputDataBit(KEY10_GPIO_PORT, KEY10_GPIO_PIN);
break;
default:
break;
}
return keyRes;
}
static void keyScanLoop(KEY_S *keyx)
{
if ( keyx->state == keyLong ){
if (KEY_ON == readKeyGpioLevel(keyx->keyNum)){
return;
} else {
keyx->preState = keyx->state;
keyx->state = keyNone;
}
}
// 不在检测状态时按键按下
if ( 0 == keyx->bIsChecking ){
if (KEY_ON == readKeyGpioLevel(keyx->keyNum)){
keyx->readKeyLevel = KEY_ON;
keyx->bIsChecking = 1;
keyx->keyDownTimes = 0;
keyx->preState = keyx->state;
keyx->state = keyNone;
keyx->timedReadRes = 0;
keyx->checkInterval = checkIntervalMs;
keyx->shakeInterval = shakeIntervalMs;
}
} else {
// 按键周期倒计时
if ( keyx->checkInterval ){
keyx->checkInterval--;
// 定时读取
if ( keyx->timedReadInterval ){
keyx->timedReadInterval--;
} else {
keyx->timedReadInterval = timedReadIntervalMs;
if( KEY_ON == readKeyGpioLevel(keyx->keyNum) ){
keyx->timedReadRes++;
}
}
// 检测状态时按键按下
if ( KEY_ON == keyx->readKeyLevel ) {
// 按键软件消抖
if ( keyx->shakeInterval ){
keyx->shakeInterval--;
} else {
keyx->shakeInterval = shakeIntervalMs;
if (KEY_ON == readKeyGpioLevel(keyx->keyNum)){
keyx->keyDownTimes++;
// 读取的电平反转
keyx->readKeyLevel = KEY_OFF;
}
}
} else {
if (KEY_OFF == readKeyGpioLevel(keyx->keyNum)){
keyx->readKeyLevel = KEY_ON;
}
}
}
// 按键倒计时结束,通过按下次数和定时读取次数判断按键键值
else {
keyx->bIsChecking = 0;
switch (keyx->keyDownTimes){
case keyNone:
keyx->state = keyNone;
return;
case keyShort:
if ( keyLong == keyx->preState ) {
keyx->state = keyNone;
keyx->keyLongInterval = checkIntervalMs;
return;
} else {
keyx->state = keyShort;
}
break;
case keyDouble:
case keyTriple: //按下三次也算双击
keyx->state = keyDouble;
break;
default :
keyx->state = keyLong;
break;
}
if ( keyx->timedReadRes > keyLongTimes ){ //可自定义读取次数
keyx->state = keyLong;
}
keyx->pfnKeyCallBack();
}
}
}
//定时器设置为1ms进一次中断
void TMR4_GLOBAL_IRQHandler(void)
{
if ( TMR_GetINTStatus( TMR4, TMR_INT_Overflow) != RESET )
{
keyScanLoop(&key1);
keyScanLoop(&key2);
keyScanLoop(&key3);
keyScanLoop(&key4);
keyScanLoop(&key5);
keyScanLoop(&key6);
keyScanLoop(&key7);
keyScanLoop(&key8);
keyScanLoop(&key9);
keyScanLoop(&key10);
TMR_ClearITPendingBit(TMR4 , TMR_INT_Overflow);
}
}
void key1TaskProc(void)
{
//do something
}
在网上看到有用按键的软件消抖,但是基本上用的是delay函数,占用了CPU太多资源了,无法实现实时调用;高级一点的用定时器+外部中断的方式,但是无法实现单击、双击、长按功能。
所以这里开发了一种功能,不占用太多CPU资源的同时实现轮询检测,且使用指针结构体,多个按键的情况下可复用性强、移植性强。
本人使用的单片机芯片型号是STM32f103VET6
有纰漏请指出,转载请说明。
学习交流请发邮件 [email protected]
key.h
#ifndef __KEY_H
#define __KEY_H
#include "includes.h"
typedef enum {
keyNone = 0,
keyShort,
keyDouble,
keyTriple,
keyLong
} keyState;
typedef struct key_s {
u8 keyNum;
bool keyDownFlag;
bool bIsChecking; //正在检测
bool bCanReadGpio; //防止消抖后一直读取电平
keyState preState; //上一个按键状态
keyState state;
u8 timedReadInterval; //定时读取的间隔
u8 keyDownTimes; //一个检测周期按下的次数
u16 timedReadRes; //定时读取,如果在一个检测周期按下的次数为1或2,而每隔n ms读取的低电平次数大于某个值,可认为是长按
u16 shakeInterval; //抖动多少时间后进行检测
u16 checkInterval; //整个检测的持续时间
} KEY_S;
extern KEY_S key1;
extern KEY_S key2;
#define keyLongTimes 80
#define timedReadIntervalMs 10
#define shakeIntervalMs 10
#define checkIntervalMs 1000
#define KEY_ON 1 //按键平时为低电平,按下为高电平
#define KEY_OFF 0
#define KEY1_GPIO_PIN GPIO_Pin_0
#define KEY1_GPIO_PORT GPIOA
#define KEY1_GPIO_CLK RCC_APB2Periph_GPIOA
void EXIT_Key_Config(void);
#endif
key.c
#include "includes.h"
KEY_S key1;
KEY_S key2;
static void KeyOsInit(void)
{
key1.keyNum = 1;
key1.bIsChecking = 0;
key1.state = keyNone;
key1.checkInterval = checkIntervalMs;
key1.keyDownTimes = 0;
key2.keyNum = 2;
key2.bIsChecking = 0;
key2.state = keyNone;
key2.checkInterval = checkIntervalMs;
key2.keyDownTimes = 0;
}
static void EXTI_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
void EXIT_Key_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
KeyOsInit();
EXTI_NVIC_Config();
RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK, ENABLE);
GPIO_InitStruct.GPIO_Pin = KEY1_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStruct);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_InitStruct.EXTI_Line = EXTI_Line0;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
}
static void keyScan(KEY_S *keyx){
if ( 0 == keyx->bIsChecking )
{
keyx->bIsChecking = 1;
keyx->keyDownTimes = 0;
keyx->preState = keyx->state;
keyx->state = keyNone;
keyx->timedReadRes = 0;
keyx->checkInterval = checkIntervalMs;
keyx->bCanReadGpio = 1;
keyx->shakeInterval = shakeIntervalMs;
} else {
keyx->shakeInterval = shakeIntervalMs;
keyx->bCanReadGpio = 1;
}
}
static void keyLoopTask(KEY_S *keyx){
if ( 0 == keyx->bIsChecking )
{
return;
}
if ( keyx->checkInterval ){
keyx->checkInterval--;
if ( keyx->shakeInterval ){
keyx->shakeInterval--;
} else {
if ( 1 == keyx->bCanReadGpio ){
if( GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON ){
keyx->keyDownTimes++;
}
keyx->bCanReadGpio = 0;
}
}
if ( keyx->timedReadInterval ){
keyx->timedReadInterval--;
} else {
keyx->timedReadInterval = timedReadIntervalMs;
if( GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON ){
keyx->timedReadRes++;
}
}
} else {
keyx->bIsChecking = 0;
if ( keyx->timedReadRes > keyLongTimes ){ //可自定义读取次数
keyx->state = keyLong;
keyx->keyDownFlag = 1;
return;
}
switch (keyx->keyDownTimes){
case keyNone:
keyx->state = keyNone;
return;
case keyShort:
keyx->state = keyShort;
break;
case keyDouble:
case keyTriple: //按下三次也算双击
keyx->state = keyDouble;
break;
default :
keyx->state = keyLong;
break;
}
keyx->keyDownFlag = 1;
}
}
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
keyScan(&key1);
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
//定时器设置为1ms进一次中断
void TIM3_IRQHandler()
{
if ( TIM_GetITStatus( TIM3, TIM_IT_Update) != RESET )
{
keyLoopTask(&key1);
TIM_ClearITPendingBit(TIM3 , TIM_FLAG_Update);
}
}
main.c
#include "includes.h"
int main(void)
{
TimerOsInit();
Delay_init();
EXIT_Key_Config();
while (1){
if( 1 == key1.keyDownFlag ){
key1.keyDownFlag = 0;
if ( keyShort == key1.state )
{
}
}
}
}