打印
[程序源码]

精妙的按键扫描函数,不采用延时消抖,实现长短按,创...

[复制链接]
8896|35
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
沧海一瞬|  楼主 | 2017-9-10 22:27 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
沙发
沧海一瞬|  楼主 | 2017-9-10 22:28 | 只看该作者
在定时器中断中扫描,不用10ms延时,动态响应极好。实现长按短按。
创建对象极度方便。
用户只需要根据自己需要修改keyInit( )函数和下图中的变量即可。

32init.png (36.78 KB )

32init.png

按键个数.png (37.31 KB )

按键个数.png

按键回调32.png (35.5 KB )

按键回调32.png

使用特权

评论回复
板凳
沧海一瞬|  楼主 | 2017-9-10 22:29 | 只看该作者
接下来是51的介绍.................................................


51init.png (47.94 KB )

51init.png

51main.png (19.15 KB )

51main.png

使用特权

评论回复
地板
沧海一瞬|  楼主 | 2017-9-10 22:30 | 只看该作者
上传.c文件。51附例程,32只有只有.c文件。



51key.rar

3.67 KB, 阅读权限: 10

32key.rar

4.82 KB, 阅读权限: 10

使用特权

评论回复
5
xyz549040622| | 2017-9-11 07:36 | 只看该作者
观摩下这个按键算法。

使用特权

评论回复
6
liujie14565| | 2017-9-11 11:24 | 只看该作者
状态机法,都是这么处理的

使用特权

评论回复
7
batsong| | 2017-9-11 11:58 | 只看该作者
几年前做过C++的按键类,可创建多实例,线程计时,不用阻塞delay,长短按、消抖参数可独立设置,按下、弹起、click等多种事件支持

使用特权

评论回复
8
xouou_53320| | 2017-9-11 12:06 | 只看该作者
楼主  大家都是这么干的

使用特权

评论回复
9
沧海一瞬|  楼主 | 2017-9-11 14:10 | 只看该作者
xouou_53320 发表于 2017-9-11 12:06
楼主  大家都是这么干的

高手是怎么干的。新手进阶高手可以看看。

使用特权

评论回复
10
shiman| | 2017-9-11 18:02 | 只看该作者

使用特权

评论回复
11
沧海一瞬|  楼主 | 2017-9-11 18:41 | 只看该作者
batsong 发表于 2017-9-11 11:58
几年前做过C++的按键类,可创建多实例,线程计时,不用阻塞delay,长短按、消抖参数可独立设置,按下、弹起 ...

厉害了,我没学过C++

使用特权

评论回复
12
hjl714016| | 2017-9-11 20:33 | 只看该作者
按键扫描还用delay死等,是没真正做过项目的,,,

使用特权

评论回复
13
litianchenghao| | 2017-9-12 10:18 | 只看该作者
看不懂,额额

使用特权

评论回复
14
keer_zu| | 2017-9-13 13:06 | 只看该作者
乐鑫的也不错:
中断方式,回调函数。
/*
* ESPRESSIF MIT License
*
* Copyright (c) 2016 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
*
* Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP8266 only, in which case,
* it is free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/

#include "ets_sys.h"
#include "os_type.h"
#include "osapi.h"
#include "mem.h"
#include "gpio.h"
#include "user_interface.h"

#include "driver/key.h"

LOCAL void key_intr_handler(void *arg);

/******************************************************************************
* FunctionName : key_init_single
* Description  : init single key's gpio and register function
* Parameters   : uint8 gpio_id - which gpio to use
*                uint32 gpio_name - gpio mux name
*                uint32 gpio_func - gpio function
*                key_function long_press - long press function, needed to install
*                key_function short_press - short press function, needed to install
* Returns      : single_key_param - single key parameter, needed by key init
*******************************************************************************/
struct single_key_param *ICACHE_FLASH_ATTR
key_init_single(uint8 gpio_id, uint32 gpio_name, uint8 gpio_func, key_function long_press, key_function short_press)
{
    struct single_key_param *single_key = (struct single_key_param *)os_zalloc(sizeof(struct single_key_param));
       
        //os_printf("+++ %s         single_key: 0x%x       gpio_id:%d   gpio_name:%d    gpio_func:%d     lone_press:0x%x      short_press : 0x%x \n",__FUNCTION__,single_key,gpio_id,gpio_name,gpio_func,long_press,short_press);

    single_key->gpio_id = gpio_id;
    single_key->gpio_name = gpio_name;
    single_key->gpio_func = gpio_func;
    single_key->long_press = long_press;
    single_key->short_press = short_press;

    return single_key;
}

/******************************************************************************
* FunctionName : key_init
* Description  : init keys
* Parameters   : key_param *keys - keys parameter, which inited by key_init_single
* Returns      : none
*******************************************************************************/
void ICACHE_FLASH_ATTR
key_init(struct keys_param *keys)
{
    uint8 i;

    ETS_GPIO_INTR_ATTACH(key_intr_handler, keys);

    ETS_GPIO_INTR_DISABLE();

        os_printf("+++ %s    key_num: %d \n",__FUNCTION__,keys->key_num);
       
    for (i = 0; i < keys->key_num; i++) {
        keys->single_key[i]->key_level = 1;

        PIN_FUNC_SELECT(keys->single_key[i]->gpio_name, keys->single_key[i]->gpio_func);

        gpio_output_set(0, 0, 0, GPIO_ID_PIN(keys->single_key[i]->gpio_id));

        gpio_register_set(GPIO_PIN_ADDR(keys->single_key[i]->gpio_id), GPIO_PIN_INT_TYPE_SET(GPIO_PIN_INTR_DISABLE)
                          | GPIO_PIN_PAD_DRIVER_SET(GPIO_PAD_DRIVER_DISABLE)
                          | GPIO_PIN_SOURCE_SET(GPIO_AS_PIN_SOURCE));

        //clear gpio14 status
        GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(keys->single_key[i]->gpio_id));

        //enable interrupt
        gpio_pin_intr_state_set(GPIO_ID_PIN(keys->single_key[i]->gpio_id), GPIO_PIN_INTR_NEGEDGE);
    }

    ETS_GPIO_INTR_ENABLE();
}

/******************************************************************************
* FunctionName : key_5s_cb
* Description  : long press 5s timer callback
* Parameters   : single_key_param *single_key - single key parameter
* Returns      : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
key_5s_cb(struct single_key_param *single_key)
{
        os_printf("\n\n+++++++++++++++++      %s\n\n\n",__FUNCTION__);
    os_timer_disarm(&single_key->key_5s);

       

    // low, then restart
    if (0 == GPIO_INPUT_GET(GPIO_ID_PIN(single_key->gpio_id))) {
        if (single_key->long_press) {
            single_key->long_press();

                        os_printf("\n\n+++++++++++++++++     0 == GPIO_INPUT_GET(GPIO_ID_PIN(single_key->gpio_id))  %s\n\n\n",__FUNCTION__);
        }
    }
}

/******************************************************************************
* FunctionName : key_50ms_cb
* Description  : 50ms timer callback to check it's a real key push
* Parameters   : single_key_param *single_key - single key parameter
* Returns      : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
key_50ms_cb(struct single_key_param *single_key)
{
        os_printf("\n\n+++++++++++++++++      %s\n\n\n",__FUNCTION__);
    os_timer_disarm(&single_key->key_50ms);
       
    // high, then key is up
    if (1 == GPIO_INPUT_GET(GPIO_ID_PIN(single_key->gpio_id))) {
        os_timer_disarm(&single_key->key_5s);
        single_key->key_level = 1;
        gpio_pin_intr_state_set(GPIO_ID_PIN(single_key->gpio_id), GPIO_PIN_INTR_NEGEDGE);

        if (single_key->short_press) {
            single_key->short_press();
        }
    } else {
        gpio_pin_intr_state_set(GPIO_ID_PIN(single_key->gpio_id), GPIO_PIN_INTR_POSEDGE);
    }
}

/******************************************************************************
* FunctionName : key_intr_handler
* Description  : key interrupt handler
* Parameters   : key_param *keys - keys parameter, which inited by key_init_single
* Returns      : none
*******************************************************************************/
LOCAL void
key_intr_handler(void *arg)
{
    uint8 i;
    uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
    struct keys_param *keys = (struct keys_param *)arg;

        //os_printf("+++++++++++++++++      %s      gpio_status:%d     key_num: %d\n",__FUNCTION__,gpio_status,keys->key_num);

    for (i = 0; i < keys->key_num; i++) {
                //os_printf("+++++++++++++++++    gpio_status:%d      keys->single_key[%d]->gpio_id:%d     BIT(keys->single_key[ %d ]->gpio_id) : %d      \n",gpio_status,i,keys->single_key[i]->gpio_id,i,BIT(keys->single_key[i]->gpio_id));
        if (gpio_status & BIT(keys->single_key[i]->gpio_id)) {
                        //os_printf("+++++++++++++++++      i: %d    \n",i);


            //disable interrupt
            gpio_pin_intr_state_set(GPIO_ID_PIN(keys->single_key[i]->gpio_id), GPIO_PIN_INTR_DISABLE);

            //clear interrupt status
            GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & BIT(keys->single_key[i]->gpio_id));

            if (keys->single_key[i]->key_level == 1) {
                // 5s, restart & enter softap mode
                os_timer_disarm(&keys->single_key[i]->key_5s);
                os_timer_setfn(&keys->single_key[i]->key_5s, (os_timer_func_t *)key_5s_cb, keys->single_key[i]);
                os_timer_arm(&keys->single_key[i]->key_5s, 5000, 0);
                keys->single_key[i]->key_level = 0;
                gpio_pin_intr_state_set(GPIO_ID_PIN(keys->single_key[i]->gpio_id), GPIO_PIN_INTR_POSEDGE);
            } else {
                // 50ms, check if this is a real key up
                os_timer_disarm(&keys->single_key[i]->key_50ms);
                os_timer_setfn(&keys->single_key[i]->key_50ms, (os_timer_func_t *)key_50ms_cb, keys->single_key[i]);
                os_timer_arm(&keys->single_key[i]->key_50ms, 50, 0);
            }


        }

    }

}

使用特权

评论回复
15
keer_zu| | 2017-9-13 13:07 | 只看该作者
回调函数和初始化:

void key_down50ms_func(void)
{
        os_printf("\n\n+++++++++++++++++      %s\n\n\n",__FUNCTION__);
        os_printf("key down 50ms!\n");
}

void key_down5s_func(void)
{
        os_printf("+++++++++++++++++      %s\n",__FUNCTION__);
        os_printf("start smartconfig............!\n");
       
        smartconfig_set_type(SC_TYPE_ESPTOUCH); //SC_TYPE_ESPTOUCH,SC_TYPE_AIRKISS,SC_TYPE_ESPTOUCH_AIRKISS
        wifi_set_opmode(STATION_MODE);
        smartconfig_start(smartconfig_done);
       
}

struct keys_param key;
struct single_key_param* single_key[1];


void my_key_init(void)
{
        struct single_key_param *psingle_key;// = //&single_key[0];
        psingle_key =
                        key_init_single(GPIO_ID_PIN(2),PERIPHS_IO_MUX_GPIO2_U,FUNC_GPIO2,key_down5s_func,key_down50ms_func);
        key.key_num = 1;
        //key.single_key = key_init_single(2,2,(key_function)NULL,(key_function)NULL,(key_function)NULL);
        single_key[0] = psingle_key;
        key.single_key = single_key;//&psingle_key;

        key_init(&key);
}

使用特权

评论回复
16
gujiamao12345| | 2017-9-13 17:09 | 只看该作者
发一个马老师的:
//machao key
/*=============
低层按键(I/0)扫描函数,即低层按键设备驱动,只返回无键、短按和长按。具体双击不在此处判断。参考本人教材的例9-1,稍微有变化。教材中为连_发。
===============*/


unsigned char key_driver(void)
{
        static unsigned char key_state = key_state_0, key_time = 0;
        unsigned char key_press, key_return = N_key;
        key_press = GPIO_ReadInputDataBit(KEY1_BANK,KEY1_PIN);       // 读按键I/O电平
       
        switch (key_state) {
                case key_state_0:                              // 按键初始态
                        if (!key_press)
                                key_state = key_state_1;             // 键被按下,状态转换到按键消抖和确认状态
                                break;

                case key_state_1:                      // 按键消抖与确认态
                        if (!key_press) {
                                key_time = 0;                   //
                                key_state = key_state_2;          // 按键仍然处于按下,消抖完成,状态转换到按下键时间的计时状态,但返回的还是无键事件
                        } else
                                key_state = key_state_0;          // 按键已抬起,转换到按键初始态。此处完成和实现软件消抖,其实按键的按下和释放都在此消抖的。
                        break;

                case key_state_2:
                        if(key_press) {
                                key_return = S_key;        // 此时按键释放,说明是产生一次短操作,回送S_key
                                key_state = key_state_0;   // 转换到按键初始态
                        } else if (++key_time >= 100) {     // 继续按下,计时加10ms(10ms为本函数循环执行间隔)
                                key_return = L_key;        // 按下时间>1000ms,此按键为长按操作,返回长键事件
                                key_state = key_state_3;   // 转换到等待按键释放状态
                        }
                        break;

                case key_state_3:                 // 等待按键释放状态,此状态只返回无按键事件
                        if (key_press)
                                key_state = key_state_0;        //按键已释放,转换到按键初始态
                        break;
        }        
        return key_return;
}

/*=============
中间层按键处理函数,调用低层函数一次,处理双击事件的判断,返回上层正确的无键、单键、双键、长键4个按键事件。
本函数由上层循环调用,间隔10ms
===============*/

unsigned char key_read(void)
{
    static unsigned char key_m = key_state_0, key_time_1 = 0;
    unsigned char key_return = N_key,key_temp;
   
    key_temp = key_driver();
   
    switch(key_m) {
                case key_state_0:
                        if (key_temp == S_key ) {
                                key_time_1 = 0;               // 第1次单击,不返回,到下个状态判断后面是否出现双击
                                key_m = key_state_1;
                        } else
                                key_return = key_temp;        // 对于无键、长键,返回原事件
                        break;

                case key_state_1:
                        if (key_temp == S_key) {            // 又一次单击(间隔肯定<500ms)
                                key_return = D_key;           // 返回双击键事件,回初始状态
                                key_m = key_state_0;
                        }
                        else {                                  // 这里500ms内肯定读到的都是无键事件,因为长键>1000ms,在1s前低层返回的都是无键
                                if(++key_time_1 >= 50) {
                                        key_return = S_key;      // 500ms内没有再次出现单键事件,返回上一次的单键事件
                                        key_m = key_state_0;     // 返回初始状态
                                }
                        }
                        break;
    }
    return key_return;
}   

使用特权

评论回复
17
座机呀| | 2017-9-13 20:36 | 只看该作者
恕我愚钝,真没看出来哪里精妙了?

使用特权

评论回复
18
沧海一瞬|  楼主 | 2017-9-16 12:25 | 只看该作者
座机呀 发表于 2017-9-13 20:36
恕我愚钝,真没看出来哪里精妙了?

高手肯定觉得不咋地,适合初学者。

使用特权

评论回复
19
ailingg| | 2017-11-23 09:11 | 只看该作者
本帖最后由 ailingg 于 2017-11-23 11:08 编辑

一个按键程序弄得的好繁琐,ram占用的也多。
我自己用的,弄个表格就好.
/**
*   按键程序
*   PIC16单片机
*/




#define KEY_QTY                  6


#define KEY_DOWN                 1
#define KEY_UP                   0
#define TRUE                     1
#define FALSE                    0


// Key pins position,                         // PORTB : I I x x  x O O O
#define KEY_READ_PINS_SET        0xc0         // Read pin : RB7 ~ RB6
#define KEY_SCAN_PINS_SET        0x07         // Scan pin : RB2 ~ RB0 经过NPN反向后作为键盘的行扫描,所以判断是否有按键按下是将所有扫描引脚置高
#define KEY_PORT                 PORTB


/** 按键扫描变量 */
typedef struct
{
    unsigned GetKey   :1;  
    unsigned KeyState :1;
   
    UINT8    FiltCnt;
    UINT8    LongCnt;
    UINT8    PressTimes; //纪录按键进入的次数
    UINT8    SetPoint;
   
}KeyVar_TypeDef;

extern KeyVar_TypeDef KeyVar;




typedef struct
{
    void (*IsKeyDownFunc)(void); // 按键事件函数
    UINT8 KeyCode;               // 键值,由硬件决定
    UINT8 LongTime;              // 长按键生效时间
    UINT8 RenewTime;             // 长按键重新开始时间
    UINT8 FiltTime;              // 去抖滤波时间   
    UINT8 KeyID;
   
}KeyElem_TypeDef;

extern KeyElem_TypeDef const KeyElem[KEY_QTY +1];


// 按键元素表
KeyElem_TypeDef const KeyElem[KEY_QTY + 1] = {
    { EmptyFunc,       0   ,  0,    0,   4,  0 },
    { StopKey,         0x81,  0,    0,   4,  1 },
    { VacuumParamSet,  0x82,  0,    0,   4,  2 },
    { SealingParamSet, 0x84,  0,    0,   4,  3 },
    { DigitIncrease,   0x41,  100,  80,  4,  4 },
    { DigitDecrease,   0x42,  100,  80,  4,  5 },
    { HeatLevelSet,    0x44,  0,    0,   4,  6 }
};


KeyVar_TypeDef KeyVar;
KeyMsg_TypeDef KeyMsg;





/***********************************************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url]  空函数
************************************************************************************************************/
void EmptyFunc(void)
{
    NOP();
}
/***********************************************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url]   获取按键参数元素
* @retval  找到对应键则返回对应结构元素指针,否则返回空键结构元素指针
************************************************************************************************************/
KeyElem_TypeDef const* get_KeyElem(UINT8 var)
{
    UINT8 i;
    for( i = 0; i < ( KEY_QTY + 1 ); i++ )
    {
        if( var == KeyElem[i].KeyCode )
        {
            return( &KeyElem[i] );
        }
    }
    return( &KeyElem[0] );
}
/***********************************************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url]  按键参数初始化
************************************************************************************************************/
void Key_Init(void)
{
    KeyMode.bMode = 0; //0=直接加减方式,1=位选加减方式
    KeyVar.GetKey = 0;
    KeyVar.KeyState = KEY_UP;
    KeyVar.FiltCnt = 0;
    KeyVar.LongCnt = 0;
    KeyVar.SetPoint = NO;
    KeyVar.PressTimes = 0;
}
/**************************************************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url]  键盘扫描函数
*         矩阵式扫描,RB7,RB6 为扫描读取口;RB2,RB1,RB0反向后作为键盘的行扫描。
* [url=home.php?mod=space&uid=536309]@NOTE[/url]   RB2,RB1,RB0反向后也是数码管和 LED的位驱动口,因此键盘扫描前要先保存
*         扫描端口的位值,扫描结束后还原。
* @Param  *PORTx    键盘扫描所在端口
* @retval KeyID
**************************************************************************************************************/
UINT8 KeyScan( PORT_TypeDef *PORTx )
{
    UINT8 retval, i;
    UINT8 PortKeep;
    UINT8 KeyPortVal;
    UINT8 MultiKeyCnt = 0;
    KeyElem_TypeDef const *pKeyElem;
   
    HC595_OEN_Pin = 1;                         // Disable 74HC595 out,RA2
    PortKeep = *PORTx;                          // Save Port Value , PORTB
    *PORTx |= KEY_SCAN_PINS_SET;               // 使能所有扫描引脚;RB2 ~ RB0通过UNL2003反向后作为键盘的行扫描,所以所有扫描引脚置高电平
    pKeyElem = &KeyElem[0];
    retval = KeyElem->KeyID;
    if( ( *PORTx & KEY_READ_PINS_SET  ) == KEY_READ_PINS_SET ) //判断按键释放
    {
        if( KeyVar.FiltCnt == 0 )
        {
            KeyVar.FiltCnt = 0;
            KeyVar.LongCnt = 0;
            KeyVar.GetKey = FALSE;
            KeyVar.KeyState = KEY_UP;
        }
        else
            KeyVar.FiltCnt--;
    }
    else
    {
        for( i = 0; i < KEY_SCAN_PINS_COUNT; i++ )
        {
            *PORTx &= ~KEY_SCAN_PINS_SET; // 扫描端口全禁止
            NOP( );
            NOP( );
            *PORTx |= 1 << i;
            NOP( );
            NOP( );
            if( ( *PORTx & KEY_READ_PINS_SET  ) != KEY_READ_PINS_SET )
            {
                KeyPortVal = *PORTx & ( KEY_READ_PINS_SET | KEY_SCAN_PINS_SET ); // *PORTx & 0xc7
                continue;
            }
        }
        if( ++KeyVar.FiltCnt > pKeyElem->FiltTime )            
        {
            KeyVar.FiltCnt = pKeyElem->FiltTime;
            KeyVar.KeyState = KEY_DOWN;
            
            pKeyElem = get_KeyElem( KeyPortVal );
            
            if( pKeyElem->LongTime > 0 )
            {
                if( ++KeyVar.LongCnt == pKeyElem->LongTime )
                {
                    KeyVar.LongCnt = pKeyElem->RenewTime;      // 长按键重新开始计时值,按键重新生效的的时间间隔等于长按生效时间减去重新开始时间
                    retval = pKeyElem->KeyID;                  // 返回对应键值的按键 ID
                }                                 
            }
            if( KeyVar.GetKey == FALSE )
            {
                KeyVar.GetKey = TRUE;
                retval =  pKeyElem->KeyID;
            }            
        }
    }
    *PORTx = PortKeep; // 还原端口值,因按键扫描引脚与数显引脚共用
    HC595_OEN_Pin = 0; // 允许 74595 三态输出口输出
    return retval;
}

/********************************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url]  按键事件函数回调
*
********************************************************************************************/
void KeyEventFunc( void )
{
    UINT8 FuncID;
    KeyElem_TypeDef const *pFunc;
   
    FuncID = KeyScan( &KEY_PORT );
    pFunc = &KeyElem[FuncID];
    pFunc->IsKeyDownFunc();
}



  

     

使用特权

评论回复
20
一路向北lm| | 2017-11-23 09:19 | 只看该作者
不错,机智云那个按键写的真的很棒,上次做项目研究了他们的代码,有好多地方需要学习。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

6

主题

38

帖子

1

粉丝