极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 11186|回复: 1

单片机基于事件的按键处理编程思想(原创)

[复制链接]
发表于 2021-9-27 13:51:49 | 显示全部楼层 |阅读模式
本帖最后由 zyhlove813 于 2021-9-27 14:25 编辑

单片机处理按键网上的思路也是五花八门。入门的,可能是直接判断端口,老手的,可能是通过键值计算;不管是谁学单片机,都逃不了做按键处理的程序。我在做项目的过程中,参考一些网上的思路,结合自己的算法,通过项目调试和验证,终于做出了比较优化和满意的按键处理程序,功能有如下几个方面:
1、多键扫描处理,提高处理速度
2、支持长按处理(单次触发或一直触发)
3、支持按下、弹起、按住、松开、长按的事件
4、项目中只需要修改扫描键值,然后在各事件中判断对应键值(单键或多键)
主要编程思路如下:

1、变量的说明


1
2
3
4
5
6
7
8
9
10
11
12


//长按键的时长
#define longkey_times 2000
//长按单次模式定义,如果要长按时一直执行,请注释下一行
#define LONG_PROCESS_ONCE
uint8_t  KEY_PRESS;  //当前按下的键值
uint8_t  KEY_NOT_PRESS;  //当前未被按的键值
uint8_t  KEY_LAST;  //上一次的键值
uint8_t  KEY_LONG;  //长按的键值
uint8_t  KEY_DOWN;  //按下的键值
uint8_t  KEY_UP; //弹起的键值
uint8_t  KEY_UP_NL; //弹起的键值不带长按键
uint32_t KEY_TICKS;  //按键时间,用于长按计时

2、按键相关函数说明


1
2
3
4
5
6
7


//按键处理程序
void JUDGE_KEY(bool SINGLE_KEY); //键值扫描及逻辑处理
void KEY_LONG_PROCESS(void);  //长按事件
void KEY_PRESS_PROCESS(void);  //按住状态事件
void KEY_NOT_PRESS_PROCESS(void); //松开状态事件
void KEY_DOWN_PROCESS(void); //按下事件
void KEY_UP_PROCESS(void);  //弹起事件

3、按键扫描及逻辑处理思路


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78


//bool SINGLE_KEY 防抖开关,True时打开
void JUDGE_KEY(bool SINGLE_KEY)
{
    uint8_t TEMP_KEY;  //临时的键值缓存
    TEMP_KEY = PIND & 0x0C; //批量扫描IO,并生成键值,用户需结合项目自已修改,PIND
    //此处表示PD0-7的端口,不同单片机不一样,0x0C只取出
    //PD2 PD3的值
    TEMP_KEY ^= 0x0C;       //此处主要是把键值取反,如果你的按键是低电平触发的话
    //如果你的按键是高电平触发,则删除此行,不需要取反
    if(TEMP_KEY > 0)  //键值大于0,表示有按键按着
    {
        delay(10); //防抖延时
        //以下再一次批量扫描键值
        KEY_PRESS = PIND & 0x0C;
        KEY_PRESS ^= 0x0C;

        //如果防抖开关有效且两次键值不一致,返回不处理
        if(TEMP_KEY != KEY_PRESS && SINGLE_KEY)
        {
            return;
        }
    }
    else //无按键动作,当前按下的键值=0
    {
        KEY_PRESS = 0;
    }
    //核心按键逻辑判断
    KEY_DOWN = (KEY_LAST ^ KEY_PRESS) & KEY_PRESS; //按下的键值
    KEY_UP = (KEY_LAST ^ KEY_PRESS) & KEY_LAST; //弹起的键值(包含长按键)
    KEY_UP_NL = (~KEY_LONG) & KEY_UP; //弹起的键值(不包含长按键)
    KEY_NOT_PRESS = ~KEY_PRESS; //未按的键状态值
    if(KEY_LONG & KEY_UP)
    {
        KEY_LONG = 0;
    }
    if(KEY_PRESS > 0)  //当前有按键值按下
    {
        if(KEY_LAST & KEY_PRESS)     //如果当前的值与上次按下的值有相同的地方
            //表示有键一直按着,否则可能只是切换了其他按键
        {
            //millis()函数是Arduino的开机时间毫秒计数,其他单片机自己实现
            if(millis() - KEY_TICKS > longkey_times)   //按键时间大于长按时间
            {
                KEY_LONG = KEY_LAST & KEY_PRESS;   //长按键值等于一直按住的值
                KEY_LONG_PROCESS();   //长按键处理
                #ifdef LONG_PROCESS_ONCE  //如果是长按单次处理
                  KEY_TICKS = millis(); //更新长按时间标记,避免进入长按判断
                #endif
            }
        }
        else
        {
            KEY_TICKS = millis(); //切换了其他键,更新长按时间标记,避免进入长按判断
        }
    }
    else   //当前无按键按下
    {
        KEY_TICKS = millis(); //更新长按时间标记,避免进入长按判断
    }
    if(KEY_UP > 0)  //如果有弹起的按键值
    {
        KEY_UP_PROCESS();    //按键弹起时处理
    }
    if(KEY_DOWN > 0)
    {
        KEY_DOWN_PROCESS();  //按键按下时处理
    }
    if(KEY_PRESS > 0)
    {
        KEY_PRESS_PROCESS();  //按键按着状态处理
    }
    if(KEY_NOT_PRESS)
    {
        KEY_NOT_PRESS_PROCESS();  //按键弹起状态处理
    }
    KEY_LAST = KEY_PRESS; //更新上一次的键值
}

4、按键逻辑处理算法详解
   4.1首次按下的键,先用异或^进行上次扫描键值和本次扫描键值计算,取得不一样的键位,不一样的键位和本次扫描键位相同,则表示首次按下。
      如 0000 0010表示上次扫描的键,第1位是按下的状态
              0000 0110 表示本次扫描的键,第1位和第2位是按下的,
              我们要算出第2位是首次按下,则
              0000 0010 ^ 0000 0110=0000 0100
              0000 0100 & 0000 0110=0000 0100

       又如 000 0010表示上次扫描的键,第1位是按下的
              0000 0100表示本次扫描的键,第2位是按下的,第1位已经松开
              我们要算出第2位是首次按下,则
              0000 0010 ^ 0000 0100=0000 0110
              0000 0110 & 0000 0100=0000 0100  
        (所以与本次扫描的键值与,可以得到首次按下的键值)
              KEY_DOWN=(KEY_LAST^KEY_PRESS) & KEY_PRESS;   //按下的键值

   4.2弹起的键值,与按下的原理一样,不同的是要和上次扫描的键值相与
          0000 0010表示上次扫描的键,第1位是按下的状态
          0000 0100 表示本次扫描的键,第2位是按下的,
          我们要算出第1位是弹起,则
          0000 0010 ^ 0000 0100=0000 0110
          0000 0110 & 0000 0010=0000 0010

          KEY_UP=(KEY_LAST^KEY_PRESS) & KEY_LAST;//弹起的键值(包含长按键)
   4.3长按键一般单独处理,弹起时如果要排除,避免多次触发事件,需要计算出
      不包含长按键的键值,用如下公式
           KEY_UP_NL=(~KEY_LONG) & KEY_UP; //弹起的键值(不包含长按键)
   4.4 长按键的计算逻辑,见程序注释
5、如何使用
    5.1 设置好长按的时间
       #define longkey_times 2000  //这里表示2秒
    5.2 修改扫描键值
        TEMP_KEY = PIND & 0x0C; //批量扫描IO,并生成键值,用户需结合项目自已修改,PIND
                            //此处表示PD0-7的端口,不同单片机不一样,0x0C只取出 PD2 PD3的值  
         TEMP_KEY ^= 0x0C;       //此处主要是把键值取反,如果你的按键是低电平触发的话
        //还有一处地方也要一起改
        KEY_PRESS = PIND & 0x0C;         
        KEY_PRESS ^= 0x0C;

       注意:51或其他单片机中,如果按键不在同一序列,比如P01 P03 P14 P16,则可以如下设置
       TEMP_KEY = P0 & 0x0A; //取出 P01 P03
       TEMP_KEY |=(P1 & 0x50); //取出 P14 P16

       TEMP_KEY ^= (0x0A|0x50);       //此处主要是把键值取反,如果你的按键是低电平触发的话,
                                    //如果你的按键是高电平触发,则删除此行,不需要取反
       //还有一处地方也要一起改
       KEY_PRESS = P0 & 0x0A; //取出 P01 P03   
       KEY_PRESS |=(P1 & 0x50); //取出 P14 P16
       KEY_PRESS ^= (0x0A|0x50);      //此处主要是把键值取反,如果你的按键是低电平触发的话,
                                 //如果你的按键是高电平触发,则删除此行,不需要取反
      为了编程方便,尽量使用同一序列的口,如果不同序列的口,那端口号也要能错开,如用了P01,就不要用P11了。
这样的话,才能方便计算,提高扫描效率,如果非要用,只能通过移位处理
      如51或其他单片机中,想判断 P01 P02 P12 P13的键
      TEMP_KEY = P1 & 0x0C; //取出 P12 P13
      TEMP_KEY =TEMP_KEY<<1; //左移1位,避开P12和P02交叉重叠
      TEMP_KEY |= (P0 & 0x06); //取出 P01 P02
      TEMP_KEY ^= (0x18|0x06);       //此处主要是把键值取反,如果你的按键是低电平触发的话
                                                      //如果你的按键是高电平触发,则删除此行,不需要取反
      这样键值里,0x02表示P01,0x04表示P02,0x08表示P12,0x10表示P13

5.3在单片机循环程序或定时器里,周期性调用扫描程序
       void loop()
      {
          JUDGE_KEY(true);
      }

5.4在对应事件里进行其他编程,如:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49


void KEY_NOT_PRESS_PROCESS()   //按键弹起状态处理
{
    if(KEY_NOT_PRESS & 0x04)
    {
        //Serial.println("KEY PD2 is NOT PRESSING");
    }
}
void KEY_PRESS_PROCESS() //按键按着状态处理
{
    if(KEY_PRESS & 0x04)
    {
        //Serial.println("KEY PD2 is PRESSING");
    }
}
void KEY_LONG_PROCESS() //长按键处理
{
    if(KEY_LONG & 0x04)
    {
        Serial.println("KEY PD2 is LONG PRESS");
    }
    if(KEY_LONG & 0x08)
    {
        Serial.println("KEY PD3 is LONG PRESS");
    }
}
void KEY_DOWN_PROCESS()  //按键按下时处理
{
    if(KEY_DOWN & 0x04)
    {
        Serial.println("KEY PD2 is DOWN NOW");
    }
    if(KEY_DOWN & 0x08)
    {
        Serial.println("KEY PD3 is DOWN NOW");
    }
    Serial.println("---------------------");
}
void KEY_UP_PROCESS()    //按键弹起时处理
{
    if(KEY_UP_NL & 0x04)
    {
        Serial.println("KEY PD2 is UP_NL NOW");
    }
    if(KEY_UP_NL & 0x08)
    {
        Serial.println("KEY PD3 is UP NOW");
    }
    Serial.println("---------------------");
}

附上Arduino的测试程序,注意Arduino Uno中PD2表示数字脚2,PD3表示数字脚3



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

发表于 2021-10-2 18:43:12 | 显示全部楼层
不用中断都是耍流氓
回复 支持 反对

使用道具 举报

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

本版积分规则 需要先绑定手机号

Archiver|联系我们|极客工坊

GMT+8, 2024-4-18 18:33 , Processed in 0.039253 second(s), 19 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

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