基于STM32F103的按键状态机

单片机裸机下简单的按键状态机

用状态机的方式实现按键,避免阻塞延时,影响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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值