李硕 发表于 2025-7-3 15:11:47

STC 通用的 【按键: 长按 / 单击 / 双击 / 三击】识别代码

STC 通用的 【按键: 长按 / 单击 / 双击 / 三击】识别代码
本代码参考了https://blog.csdn.net/m0_52596850/article/details/126776765#,
在原代码的基础上按我的代码风格重写了,添加了三击功能。
具体原理在注释中已经给出,通读一遍便能理解,
简单的概括就是通过switch在各种不同的状态之间切换实现按键的消抖,
长短按识别,相当的巧妙。
其中消抖直接使用了定时器中断的间隔进行消抖,我认为这是这个方案最巧思的一点。
如果要复用,直接根据你的按键IO口改变第一行的keyinput就行了,其他的不需要改变,
然后在主循环中或者定时器中断调用就可以了。
目前为P32低电平为有效按键输入,如有需求可以改为高电平。

#define KEYINPUT P32//按键输入为P32
#define NOKEY 0//无
#define SINGLEKEY 1//单键
#define DOUBLEKEY 2//双键
#define TRIPLEKEY 3//三键
#define LONGKEY 4//长键
#define KEYSTATE0 0
#define KEYSTATE1 1
#define KEYSTATE2 2
#define KEYSTATE3 3


unsigned char KEY_DRIVER(void){
    static unsigned char keystate = KEYSTATE0;
    static unsigned char keytime = 0;
    unsigned char keypress;
    unsigned char keyreturn = NOKEY;
    keypress = KEYINPUT;//读取P32电平
    switch(keystate){
      case KEYSTATE0://按键初始状态,按下后转换到消抖与确认态,用定时器中断间隔实现消抖
            if(!keypress){//P32==0
                keystate = KEYSTATE1;//如果无按键按下就始终返回为NOKEY
            }
            break;
      case KEYSTATE1:
            if(!keypress){//P32==0
                keytime = 0;
                keystate = KEYSTATE2;}//按键仍然处于按下,消抖完成,状态转换到计时
            else{
                keystate = KEYSTATE0;//低电平持续时间过小,只有一个定时器间隔
            }                        //认为是无效按键,清零状态,实现消抖
            break;
      case KEYSTATE2:
            if(keypress){//P32==1,按键释放,且间隔2个定时器中断,认为是无抖动的按键输入。
                keyreturn = SINGLEKEY;//返回单击
                keystate = KEYSTATE0;//清空状态
            }
            else if(++keytime >= 64){//P32=0,继续按下,计时加一个定时器中断间隔时间,
                keyreturn = LONGKEY;//在下次定时器中断直接输出为长按,不需要等待
                keystate = KEYSTATE3;//进入状态3,等待按键释放
            }
            break;
      case KEYSTATE3://等待按键释放,释放后清空状态
            if(keypress){//P32==1,按键已经抬起
                keystate = KEYSTATE0;//清空状态
            }
            break;
      }
            return keyreturn;
    }
//////////////////////////////////////////////////
unsigned char KEY_READ(void){
    static unsigned char key1 = KEYSTATE0;
    static unsigned char keytime1 = 0;//多次按键计数器
    unsigned char keyreturn = NOKEY;
    unsigned char keytemp;
    keytemp = KEY_DRIVER();//读取按键状态
    switch(key1){
      case KEYSTATE0:
            if(keytemp == SINGLEKEY){
                keytime1 = 0;//第一次单击,无返回值,到下个状态判断之后是否有再次单击
                key1 = KEYSTATE1;//切换单击
            }
            else{
                keyreturn = keytemp;//对于无键,长按时间返回原事件
            }
            break;
      case KEYSTATE1:
            if(keytemp == SINGLEKEY){//再次单击,间隔小于640ms
                key1 = KEYSTATE2;//切换到状态3,等待三击
            }//不清空计数器,因为要实现总间隔检测
            else{
                if(++keytime1 >= 32){//在这里实现等待双击
                  keyreturn = SINGLEKEY;//返回单击
                  key1 = KEYSTATE0;//清空状态
                }
            }
            break;
      case KEYSTATE2:
            if(keytemp == SINGLEKEY){//第三次单击,总间隔小于640ms,沿用state1中的计数器
                keyreturn = TRIPLEKEY;//输出为三击
                key1 = KEYSTATE0;//返回初始状态
            }
            else{
                if(++keytime1 >= 32){//沿用之前的计数器值,继续计数
                  keyreturn = DOUBLEKEY;//超时,输出双击
                  key1 = KEYSTATE0;
                }
            }
      }
    return keyreturn;
    }

下面是测试代码及调用功能示范,长按带点亮led,三击熄灭led,可以用于测试功能,需要手动

bit timer20msok = 0;
void TIMER0_ROUTINE(void) interrupt 1{
    timer20msok = 1;
}


unsigned char keyevent = NOKEY;
void main(void){
    TH0 = 0XD8;
    TL0 = 0XF0;
    IE = 0X8F;//允许中断
    TR0 = 1;//打开定时器电源
    while(1){
      if(timer20msok){
            timer20msok = 0;
            keyevent = KEY_READ();
            if(keyevent == LONGKEY){
                P30 = 0;
            }
            else if(keyevent == TRIPLEKEY)
                P30 = 1;
      }
    }
}




神农鼎 发表于 2025-7-3 15:30:45


https://www.stcaimcu.com/data/download/Datasheet/AI8051U.pdf









vb2002 发表于 2025-7-4 00:06:57

{:4_250:}
强大,我有个加上单击加长按的,不过老是有点小小问题

神农鼎 发表于 2025-7-4 09:48:52

vb2002 发表于 2025-7-4 00:06
强大,我有个加上单击加长按的,不过老是有点小小问题
我们数据手册中有, 深圳大学,同学们调试通过的 参考例程
参考现成的 90分的程序,再去完善

李硕 发表于 2025-7-5 10:51:44

vb2002 发表于 2025-7-4 00:06
强大,我有个加上单击加长按的,不过老是有点小小问题
我在人家大佬的代码的基础上扩展出的三击功能,
只能说是人家思路确实牛,感觉不是嵌入式老手想不出这么精妙的方案,
我自己也折腾了好久按键逻辑,
但就是没想到像人家这样在不同的状态之间转换的方案,太牛了,
我看了他的方案之后就想到这写的简直就是我

李硕 发表于 2025-7-5 11:01:21

按键程序调用举例:下面这段代码通过一个ledmode变量来进入不同的状态,限制最大档位,需要在定时器中断内设置keydet =1 允许检测,在主循环中添加
if(keydet){
KEY_HANDLER();
}
这个函数会调用中层和底层的按键处理代码。
如果只是测试就把ledmode变量初始化为0就可以了
const unsigned char ccapvalues[] = {0x36, 0x33, 0x2E, 0x27, 0x20, 0x16, 0x10, 0x00};
unsigned char ccapcounter;
void PCA_CONFIG(void){
//    P_SW1 = 0x10;   // P3.1切换为PWM输出
    CCON = 0x00;    // 复位PCA
    CMOD = 0x0A;    // 系统时钟/4,6MHZ下为23.5khz
    CL = 0x00;      // 复位低字节
    CH = 0x00;      // 复位高字节
    CCAPM0 = 0x42;// PCA0 PWM模式
    PCA_PWM0 = 0x80;// 6位PWM模式
    CCAP0H = ccapvalues;//在唤醒后读取ram中的ccapcounter值
    CR = 1;         //初始化不开启电源
}
void TM0_ROUTINE(void) interrupt 1{//timer0中断服务函数,20ms一次
    keydet = 1;
}
bit keydet = 0;//允许按键检测
unsigned char keyevent = NOKEY;//初始化为0
void KEY_HANDLER(void){
    keyevent = KEY_READ();//调用按键读取函数
      switch(keyevent){
            case LONGKEY:
                poweron ^= 1;
                break;
            case SINGLEKEY:
                if(poweron && ledmode<=2){
                  if(ccapcounter <= 7) ccapcounter++;//如果小于7就增加,等于7后不再增加
                }
                else if(poweron && ledmode>2){
                  if(ccapcounter <= 5) ccapcounter++;
                }
                break;
            case DOUBLEKEY:
                if(poweron && ledmode<=2){
                  if(ccapcounter > 0) ccapcounter--;//如果大于0就减小,等于0后不再减小
                }
                else if(poweron && ledmode>2){//不能写成>=0,否则当为0时再减1...好吧这是无符号字符,不会小于0
                  if(ccapcounter > 0) ccapcounter--;
                }
                break;
            case TRIPLEKEY:
                switch(ledmode){
                  case 0:
                  case 1:
                  case 2:
                        if(ccapcounter == 7){
                            ccapcounter = 0;
                        }
                        else{
                            ccapcounter = 7;
                        }
                        break;
                  case 3:
                  case 4:
                  case 5:
                        if(ccapcounter == 5){
                            ccapcounter = 0;
                        }
                        else{
                            ccapcounter =5;
                        }
                }
                break;
            }
               
      }

无用科技 发表于 2025-7-9 14:13:31

学习了

无用科技 发表于 2025-7-9 14:13:48

最近正好有项目要用

Istar_MCU 发表于 2025-7-17 08:45:02

老师,我问一下组合按键有示例程序吗?

lwh13 发表于 2025-7-19 11:34:58

请问是24mkz的吗
页: [1]
查看完整版本: STC 通用的 【按键: 长按 / 单击 / 双击 / 三击】识别代码