海思Hi3531 GPIO按键的长按、短按、连发——Linux驱动+应用程序

之前整理了一篇博文,是纯粹在应用层(用户空间)来轮询GPIO口的电平状态,来达到按键检测的目的。

https://blog.csdn.net/cfl927096306/article/details/88640930

显然这样就会一直不停的占用CPU,虽然每次轮询都delay了10毫秒,但这样的代码还是不够优秀。

本篇文章提供了另一种思路:

1. 由Linux驱动来完成按键的检测,借用Linux的输入子系统,再利用海思Hi3531的GPIO硬件中断来做

2. 应用层则使用系统调用read()函数来获取按键按下、抬起、长按的事件即可

Linux驱动程序如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/input.h>
#include <linux/jiffies.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/timer.h>

#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#include <mach/hardware.h>

//按键映射
//KEY1   -   GPIO6_1
//KEY2   -   GPIO5_7
//KEY3   -   GPIO5_5

#define MY_KEY1 1
#define MY_KEY2 2
#define MY_KEY3 3

#define WRITE_REG(Addr, Value) ((*(volatile unsigned int *)(Addr)) = (Value))
#define READ_REG(Addr)         (*(volatile unsigned int *)(Addr))

//Hi3531复用寄存器基地址
#define MUXCTRL_BASE_ADDR 0x200F0000
//Hi3531 GPIO5基地址
#define GPIO_5_BASE_ADDR  0x201A0000
//Hi3531 GPIO6基地址
#define GPIO_6_BASE_ADDR  0x201B0000

//方向控制寄存器,配置输入或输出
#define GPIO_DIR_OFFSET_ADDR    0x400
//中断触发寄存器,配置边沿或电平触发
#define GPIO_IS_OFFSET_ADDR     0x404
//双沿触发中断寄存器,配置单边沿或双边沿触发方式
#define GPIO_IBE_OFFSET_ADDR    0x408
//触发中断条件寄存器,配置下降沿/低电平或上升沿/高电平触发
#define GPIO_IEV_OFFSET_ADDR    0x40C
//中断屏蔽寄存器,用来屏蔽或使能中断
#define GPIO_IE_OFFSET_ADDR     0x410
//原始中断状态寄存器,用来查询 GPIO 管脚是否发生中断(0:未发生,1:发生)
#define GPIO_RIS_OFFSET_ADDR    0x414
//屏蔽状态中断寄存器,用来查询 GPIO 管脚屏蔽后的中断是否有效
#define GPIO_MIS_OFFSET_ADDR    0x418
//中断清除寄存器,用来清除管脚产生的中断,同时清除GPIO_RIS和GPIO_MIS
#define GPIO_IC_OFFSET_ADDR     0x41C

unsigned int muxctrl_virtual_addr = 0;
unsigned int gpio_5_virtual_addr = 0;
unsigned int gpio_6_virtual_addr = 0;

//定义一个结构体用来对输入按键进行描述
struct my_buttons_desc {
    int gpio;         // 表示对应的按键引脚
    int irq;          // 表示对应的中断位
    char *name;       // 表示对应的按键请求中断时的中断名
    int  key_code;    // 表示按键在输入子系统中对应的键值
};

//定义一个描述按键的数组
//根据hisi SDK文档得知:GPIO6的中断号是111,GPIO5的中断号是110
static struct my_buttons_desc buttons_desc[] = {
        {MY_KEY1, 111, "my_buttons_A", KEY_A},
        {MY_KEY2, 110, "my_buttons_B", KEY_B},
        {MY_KEY3, 110, "my_buttons_C", KEY_C},
};

//定义一个输入子系统的结构体指针变量
static struct input_dev *buttons_dev;
static struct my_buttons_desc *irq_buttons_desc = NULL;
static struct timer_list buttons_timer;


//地址映射
static int hi3531_virtual_addr_map(void)
{
    muxctrl_virtual_addr = (unsigned int)ioremap_nocache(MUXCTRL_BASE_ADDR, 0x10000);
    if(!muxctrl_virtual_addr)
    {
        printk("MUXCTRL_BASE_ADDR ioremap addr failed !\n");
        return -1;
    }

    gpio_5_virtual_addr = (unsigned int)ioremap_nocache(GPIO_5_BASE_ADDR, 0x10000);
    if(!gpio_5_virtual_addr)
    {
        printk("GPIO_5_BASE_ADDR ioremap addr failed !\n");
        return -1;
    }

    gpio_6_virtual_addr = (unsigned int)ioremap_nocache(GPIO_6_BASE_ADDR, 0x10000);
    if(!gpio_6_virtual_addr)
    {
        printk("GPIO_6_BASE_ADDR ioremap addr failed !\n");
        return -1;
    }

    return 0;
}

//取消地址映射
static void hi3531_virtual_addr_unmap(void)
{
    iounmap((void*)muxctrl_virtual_addr);
    iounmap((void*)gpio_5_virtual_addr);
    iounmap((void*)gpio_6_virtual_addr);
}

//海思官方提供的中断操作
//如果要产生中断,且避免假中断,则必须按照下面的初始化顺序:
//1. 配置 GPIO_IS,选择边沿触发或电平触发。
//2. 配置 GPIO_IEV,选择下降沿/上升沿触发和高电平/低电平触发。
//3. 如果选择边沿触发,需配置 GPIO_IBE,选择单沿或双沿触发方式。
//4. 保证 GPIO 数据线在以上操作过程中保持稳定。
//5. 向寄存器 GPIO_IC 写 0xFF,清中断。
//6. 配置 GPIO_IE 为 1,使能中断。
static int hi3531_button_gpio_config(void)
{
    unsigned int u32Reg = 0;

    //配置为gpio
    WRITE_REG(muxctrl_virtual_addr + 0xC4, 0x1);//KEY1   -   GPIO6_1
    WRITE_REG(muxctrl_virtual_addr + 0xBC, 0x1);//KEY3   -   GPIO5_7

    //配置为输入
    u32Reg = READ_REG(gpio_6_virtual_addr + GPIO_DIR_OFFSET_ADDR);
    u32Reg &= (~0x02);
    WRITE_REG(gpio_6_virtual_addr + GPIO_DIR_OFFSET_ADDR, u32Reg);//GPIO6_1

    u32Reg = READ_REG(gpio_5_virtual_addr + GPIO_DIR_OFFSET_ADDR);
    u32Reg &= (~0x80);
    WRITE_REG(gpio_5_virtual_addr + GPIO_DIR_OFFSET_ADDR, u32Reg);//GPIO5_7

    //配置中断
    u32Reg = READ_REG(gpio_6_virtual_addr + GPIO_IS_OFFSET_ADDR);
    u32Reg &= (~0x02);
    WRITE_REG(gpio_6_virtual_addr + GPIO_IS_OFFSET_ADDR, u32Reg); //GPIO6_1: 边沿触发中断

    u32Reg = READ_REG(gpio_6_virtual_addr + GPIO_IBE_OFFSET_ADDR);
    u32Reg |= (0x02);
    WRITE_REG(gpio_6_virtual_addr + GPIO_IBE_OFFSET_ADDR, u32Reg); //GPIO6_1: 双边沿触发中断

    WRITE_REG(gpio_6_virtual_addr + GPIO_IC_OFFSET_ADDR, 0xFF); //GPIO6: 清除中断

    u32Reg = READ_REG(gpio_6_virtual_addr + GPIO_IE_OFFSET_ADDR);
    u32Reg |= (0x02);
    WRITE_REG(gpio_6_virtual_addr + GPIO_IE_OFFSET_ADDR, u32Reg); //GPIO6_1: 使能中断


    u32Reg = READ_REG(gpio_5_virtual_addr + GPIO_IS_OFFSET_ADDR);
    u32Reg &= (~0xA0);
    WRITE_REG(gpio_5_virtual_addr + GPIO_IS_OFFSET_ADDR, u32Reg); //GPIO5_7,GPIO5_5: 边沿触发中断

    u32Reg = READ_REG(gpio_5_virtual_addr + GPIO_IBE_OFFSET_ADDR);
    u32Reg |= (0xA0);
    WRITE_REG(gpio_5_virtual_addr + GPIO_IBE_OFFSET_ADDR, u32Reg); //GPIO5_7,GPIO5_5: 双边沿触发中断

    WRITE_REG(gpio_5_virtual_addr + GPIO_IC_OFFSET_ADDR, 0xFF); //GPIO5: 清除中断

    u32Reg = READ_REG(gpio_5_virtual_addr + GPIO_IE_OFFSET_ADDR);
    u32Reg |= (0xA0);
    WRITE_REG(gpio_5_virtual_addr + GPIO_IE_OFFSET_ADDR, u32Reg); //GPIO5_7,GPIO5_5: 使能中断

    return 0;
}

//GPIO按键中断处理函数
static irqreturn_t my_buttons_irq(int irq, void *dev_id)
{
    unsigned int u32Reg = 0;
    struct my_buttons_desc *tmp_desc = (struct my_buttons_desc *)dev_id;

    //因为是一组GPIO(8个pin)共享一个中断号,所以这里一开始就要判断到底是哪个中断来了
    //通过读中断状态寄存器来判断
    if(tmp_desc->gpio == MY_KEY1)
    {
        u32Reg = READ_REG(gpio_6_virtual_addr + GPIO_RIS_OFFSET_ADDR);
        if(!(u32Reg & 0x02)) //GPIO6_1
        {
            //MY_KEY1 interrupt not happened
            return IRQ_HANDLED;
        }
        WRITE_REG(gpio_6_virtual_addr + GPIO_IC_OFFSET_ADDR, 0xFF); //GPIO6: 清除中断
    }
    else if(tmp_desc->gpio == MY_KEY2)
    {
        u32Reg = READ_REG(gpio_5_virtual_addr + GPIO_RIS_OFFSET_ADDR);
        if(!(u32Reg & 0x80)) //GPIO5_7
        {
            //MY_KEY2 interrupt not happened
            return IRQ_HANDLED;
        }
        WRITE_REG(gpio_5_virtual_addr + GPIO_IC_OFFSET_ADDR, 0xFF);//GPIO5: 清除中断
    }
    else if(tmp_desc->gpio == MY_KEY3)
    {
        u32Reg = READ_REG(gpio_5_virtual_addr + GPIO_RIS_OFFSET_ADDR);
        if(!(u32Reg & 0x20)) //GPIO5_5
        {
            //MY_KEY3 interrupt not happened
            return IRQ_HANDLED;
        }
        WRITE_REG(gpio_5_virtual_addr + GPIO_IC_OFFSET_ADDR, 0xFF);//GPIO5: 清除中断
    }

    //按键IO发生边沿中断时重新设置定时间隔,用于按键消抖
    //20ms之后触发定时器中断,执行my_buttons_timer_function(),并将buttons_timer.data传过去
    irq_buttons_desc = (struct my_buttons_desc *)dev_id;
    buttons_timer.data = irq_buttons_desc->gpio;
    mod_timer(&buttons_timer, jiffies+msecs_to_jiffies(20));

    return IRQ_HANDLED;
}

static unsigned int my_buttons_read_gpio(unsigned int gpio)
{
    unsigned int gpio_level = 0;

    switch(gpio)
    {
        case MY_KEY1:
            gpio_level = READ_REG(gpio_6_virtual_addr + (0x02 << 2)); //GPIO6_1
            gpio_level = gpio_level >> 1;
            break;
        case MY_KEY2:
            gpio_level = READ_REG(gpio_5_virtual_addr + (0x80 << 2)); //GPIO5_7
            gpio_level = gpio_level >> 7;
            break;
        case MY_KEY3:
            gpio_level = READ_REG(gpio_5_virtual_addr + (0x20 << 2)); //GPIO5_5
            gpio_level = gpio_level >> 5;
            break;
        default:
            break;
    }
    return gpio_level;
}

//定时器中断处理函数
static void my_buttons_timer_function(unsigned long data)
{
    unsigned int gpio_level;

    if (!irq_buttons_desc)
    {
        // 初始化定时器会走进该function一次
        printk("irq_buttons_desc == NULL, return\n");
        return;
    }

    //获取按键IO状态
    gpio_level = my_buttons_read_gpio((unsigned int)data);
    printk("my_buttons_timer_function: gpio = %ld, gpio_level = %d\n", data, gpio_level);

    //根据按键IO状态上报按键事件
    if (gpio_level)
    {
        //上报按键弹起
        input_event(buttons_dev, EV_KEY, irq_buttons_desc->key_code, 0);
        input_sync(buttons_dev);
    }
    else
    {
        //上报按键按下
        input_event(buttons_dev, EV_KEY, irq_buttons_desc->key_code, 1);
        input_sync(buttons_dev);
    }
}

//入口函数
static int __init my_buttons_init(void)
{
    int i = 0;
    int ret = 0;

    printk("my_buttons_init start\n");

    //1、分配一个input_dev结构体
    buttons_dev = input_allocate_device();
    if(!buttons_dev)
    {
        printk("input_allocate_device error!\n");
        return -ENOMEM;
    }

    //2、设置input_dev结构体
    //2.1、设置支持的事件类型
    set_bit(EV_KEY, buttons_dev->evbit);
    set_bit(EV_REP, buttons_dev->evbit); //支持长按

    //2.2、设置支持该类事件中的事件码
    for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
    {
        set_bit(buttons_desc[i].key_code, buttons_dev->keybit);
    }

    //2.3、硬件相关的操作
    hi3531_virtual_addr_map();
    hi3531_button_gpio_config();

    //3、中断相关的操作
    //为每个按键申请一个中断,共用中断处理函数my_buttons_irq()
    //按键触发方式为双边沿触发
    for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
    {
        //Hi3531的一组GPIO只有一个中断号,一组GPIO有8个pin,所以这里得是共享中断
        ret = request_irq(buttons_desc[i].irq, my_buttons_irq, IRQF_SHARED, buttons_desc[i].name, (void*)&buttons_desc[i]);
        printk("request_irq %s\n", ret==0?"succeed":"failed");
    }

    //4、注册input_dev结构体
    input_register_device(buttons_dev);

    //初始化定时器,用于按键消抖
    init_timer(&buttons_timer);
    buttons_timer.function = my_buttons_timer_function;
    add_timer(&buttons_timer);

    printk("my_buttons_init end\n");

    return 0;
}

//出口函数
static void __exit my_buttons_exit(void)
{
    int i;

    printk("my_buttons_exit start\n");

    hi3531_virtual_addr_unmap();

    //释放申请的按键中断
    for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
    {
        free_irq(buttons_desc[i].irq, (void*)&buttons_desc[i]);
    }

    //删除定时器
    del_timer(&buttons_timer);

    //注销输入设备
    input_unregister_device(buttons_dev);

    //释放输入设备内存空间
    input_free_device(buttons_dev);

    printk("my_buttons_exit end\n");
}

module_init(my_buttons_init);
module_exit(my_buttons_exit);
MODULE_LICENSE("GPL");

将以上驱动程序编译成kernel模块,需事先完整编译一遍kernel代码,如:

该文件命名为xxx.c

在 linux-3.0.y/drivers/input/keyboard/Makefile 加一句

obj-m       += xxx.o

把该文件放于 linux-3.0.y/drivers/input/keyboard 目录下

cd linux-3.0.y

make ARCH=arm CROSS_COMPILE=arm-hisiv200-linux- SUBDIRS=./drivers/input/keyboard  modules

 

简单的应用程序如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

#include <linux/input.h>

/*
struct input_event {
    struct timeval time;
    __u16 type; //EV_SYN=0x00,EV_KEY=0x01
    __u16 code; //KEY_A,KEY_B,KEY_C
    __s32 value;//抬起=0,按下=1,长按=2
};
*/

int main(int argc, char **argv)
{
    int fd = 0;
    struct input_event buttons_event;
    unsigned long cur_ms = 0;

    //fd = open("/dev/event0", O_RDWR | O_NONBLOCK);
    fd = open("/dev/event0", O_RDWR);
    if (fd < 0)
    {
        printf("can't open!\n");
        return -1;
    }

    while (1)
    {
        read(fd, &buttons_event, sizeof(struct input_event));

        if(buttons_event.type == EV_SYN)
            continue;

        cur_ms = (buttons_event.time.tv_sec * 1000) + (buttons_event.time.tv_usec/1000);

        //打印时间,事件类型,事件码,事件值
        printf("cur_ms:%ld type:0x%x code:%d value:%d\n",
            cur_ms,
            buttons_event.type,
            buttons_event.code,
            buttons_event.value);
    }

    return 0;
}

安装驱动后(insmod xxx.ko),执行  cat /proc/interrupts  看一下中断相关情况,可见110这个中断号挂了两个gpio pin (上面的驱动代码就是这样写的)

运行应用程序,效果如下

以上应用程序平时没人按按键的话,就会阻塞在read()函数,不占用CPU资源,比轮询的优秀!

要想识别长按、短按、连发,还得加写逻辑判断和去掉应用程序消抖的动作(已在kernel驱动做了消抖了)。

代码如下:

 

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

#include <linux/input.h>

// 定义长按键的TICK数, 以及连发间隔的TICK数, 单位是毫秒ms
//#define KEY_DEBOUNCE_PERIOD     20   // 延时消抖已经在kernel驱动做了,应用层不需要做了
#define KEY_LONG_PERIOD         1000
#define KEY_CONTINUE_PERIOD     200

#define TRUE    1
#define FALSE   0

typedef unsigned int Bool;
typedef void (*pf)(void);

typedef enum
{
    APP_KEY1 = 0,
    APP_KEY2,
    APP_KEY3,
    APP_KEY_MAX_NUM
} KEY_NUM_E;

typedef enum {
    APP_KEY_STATE_INIT = 0,
    APP_KEY_STATE_WOBBLE,
    APP_KEY_STATE_PRESS,
    APP_KEY_STATE_LONG,
    APP_KEY_STATE_CONTINUE,
    APP_KEY_STATE_RELEASE
} KEY_STATE_E;

void key1DownAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key1LongAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key1ContinueAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key1DownUpAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key1LongUpAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key2DownAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key2LongAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key2ContinueAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key2DownUpAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key2LongUpAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key3DownAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key3LongAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key3ContinueAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key3DownUpAction(void)
{
    printf("%s\n", __FUNCTION__);
}

void key3LongUpAction(void)
{
    printf("%s\n", __FUNCTION__);
}


typedef struct
{
    KEY_NUM_E eKeyId;
    unsigned int downTick;
    unsigned int upTick;
    KEY_STATE_E eKeyCurState;      //key cur state(fsm)
    Bool bStateChangedFlag;        //state changed flag

    pf keyDownAction;
    pf keyLongAction;
    pf keyContinueAction;
    pf keyDownUpAction;
    pf keyLongUpAction;
} KEY_HANDLE_T;


KEY_HANDLE_T keyList[APP_KEY_MAX_NUM] =
{
    {APP_KEY1, 0, 0, APP_KEY_STATE_INIT, FALSE, key1DownAction, key1LongAction, key1ContinueAction, key1DownUpAction, key1LongUpAction},
    {APP_KEY2, 0, 0, APP_KEY_STATE_INIT, FALSE, key2DownAction, key2LongAction, key2ContinueAction, key2DownUpAction, key2LongUpAction},
    {APP_KEY3, 0, 0, APP_KEY_STATE_INIT, FALSE, key3DownAction, key3LongAction, key3ContinueAction, key3DownUpAction, key3LongUpAction},
};


void keyScan(unsigned long cur_ms, int value, KEY_HANDLE_T *key)
{
    if(key == NULL)
    {
        printf("key == NULL, return\n");
        return;
    }

    switch(key->eKeyCurState)
    {
        case APP_KEY_STATE_INIT:
            if(value == 1)
            {
                key->downTick = cur_ms;
                key->keyDownAction(); //短按
            }

            if(value)
            {
                if((cur_ms - key->downTick) >= KEY_LONG_PERIOD)
                {
                    key->bStateChangedFlag = TRUE;
                    key->eKeyCurState = APP_KEY_STATE_LONG;
                }
            }
            else
            {
                key->upTick = cur_ms;
                key->bStateChangedFlag = TRUE;
                key->eKeyCurState = APP_KEY_STATE_INIT;
                key->keyDownUpAction(); //短按抬起
            }
            break;

        case APP_KEY_STATE_LONG:
            if(TRUE == key->bStateChangedFlag)
            {
                key->bStateChangedFlag = FALSE;
                key->keyLongAction(); //长按
            }

            if(value)
            {
                if((cur_ms - key->downTick) >= (KEY_LONG_PERIOD + KEY_CONTINUE_PERIOD))
                {
                    key->downTick = cur_ms;
                    key->bStateChangedFlag = TRUE;
                    key->eKeyCurState = APP_KEY_STATE_CONTINUE;
                }
            }
            else
            {
                key->upTick = cur_ms;
                key->bStateChangedFlag = TRUE;
                key->eKeyCurState = APP_KEY_STATE_INIT;
                key->keyLongUpAction(); //长按抬起
            }
            break;

        case APP_KEY_STATE_CONTINUE:
            if(TRUE == key->bStateChangedFlag)
            {
                key->bStateChangedFlag = FALSE;
                key->keyContinueAction(); //连发
            }

            if(value)
            {
                if((cur_ms - key->downTick) >= KEY_CONTINUE_PERIOD)
                {
                    key->downTick = cur_ms;
                    key->bStateChangedFlag = TRUE;
                    key->eKeyCurState = APP_KEY_STATE_CONTINUE;
                }
            }
            else
            {
                key->upTick = cur_ms;
                key->bStateChangedFlag = TRUE;
                key->eKeyCurState = APP_KEY_STATE_INIT;
                key->keyLongUpAction(); //长按抬起
            }
            break;

        default:
            break;
    }
}

/*
struct input_event {
    struct timeval time;
    __u16 type; //EV_SYN=0x00,EV_KEY=0x01
    __u16 code; //KEY_A,KEY_B,KEY_C
    __s32 value;//抬起=0,按下=1,长按=2
};
*/

int main(int argc, char **argv)
{
    int fd = 0;
    struct input_event buttons_event;
    KEY_HANDLE_T *curKey = NULL;
    unsigned long cur_ms = 0;

    //fd = open("/dev/event0", O_RDWR | O_NONBLOCK);
    fd = open("/dev/event0", O_RDWR);
    if (fd < 0)
    {
        printf("can't open!\n");
        return -1;
    }

    while (1)
    {
        read(fd, &buttons_event, sizeof(struct input_event));

        if(buttons_event.type == EV_SYN)
            continue;

        cur_ms = (buttons_event.time.tv_sec * 1000) + (buttons_event.time.tv_usec/1000);

        //打印时间,事件类型,事件码,事件值
        printf("cur_ms:%ld type:0x%x code:%d value:%d\n",
            cur_ms,
            buttons_event.type,
            buttons_event.code,
            buttons_event.value);

        // match key struct
        switch(buttons_event.code)
        {
            case KEY_A:
                curKey = &keyList[APP_KEY1];
                break;
            case KEY_B:
                curKey = &keyList[APP_KEY2];
                break;
            case KEY_C:
                curKey = &keyList[APP_KEY3];
                break;
            default:
                curKey = NULL;
                break;
        }

        keyScan(cur_ms, buttons_event.value, curKey);
    }

    return 0;
}

运行该应用程序

短按的情况:

长按的情况:

连发的情况:

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cfl927096306

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值