栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

按键扫描的状态机写法

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

按键扫描的状态机写法

前言:

按键是嵌入式系统经常会用到的外设,编写也很简单,就是检测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)
{
    
    if(gtKeyVar.u8JitterCnts > 0)
    {
        gtKeyVar.u8JitterCnts--;
    }
    else
    {
        ;
    }
}

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1018107.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号