沧海一瞬 发表于 2017-9-10 22:27

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

本按键实现移植机制云的stm32按键扫描,不过也有些不同。
不同点:

沧海一瞬 发表于 2017-9-10 22:28

在定时器中断中扫描,不用10ms延时,动态响应极好。实现长按短按。
创建对象极度方便。
用户只需要根据自己需要修改keyInit( )函数和下图中的变量即可。

沧海一瞬 发表于 2017-9-10 22:29

接下来是51的介绍.................................................


沧海一瞬 发表于 2017-9-10 22:30

上传.c文件。51附例程,32只有只有.c文件。



xyz549040622 发表于 2017-9-11 07:36

观摩下这个按键算法。

liujie14565 发表于 2017-9-11 11:24

状态机法,都是这么处理的

batsong 发表于 2017-9-11 11:58

几年前做过C++的按键类,可创建多实例,线程计时,不用阻塞delay,长短按、消抖参数可独立设置,按下、弹起、click等多种事件支持

xouou_53320 发表于 2017-9-11 12:06

楼主大家都是这么干的

沧海一瞬 发表于 2017-9-11 14:10

xouou_53320 发表于 2017-9-11 12:06
楼主大家都是这么干的

高手是怎么干的。新手进阶高手可以看看。{:lol:}

shiman 发表于 2017-9-11 18:02

沧海一瞬 发表于 2017-9-11 18:41

batsong 发表于 2017-9-11 11:58
几年前做过C++的按键类,可创建多实例,线程计时,不用阻塞delay,长短按、消抖参数可独立设置,按下、弹起 ...

厉害了{:lol:},我没学过C++

hjl714016 发表于 2017-9-11 20:33

按键扫描还用delay死等,是没真正做过项目的,,,

litianchenghao 发表于 2017-9-12 10:18

看不懂,额额

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->key_level = 1;

      PIN_FUNC_SELECT(keys->single_key->gpio_name, keys->single_key->gpio_func);

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

      gpio_register_set(GPIO_PIN_ADDR(keys->single_key->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->gpio_id));

      //enable interrupt
      gpio_pin_intr_state_set(GPIO_ID_PIN(keys->single_key->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->gpio_id,i,BIT(keys->single_key->gpio_id));
      if (gpio_status & BIT(keys->single_key->gpio_id)) {
                        //os_printf("+++++++++++++++++      i: %d    \n",i);


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

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

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


      }

    }

}

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;


void my_key_init(void)
{
        struct single_key_param *psingle_key;// = //&single_key;
        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 = psingle_key;
        key.single_key = single_key;//&psingle_key;

        key_init(&key);
}

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;
}   

座机呀 发表于 2017-9-13 20:36

恕我愚钝,真没看出来哪里精妙了?

沧海一瞬 发表于 2017-9-16 12:25

座机呀 发表于 2017-9-13 20:36
恕我愚钝,真没看出来哪里精妙了?

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

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 xx 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;


// 按键元素表
KeyElem_TypeDef const KeyElem = {
    { 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;





/***********************************************************************************************************
* @brief空函数
************************************************************************************************************/
void EmptyFunc(void)
{
    NOP();
}
/***********************************************************************************************************
* @brief   获取按键参数元素
* @retval找到对应键则返回对应结构元素指针,否则返回空键结构元素指针
************************************************************************************************************/
KeyElem_TypeDef const* get_KeyElem(UINT8 var)
{
    UINT8 i;
    for( i = 0; i < ( KEY_QTY + 1 ); i++ )
    {
      if( var == KeyElem.KeyCode )
      {
            return( &KeyElem );
      }
    }
    return( &KeyElem );
}
/***********************************************************************************************************
* @brief按键参数初始化
************************************************************************************************************/
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;
}
/**************************************************************************************************************
* @brief键盘扫描函数
*         矩阵式扫描,RB7,RB6 为扫描读取口;RB2,RB1,RB0反向后作为键盘的行扫描。
* @NOTE   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;
    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;
}

/********************************************************************************************
* @brief按键事件函数回调
*
********************************************************************************************/
void KeyEventFunc( void )
{
    UINT8 FuncID;
    KeyElem_TypeDef const *pFunc;
   
    FuncID = KeyScan( &KEY_PORT );
    pFunc = &KeyElem;
    pFunc->IsKeyDownFunc();
}




   

一路向北lm 发表于 2017-11-23 09:19

不错,机智云那个按键写的真的很棒,上次做项目研究了他们的代码,有好多地方需要学习。
页: [1] 2
查看完整版本: 精妙的按键扫描函数,不采用延时消抖,实现长短按,创...