打印
[CW32F030系列]

【CW32F030CxTx StartKit测评】03.基于MultiButton的按键检测

[复制链接]
1276|24
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 xld0932 于 2022-7-2 19:46 编辑

概述
CW32F030CxTx StartKit开发板板载了三个按键,一个是用于复位芯片的RESET按键,另外两个是可用户自定义的编程按键,通过原理图知道KEY1/KEY2分别与PA1/PA2相连接,本文将结合开源的MultiButton软件库现实现这两个按键的多态功能;为了能够实时监测/查看按键状态,我们还配置了USART串口实现了printf的功能,当然为了后面的调试方便,基于USART还移植了Letter-shell_3.x软件库,为后面调试、或者是发送shell命令调用软件代码功能先做好准备,当前本文先讲述一下移植,具体应用到后面使用的时候再详细描述。


MultiButton
MultiButton是一个小巧装简单易用的事件驱动型按键驱动模块,使用C语言实现,它是基于面向对象方式设计思路实现的,每个按键对象都会单独使用一份数据结构来管理,所以MultiButton可以无限量的扩展按键,其按键事件的回调异步处理方式可以简化程序结构,去除了冗余的按键处理硬编码,让按键业务逻辑更清晰。详情可以参考如下链接:https://github.com/0x1abin/MultiButton

MultiButton包含了7种类型的按键事件,如下所示:
事件
说明
PRESS_DOWN
按键按下,每次按下都触发
PRESS_UP
按键抬起,每次松开都触发
PRESS_REPEAT
重复按下触发,变量repeat计数连击次数
SINGLE_CLICK
单击按键事件
DOUBLE_CLICK
双击按键事件
LONG_PRESS_START
达到长按时间阈值触发一次
LONG_PRESS_HOLD
长按期间一直触发


MultiButton的使用方法也很简单,下面将一步步来进行讲解……
  • 申请一个全局按键结构体变量

struct Button button1

  • 初始化按键对象,此时需要绑定这个按键的GPIO端口引脚电平读取接口函数read_pin_level(),后面两个参数是按键有效触发的电平值和按键ID号,这个ID号必须是唯一值,不能重复哦

button_init(&button1, read_pin_level, 0, 1)

  • 注册按键事件,用户可以根据应用功能的需要对相应的按键注册一个或者多个事件,然后在回调函数里面实现用户按键的具体功能

button_attach(&button1, SINGLE_CLICK, BTN_SingleClickHandler)

button_attach(&button1, DOUBLE_CLICK, BTN_DoubleClickHandler)

  • 启动按键

button_start(&button1)

  • 设置一个循环调用后台的处理函数,我们使用上一节讲到的MultiTimer来实现这个功能,调用button_ticks()函数即可。


MultiButton实现代码
MultiTimer KEY_MultiTimer;

struct Button KEY1;
struct Button KEY2;

uint8_t key1_pin_level(void)
{
    return GPIO_ReadPin(CW_GPIOA, GPIO_PIN_1);
}

uint8_t key2_pin_level(void)
{
    return GPIO_ReadPin(CW_GPIOA, GPIO_PIN_2);
}

void KEY_PressDownHandler(void *btn)
{
    Button *key = btn;

    switch(key->button_id)
    {
        case 1 : printf("\r\nKEY1 : Press Down"); break;
        case 2 : printf("\r\nKEY2 : Press Down"); break;
        default: break;
    }
}

void KEY_PressUpHandler(void *btn)
{
    Button *key = btn;

    switch(key->button_id)
    {
        case 1 : printf("\r\nKEY1 : Press Up"); break;
        case 2 : printf("\r\nKEY2 : Press Up"); break;
        default: break;
    }
}

void KEY_PressRepeatHandler(void *btn)
{
    Button *key = btn;

    switch(key->button_id)
    {
        case 1 : printf("\r\nKEY1 : Press Repeat"); break;
        case 2 : printf("\r\nKEY2 : Press Repeat"); break;
        default: break;
    }
}

void KEY_SingleClickHandler(void *btn)
{
    Button *key = btn;

    switch(key->button_id)
    {
        case 1 : printf("\r\nKEY1 : Single Click"); break;
        case 2 : printf("\r\nKEY2 : Single Click"); break;
        default: break;
    }
}

void KEY_DoubleClickHandler(void *btn)
{
    Button *key = btn;

    switch(key->button_id)
    {
        case 1 : printf("\r\nKEY1 : Double Click"); break;
        case 2 : printf("\r\nKEY2 : Double Click"); break;
        default: break;
    }
}

void KEY_LongPressStartHandler(void *btn)
{
    Button *key = btn;

    switch(key->button_id)
    {
        case 1 : printf("\r\nKEY1 : Long Press Start"); break;
        case 2 : printf("\r\nKEY2 : Long Press Start"); break;
        default: break;
    }
}

void KEY_LongPressHoldHandler(void *btn)
{
    Button *key = btn;

    switch(key->button_id)
    {
        case 1 : printf("\r\nKEY1 : Long Press Hold"); break;
        case 2 : printf("\r\nKEY2 : Long Press Hold"); break;
        default: break;
    }
}

void KEY_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    __RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStruct.Pins  = GPIO_PIN_1 | GPIO_PIN_2;
    GPIO_InitStruct.Mode  = GPIO_MODE_INPUT;
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStruct.IT    = GPIO_IT_NONE;
    GPIO_Init(CW_GPIOA, &GPIO_InitStruct);

    button_init(&KEY1, key1_pin_level, GPIO_Pin_RESET, 1);
    button_init(&KEY2, key2_pin_level, GPIO_Pin_RESET, 2);

    button_attach(&KEY1, PRESS_DOWN,        KEY_PressDownHandler);
    button_attach(&KEY1, PRESS_UP,          KEY_PressUpHandler);
    button_attach(&KEY1, PRESS_REPEAT,      KEY_PressRepeatHandler);
    button_attach(&KEY1, SINGLE_CLICK,      KEY_SingleClickHandler);
    button_attach(&KEY1, DOUBLE_CLICK,      KEY_DoubleClickHandler);
    button_attach(&KEY1, LONG_PRESS_START,  KEY_LongPressStartHandler);
    button_attach(&KEY1, LONG_PRESS_HOLD,   KEY_LongPressHoldHandler);

    button_attach(&KEY2, SINGLE_CLICK,      KEY_SingleClickHandler);

    button_start(&KEY1);
    button_start(&KEY2);

    MultiTimerStart(&KEY_MultiTimer, 5, KEY_MultiTimerCallback, "KEY Scan");
}

void KEY_MultiTimerCallback(MultiTimer *timer, void *userData)
{
    button_ticks();

    MultiTimerStart(&KEY_MultiTimer, 5, KEY_MultiTimerCallback, "KEY Scan");
}


运行结果


Letter-shell_3.x
Letter-shell是一个用C语言来编写的,可以嵌入在程序中的嵌入式shell,主要面向嵌入式设备,以C语言函数为运行单位,可以通过命令行调用,运行程序中的函数。其实从早期的2.x版本就已经开发开使用了,相比于其它shell来说操作更为方便,增加shell指令的方式也更为灵活;在CW32芯片上是首次基于USART来移植Letter-shell_3.x的版本,相对于功能而言,新版本增加了用户管理、权限管理、以及对文件系统的初步支持;但对于移植来说,最大的体会就是修改了读写函数的原型,提升了效率;还有就是在Letter-shell_3.x版本上需要申请一片缓冲区,其它的与Letter-shell_2.x的移植大体一致,详情可以参考如下链接:https://github.com/NevermindZZT/letter-shell,费话不多说,直接上代码:
Shell shell;

char shellBuffer[512];

signed short shellPortRead(char *data, unsigned short len)
{
    return len;
}

signed short shellPortWrite(char *data, unsigned short len)
{
    for(unsigned short i = 0; i < len; i++)
    {
        USART_SendData_8bit(CW_UART1, *data++);
        while(USART_GetFlagStatus(CW_UART1, USART_FLAG_TXE) == RESET);
    }

    while(USART_GetFlagStatus(CW_UART1,  USART_FLAG_TXBUSY) != RESET);

    return len;
}

void shellPortInit(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_GPIOA,   ENABLE);
    RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_UART1, ENABLE);

    PA08_AFx_UART1TXD();
    PA09_AFx_UART1RXD();

    GPIO_InitStructure.Pins  = GPIO_PIN_8;
    GPIO_InitStructure.Mode  = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.Pins  = GPIO_PIN_9;
    GPIO_InitStructure.Mode  = GPIO_MODE_INPUT_PULLUP;
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_Over     = USART_Over_16;
    USART_InitStructure.USART_Source   = USART_Source_PCLK;
    USART_InitStructure.USART_UclkFreq = 64000000;
    USART_InitStructure.USART_StartBit = USART_StartBit_FE;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity   = USART_Parity_No ;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode                 = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(CW_UART1, &USART_InitStructure);         

    NVIC_SetPriority(UART1_IRQn, 0);
    NVIC_EnableIRQ(UART1_IRQn);

    USART_ITConfig(CW_UART1, USART_IT_RC, ENABLE);

    shell.read  = shellPortRead;
    shell.write = shellPortWrite;
    shellInit(&shell, shellBuffer, sizeof(shellBuffer));
}

void shellPortHandler(void)
{
    if(USART_GetITStatus(CW_UART1, USART_IT_RC) != RESET)
    {
        shellHandler(&shell, USART_ReceiveData_8bit(CW_UART1));
        USART_ClearITPendingBit(CW_UART1, USART_IT_RC);
    }
}

int fputc(int ch, FILE *f)
{
    USART_SendData_8bit(CW_UART1, (uint8_t)ch);
    while(USART_GetFlagStatus(CW_UART1, USART_FLAG_TXE)  == RESET);

    while(USART_GetFlagStatus(CW_UART1, USART_FLAG_TXBUSY) == SET);

    return ch;
}


运行结果


工程源码
Template.zip (4.87 MB)


使用特权

评论回复
沙发
xld0932|  楼主 | 2022-7-2 23:08 | 只看该作者
在上面程序中给按键的事件添加回调函数时,每一个事件都对应了一个函数,这样就导致需要实现的函数太多了……是不是可以通过一个函数来解决呢,后来发现我们可以通过event这个属性来进行判断,将所有的event回调函数放到一个函数里来……然后根据button->event的属性值来区别处理……实现如下:
void KEY_Handler(void *btn)
{
    Button *key = btn;

    char *KEY_NAME[3]  = {"NONE", "KEY1", "KEY2"};

    switch(key->event)
    {
        case PRESS_DOWN:
            printf("\r\n%s : PRESS_DOWN",       KEY_NAME[key->button_id]);
            break;

        case PRESS_UP:
            printf("\r\n%s : PRESS_UP",         KEY_NAME[key->button_id]);
            break;

        case PRESS_REPEAT:
            printf("\r\n%s : PRESS_REPEAT",     KEY_NAME[key->button_id]);
            break;

        case SINGLE_CLICK:
            printf("\r\n%s : SINGLE_CLICK",     KEY_NAME[key->button_id]);
            break;

        case DOUBLE_CLICK:
            printf("\r\n%s : DOUBLE_CLICK",     KEY_NAME[key->button_id]);
            break;

        case LONG_PRESS_START:
            printf("\r\n%s : LONG_PRESS_START", KEY_NAME[key->button_id]);
            break;

        case LONG_PRESS_HOLD:
            printf("\r\n%s : LONG_PRESS_HOLD",  KEY_NAME[key->button_id]);
            break;

        default:
            break;
    }
}

……
    button_attach(&KEY1, PRESS_DOWN,        KEY_Handler);
    button_attach(&KEY1, PRESS_UP,          KEY_Handler);
    button_attach(&KEY1, PRESS_REPEAT,      KEY_Handler);
    button_attach(&KEY1, SINGLE_CLICK,      KEY_Handler);
    button_attach(&KEY1, DOUBLE_CLICK,      KEY_Handler);
    button_attach(&KEY1, LONG_PRESS_START,  KEY_Handler);
    button_attach(&KEY1, LONG_PRESS_HOLD,   KEY_Handler);

    button_attach(&KEY2, SINGLE_CLICK,      KEY_Handler);
……

但是在实际调试的过程中我们又遇到问题,发现上面这种写法,PRESS_REPEAT的回调函数没有执行……通过分析发现问题出现在MultiButton的如下的语句部分:
……
        case 2:
            if(handle->button_level == handle->active_level)    //press down again
            {
                handle->event = (uint8_t)PRESS_DOWN;
                EVENT_CB(PRESS_DOWN);
                handle->repeat++;
                EVENT_CB(PRESS_REPEAT);                         // repeat hit
                handle->ticks = 0;
                handle->state = 3;
            }
            else if(handle->ticks > SHORT_TICKS)                //released timeout
            {
                if(handle->repeat == 1)
                {
                    handle->event = (uint8_t)SINGLE_CLICK;
                    EVENT_CB(SINGLE_CLICK);
                }
                else if(handle->repeat == 2)
                {
                    handle->event = (uint8_t)DOUBLE_CLICK;
                    EVENT_CB(DOUBLE_CLICK);                     // repeat hit
                }

                handle->state = 0;
            }
            break;
……

上面这部分处理对event只是赋了PRESS_DOWN值,但是却调用了PRESS_DOWN和PRESS_REPEAT这两个回调函数,这就导致我们在KEY_Handle中只会执行PRESS_DOWN分支,而PRESS_REPEAT永远执行不到了……所以在不修改MultiButton代码的基础上,我们只好将PRESS_REPEAT的回调函数单独拿出来处理,如下程序:
void KEY_Handler(void *btn)
{
    Button *key = btn;

    char *KEY_NAME[3]  = {"NONE", "KEY1", "KEY2"};

    switch(key->event)
    {
        case PRESS_DOWN:
            printf("\r\n%s : PRESS_DOWN",       KEY_NAME[key->button_id]);
            break;

        case PRESS_UP:
            printf("\r\n%s : PRESS_UP",         KEY_NAME[key->button_id]);
            break;

        case SINGLE_CLICK:
            printf("\r\n%s : SINGLE_CLICK",     KEY_NAME[key->button_id]);
            break;

        case DOUBLE_CLICK:
            printf("\r\n%s : DOUBLE_CLICK",     KEY_NAME[key->button_id]);
            break;

        case LONG_PRESS_START:
            printf("\r\n%s : LONG_PRESS_START", KEY_NAME[key->button_id]);
            break;

        case LONG_PRESS_HOLD:
            printf("\r\n%s : LONG_PRESS_HOLD",  KEY_NAME[key->button_id]);
            break;

        default:
            break;
    }
}

void KEY_RepeatHandler(void *btn)
{
    Button *key = btn;

    switch(key->button_id)
    {
        case 1 : printf("\r\nKEY1 : PRESS_REPEAT"); break;
        case 2 : printf("\r\nKEY2 : PRESS_REPEAT"); break;
        default: break;
    }
}
……
    button_attach(&KEY1, PRESS_DOWN,        KEY_Handler);
    button_attach(&KEY1, PRESS_UP,          KEY_Handler);
    button_attach(&KEY1, PRESS_REPEAT,      KEY_RepeatHandler);
    button_attach(&KEY1, SINGLE_CLICK,      KEY_Handler);
    button_attach(&KEY1, DOUBLE_CLICK,      KEY_Handler);
    button_attach(&KEY1, LONG_PRESS_START,  KEY_Handler);
    button_attach(&KEY1, LONG_PRESS_HOLD,   KEY_Handler);

    button_attach(&KEY2, SINGLE_CLICK,      KEY_Handler);
……

这个时候,所有的处理就全都正常了……为了少写一些函数,竟然发现了这个注意点!跟大家分享一下……

使用特权

评论回复
板凳
chenjun89| | 2022-7-3 10:04 | 只看该作者
下载看看,谢谢分享。

使用特权

评论回复
地板
www5911839| | 2022-7-3 11:43 | 只看该作者
感谢大佬分享好资料,内容详尽,太辛苦了

使用特权

评论回复
5
740071911| | 2022-7-3 21:09 | 只看该作者
Letter-shell加进去有什么好处

使用特权

评论回复
6
weifeng90| | 2022-7-4 07:49 | 只看该作者
哈哈,letter-shell好用。

使用特权

评论回复
7
xld0932|  楼主 | 2022-7-4 08:36 | 只看该作者
740071911 发表于 2022-7-3 21:09
Letter-shell加进去有什么好处

可以通过命令行调用,运行程序中的函数

使用特权

评论回复
8
740071911| | 2022-7-4 08:47 | 只看该作者
xld0932 发表于 2022-7-4 08:36
可以通过命令行调用,运行程序中的函数

在实际项目中,比如呢

使用特权

评论回复
9
xld0932|  楼主 | 2022-7-4 09:30 | 只看该作者
740071911 发表于 2022-7-4 08:47
在实际项目中,比如呢

看你具体应用,就是开放调试、调用接口

使用特权

评论回复
10
fentianyou| | 2022-7-9 12:20 | 只看该作者
这个检测性能怎么样  

使用特权

评论回复
11
xld0932|  楼主 | 2022-7-9 16:21 | 只看该作者
fentianyou 发表于 2022-7-9 12:20
这个检测性能怎么样

使用很方便,功能很强大

使用特权

评论回复
12
alvpeg| | 2022-8-19 21:06 | 只看该作者
MultiButton是什么?   

使用特权

评论回复
13
xld0932|  楼主 | 2022-8-22 09:09 | 只看该作者
alvpeg 发表于 2022-8-19 21:06
MultiButton是什么?

一个开源的软件,实现按键事件处理的框架

使用特权

评论回复
14
wilhelmina2| | 2022-9-5 17:24 | 只看该作者
RT-Thread 软件包里也有一个开源的按键库 MultiButton

使用特权

评论回复
15
E=MC2U| | 2022-9-5 19:03 | 只看该作者
看spec,IO 带有硬件 key debounce 功能,硬件自带过滤噪声硬件,唯一不好的是没有硬件支持key board。

使用特权

评论回复
16
Bowclad| | 2022-9-5 21:36 | 只看该作者
确实没有硬件支持key board很难受

使用特权

评论回复
17
vivilyly| | 2022-9-7 20:42 | 只看该作者
实现多个按钮提交的方法

使用特权

评论回复
18
modesty3jonah| | 2022-9-7 21:08 | 只看该作者
MultiButton | 一个小巧简单易用的事件驱动型按键驱动

使用特权

评论回复
19
phoenixwhite| | 2022-9-8 12:19 | 只看该作者
可以简化你的程序结构  

使用特权

评论回复
20
belindagraham| | 2022-9-8 13:31 | 只看该作者
同一个函数做不同的处理?

使用特权

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

本版积分规则

认证:上海灵动微电子股份有限公司资深现场应用工程师
简介:诚信·承诺·创新·合作

67

主题

2993

帖子

29

粉丝