按键扫描的状态机写法

前言:

按键是嵌入式系统经常会用到的外设,编写也很简单,就是检测IO的高低电平,但是因为按键有需要消抖的问题,在单片机学习中会采用delay延时来进行,但是在具体项目中,延时函数是大忌,会影响系统的实时性,所以一般采用定时器来写,本文将利用状态机的思路配合滴答定时器来编写按键扫描。

程序的编写方法以及讲解

1. 先定义函数中用到的参数

/* 按键相关变量*/
typedef struct
{
    tEnumKeyStateType eState;    
    tEnumKeyType tKeyOld;
    tEnumKeyType tKeyNew;    
    uint8_t u8JitterCnts;	
}tKeyVar;

解释:
tEnumKeyStateType eState:按键当前的状态
tEnumKeyType tKeyOld:上一次按键的键值
tEnumKeyType tKeyNew:当前按键的键值
uint8_t u8JitterCnts:延时消抖的计数值。

/* 按键类型*/
typedef enum
{
    /*无按键按下*/
	ENUMKEYNONE = 0,
    /*设置按键按下*/
	ENUMKEYSET, 
    /*累加按键按下*/
	ENUMKEYADD,
    /*移动按键按下*/
}tEnumKeyType;

解释:这段是一个枚举结构,里面是按键按下之后返回的键值,我们根据键值来判断是哪个按键按下的。

/* 按键状态*/
typedef enum
{
	ENUM_KEY_IDLE = 0,
	ENUM_KEY_WAIT,
}tEnumKeyStateType;

解释:在这里定义两个当前按键的状态,用于简易状态机的状态定义。

2. 函数的编写

void KeyInit(void)
{
    /*初始化按键管脚*/
    DISP_KEY_DIR &= ~KEY_MASK;    
    gtKeyVar.eState = ENUM_KEY_IDLE;
    gtKeyVar.tKeyOld = ENUMKEYNONE;
    gtKeyVar.tKeyNew = ENUMKEYNONE;
}

解释:首先对于按键GPIO的初始化,因为我用的是MSP430,如果采用STM32,只需要把IO配置为输入就行,上拉下拉取决于低电平使能还是高电平使能。

static tEnumKeyType KeyRead(void)
{
    uint8_t u8Key = 0;
    u8Key = DISP_KEY_IN & KEY_MASK;
    /*累加按键*/
    if(u8Key == 0x0A)
    {       
        return ENUMKEYMOVE;
    }
    /*设置按键*/
    else if(u8Key == 0x0C)
    {
        return ENUMKEYADD;
    }
    /*移动按键*/
    else if(u8Key == 0x06)
    {
        return ENUMKEYSET;        
    }   
    /*无按键*/
    else
    {
        return ENUMKEYNONE;
    }
}

解释:此函数是检测IO的高低电平,用于获取当前是哪一个按键按下,在STM32中直接替换为读取当前引脚电平函数即可,写到这里,基本上套路都是相同的,不同的是下面这个函数。

tEnumKeyType KeyScan(void)
{
    tEnumKeyType tKey = ENUMKEYNONE;
    switch (gtKeyVar.eState)
    {
        case ENUM_KEY_IDLE:
        {
            gtKeyVar.tKeyNew = KeyRead();
            
            if(gtKeyVar.tKeyNew != ENUMKEYNONE)
            {
                gtKeyVar.tKeyOld = gtKeyVar.tKeyNew;
                gtKeyVar.u8JitterCnts = KEYJITTERCNTS;               
                gtKeyVar.eState = ENUM_KEY_WAIT;
            }
            break;
        }
        case ENUM_KEY_WAIT:
        {
            if(gtKeyVar.u8JitterCnts == 0)
            {
                gtKeyVar.tKeyNew = KeyRead();
                if(gtKeyVar.tKeyNew == gtKeyVar.tKeyOld)
                {
                    tKey = gtKeyVar.tKeyNew;
                }
                gtKeyVar.eState = ENUM_KEY_IDLE;
            }
            break;
        }
        default:
        {
            break;
        }
    }    
    return tKey;
}

解释:这部分代码是整个程序的核心,程序运行进来时先将tKey清零,然后在switch语句中判断gtKeyVar.eState当前状态,如果当前状态为ENUM_KEY_IDLE,则读取当前的键值并赋值给当前的变量gtKeyVar.tKeyNew,如果当前检测的变量不为0也就是有按键按下时,将当前键值变量gtKeyVar.tKeyNew的值给gtKeyVar.tKeyOld,并且给gtKeyVar.u8JitterCnts进行赋值,“KEYJITTERCNTS”是宏定义的数据,用于调节消抖的时间,我在这里定义的是15,然后就改变当前按键的状态从ENUM_KEY_IDLE变为ENUM_KEY_WAIT。 当函数第二次被轮询时,当前的状态为ENUM_KEY_WAIT,则就会跑第二个case,在第二个case里面检测gtKeyVar.u8JitterCnts是否等于0,如果等于0那么就再次读取键值并赋值给gtKeyVar.tKeyNew,因为在上一次读取键值时gtKeyVar.tKeyOld被赋值也就保存的是上一次的键值,所以判断gtKeyVar.tKeyNew与gtKeyVar.tKeyOld是否相等,如果相等,则认为这个按键被按下并且将键值gtKeyVar.tKeyNew赋值给返回值tKey,然后将按键的状态更改为ENUM_KEY_IDLE,最后返回我们要的键值,这样完成了一次按键的读取,这种写法不仅可以防止误触发,也可以起到消抖并且消抖时间可更改,最重要的就是没有用delay函数,不会让程序死等,不会影响程序的实时性。


到这里,很多人会问,为什么gtKeyVar.u8JitterCnts的值会变成0,我们不是给它赋值了15吗,是因为在滴答定时器中有一条语句是下面这样:

#pragma vector = TIMER0_A0_VECTOR
__interrupt void TIMER0_A0_ISR(void)
{
    /*按键定时,计数单位20ms*/
    if(gtKeyVar.u8JitterCnts > 0)
    {
        gtKeyVar.u8JitterCnts--;
    }
    else
    {
        ;
    }
}

在这个函数中我们对这个变量进行的自减,利用定时器去卡按键之间延时,保证了程序的实时性。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值