找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 54|回复: 0
打印 上一主题 下一主题
收起左侧

单片机通用的复杂按键状态机,可实现单击后长按,双击后长按及更多功能

[复制链接]
跳转到指定楼层
楼主
ID:1155837 发表于 2026-2-5 02:02 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
下面的代码实现了多种按键输入处理,基于switch状态机实现,可实现单击双击三击长按,单击后长按,双击后长按。
多平台可用,只需要修改KEYINPUT即可。
如果是51单片机,改为#define KEYINPUT P32
如果是32单片机,改为#define KEYINPUT GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)
使用方法:可以在定时器中断中直接调用KEY_HANDLER函数,也可以在定时器中断设允许标志,主循环执行KEY_HANDLER
KEY_HANDLER函数中的不同case,我现在填入的是测试用的串口打印函数,可以直接改为你需要的功能代码。

本函数无法连续返回同一个按键状态,比如在三次定时器中断中状态先后变化为NOKEY→LONGKEY→NOKEY,所有的按键装填,只能返回一次!
如果需要持续长按来修改某个变量值的效果,推荐在KEY_HANDLER函数中仅设允许位,比如这样
            case SINGLETOLONGKEY://单击后长按
                decreaseduty = 1;//减少占空比标志
                break;


然后在主循环中执行下面的,即按键抬起后,清零标志
        if(KEYINPUT){//P32 == 1,按键抬起
            increaseduty = 0;//清空占空比调节标志
            decreaseduty = 0;
        }

下面为全部代码:
  1. #define KEYINPUT 15//按键输入为P32
  2. #define NOKEY 0//无
  3. #define SINGLEKEY 1//单键
  4. #define DOUBLEKEY 2//双键
  5. #define TRIPLEKEY 3//三键
  6. #define LONGKEY 4//长键
  7. #define SINGLETOLONGKEY 5//单击后长按键
  8. #define DOUBLETOLONGKEY 6//双击后长按键
  9. #define KEYSTATE0 0
  10. #define KEYSTATE1 1
  11. #define KEYSTATE2 2
  12. #define KEYSTATE3 3
  13. unsigned char KEY_DRIVER(void){
  14.     static unsigned char keystate = KEYSTATE0;
  15.     static unsigned char keytime = 0;
  16.     bool keypress;
  17.     unsigned char keyreturn = NOKEY;
  18.     keypress = digitalRead(Button);//读取P32电平
  19.     switch(keystate){
  20.         case KEYSTATE0://按键初始状态,按下后转换到消抖与确认态,用定时器中断间隔实现消抖
  21.             if(!keypress){//P32==0
  22.                 keystate = KEYSTATE1;//如果无按键按下就始终返回为NOKEY
  23.             }
  24.             break;
  25.         case KEYSTATE1:
  26.             if(!keypress){//P32==0
  27.                 keytime = 0;
  28.                 keystate = KEYSTATE2;}//按键仍然处于按下,消抖完成,状态转换到计时
  29.             else{
  30.                 keystate = KEYSTATE0;//低电平持续时间过小,不足一个定时器间隔
  31.             }                        //认为是无效按键,清零状态,实现消抖
  32.             break;
  33.         case KEYSTATE2:
  34.             if(keypress){//P32==1,按键释放,且间隔2个定时器中断以上,认为是无抖动的有效按键输入。
  35.                 keyreturn = SINGLEKEY;//返回单击
  36.                 keystate = KEYSTATE0;//清空状态
  37.             }//重要修改!更改长按时间由1280ms到400ms!
  38.             else if(++keytime >= 20){//P32=0,持续按下,计时加一个定时器中断间隔时间
  39.                 keyreturn = LONGKEY;//在下次定时器中断直接输出为长按,不需要等待
  40.                 keystate = KEYSTATE3;//进入状态3,等待按键释放
  41.             }
  42.             break;
  43.         case KEYSTATE3://等待按键释放,释放后清空状态
  44.             if(keypress){//P32==1,按键已经抬起
  45.                 keystate = KEYSTATE0;//清空状态
  46.             }
  47.             break;
  48.         }
  49.             return keyreturn;
  50.     }
  51. unsigned char KEY_READ(void){
  52.     static unsigned char key1 = KEYSTATE0;
  53.     static unsigned char keytime1 = 0;//多次按键计数器
  54.     unsigned char keyreturn = NOKEY;
  55.     //对于单击后长按,双击后长按,单独设静态变量防止状态丢失
  56.     unsigned char keytemp = KEY_DRIVER();//读取按键状态
  57.     switch(key1){
  58.         case KEYSTATE0:
  59.             if(keytemp == SINGLEKEY){
  60.                 keytime1 = 0;//第一次单击,无返回值,到下个状态判断之后是否有再次单击
  61.                 key1 = KEYSTATE1;//切换至下一状态
  62.             }
  63.             else{
  64.                 keyreturn = keytemp;//对于无键,或首先为长按的事件返回原事件
  65.             }
  66.             break;
  67.         case KEYSTATE1:
  68.             if(keytemp == LONGKEY){//在这里检测单击后长按
  69.                 keyreturn = SINGLETOLONGKEY;//状态保存到multikeyreturn中
  70.                 key1 = KEYSTATE0;//切换到状态3,等待按键抬起
  71.             }
  72.             else if(keytemp == SINGLEKEY){//再次检测到单击,进入state2
  73.                 key1 = KEYSTATE2;}
  74.             else{
  75.                 if(++keytime1 >= 40){//在这里实现等待双击
  76.                     keyreturn = SINGLEKEY;//如果超时,返回单击
  77.                     key1 = KEYSTATE0;//清空状态
  78.                 }//等待时间800ms?无所谓,不需要对双击做反应
  79.             }//三击是直接输出,无需等待
  80.             break;
  81.         case KEYSTATE2:
  82.             if(keytemp == SINGLEKEY){//第三次单击,总间隔小于640ms,沿用state1中的计数器
  83.                 keyreturn = TRIPLEKEY;//输出为三击
  84.                 key1 = KEYSTATE0;//返回初始状态
  85.             }
  86.             else if(keytemp == LONGKEY){//检测到长按
  87.                 keyreturn = DOUBLETOLONGKEY;
  88.                 key1 = KEYSTATE0;//切换到状态0
  89.             }
  90.             else{
  91.                 if(++keytime1 >= 48){//沿用之前的计数器值,继续计数
  92.                     keyreturn = DOUBLEKEY;//超时,没有检测到第三次单击,输出双击
  93.                     key1 = KEYSTATE0;
  94.                 }
  95.             }
  96.             break;
  97.         }
  98.         return keyreturn;
  99.     }
  100. unsigned char keyevent = NOKEY;//初始化为0
  101. void KEY_HANDLER(void){//按键处理
  102.     keyevent = KEY_READ();//调用按键读取函数
  103.         switch(keyevent){
  104.             case NOKEY:
  105.                 break;
  106.             case SINGLEKEY:
  107.             Serial.println("SINGLEKEY");
  108.                 break;
  109.             case LONGKEY://长按按键,增加占空比
  110.                 Serial.println("LONGKEY");
  111.                 break;
  112.             case SINGLETOLONGKEY://单击后长按
  113.                 Serial.println("SINGLETOLONGKEY");
  114.                 break;
  115.             case DOUBLEKEY://双击,快速退出闪烁模式
  116.             Serial.println("DOUBLEKEY");
  117.                 break;
  118.             case DOUBLETOLONGKEY:
  119.             Serial.println("DOUBLETOLONGKEY");
  120.                 break;
  121.             case TRIPLEKEY://三击
  122.                 Serial.println("TRIPLEKEY");
  123.                 break;
  124.             }
  125.         }
复制代码

评分

参与人数 1黑币 +10 收起 理由
wpppmlah + 10 共享资料的奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏2 分享淘帖 顶 踩
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表