找回密码
 立即注册
查看: 50|回复: 5

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

[复制链接]
  • 打卡等级:偶尔看看I
  • 打卡总天数:10
  • 最近打卡:2025-07-05 10:36:05
已绑定手机

7

主题

11

回帖

99

积分

注册会员

积分
99
发表于 前天 15:11 | 显示全部楼层 |阅读模式
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;
        }
    }
}




回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:118
  • 最近打卡:2025-07-05 11:06:27

746

主题

1万

回帖

1万

积分

管理员

积分
17449
发表于 前天 15:30 | 显示全部楼层
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:414
  • 最近打卡:2025-07-05 00:04:13
已绑定手机

145

主题

1713

回帖

2704

积分

金牌会员

积分
2704
发表于 昨天 00:06 | 显示全部楼层

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

点评

我们数据手册中有, 深圳大学,同学们调试通过的 参考例程  详情 回复 发表于 昨天 09:48
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:118
  • 最近打卡:2025-07-05 11:06:27

746

主题

1万

回帖

1万

积分

管理员

积分
17449
发表于 昨天 09:48 | 显示全部楼层
vb2*** 发表于 2025-7-4 00:06
强大,我有个加上单击加长按的,不过老是有点小小问题

我们数据手册中有, 深圳大学,同学们调试通过的 参考例程
参考现成的 90分的程序,再去完善
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:10
  • 最近打卡:2025-07-05 10:36:05
已绑定手机

7

主题

11

回帖

99

积分

注册会员

积分
99
发表于 2 小时前 | 显示全部楼层
vb2*** 发表于 2025-7-4 00:06
强大,我有个加上单击加长按的,不过老是有点小小问题

我在人家大佬的代码的基础上扩展出的三击功能,只能说是人家思路确实牛,感觉不是嵌入式老手想不出这么精妙的方案,我自己也折腾了好久按键逻辑,但就是没想到像人家这样在不同的状态之间转换的方案,太牛了,我看了他的方案之后就想到这写的简直就是我
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:10
  • 最近打卡:2025-07-05 10:36:05
已绑定手机

7

主题

11

回帖

99

积分

注册会员

积分
99
发表于 2 小时前 | 显示全部楼层
按键程序调用举例:下面这段代码通过一个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[ccapcounter];//在唤醒后读取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;
            }
               
        }
回复 支持 反对

使用道具 举报 送花

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

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-7-5 13:31 , Processed in 0.125073 second(s), 84 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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