[CW32F030系列] 【CW32F030CxTx StartKit测评】03.基于MultiButton的按键检测

[复制链接]
 楼主| xld0932 发表于 2022-7-2 19:43 | 显示全部楼层 |阅读模式
本帖最后由 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实现代码
  1. MultiTimer KEY_MultiTimer;

  2. struct Button KEY1;
  3. struct Button KEY2;

  4. uint8_t key1_pin_level(void)
  5. {
  6.     return GPIO_ReadPin(CW_GPIOA, GPIO_PIN_1);
  7. }

  8. uint8_t key2_pin_level(void)
  9. {
  10.     return GPIO_ReadPin(CW_GPIOA, GPIO_PIN_2);
  11. }

  12. void KEY_PressDownHandler(void *btn)
  13. {
  14.     Button *key = btn;

  15.     switch(key->button_id)
  16.     {
  17.         case 1 : printf("\r\nKEY1 : Press Down"); break;
  18.         case 2 : printf("\r\nKEY2 : Press Down"); break;
  19.         default: break;
  20.     }
  21. }

  22. void KEY_PressUpHandler(void *btn)
  23. {
  24.     Button *key = btn;

  25.     switch(key->button_id)
  26.     {
  27.         case 1 : printf("\r\nKEY1 : Press Up"); break;
  28.         case 2 : printf("\r\nKEY2 : Press Up"); break;
  29.         default: break;
  30.     }
  31. }

  32. void KEY_PressRepeatHandler(void *btn)
  33. {
  34.     Button *key = btn;

  35.     switch(key->button_id)
  36.     {
  37.         case 1 : printf("\r\nKEY1 : Press Repeat"); break;
  38.         case 2 : printf("\r\nKEY2 : Press Repeat"); break;
  39.         default: break;
  40.     }
  41. }

  42. void KEY_SingleClickHandler(void *btn)
  43. {
  44.     Button *key = btn;

  45.     switch(key->button_id)
  46.     {
  47.         case 1 : printf("\r\nKEY1 : Single Click"); break;
  48.         case 2 : printf("\r\nKEY2 : Single Click"); break;
  49.         default: break;
  50.     }
  51. }

  52. void KEY_DoubleClickHandler(void *btn)
  53. {
  54.     Button *key = btn;

  55.     switch(key->button_id)
  56.     {
  57.         case 1 : printf("\r\nKEY1 : Double Click"); break;
  58.         case 2 : printf("\r\nKEY2 : Double Click"); break;
  59.         default: break;
  60.     }
  61. }

  62. void KEY_LongPressStartHandler(void *btn)
  63. {
  64.     Button *key = btn;

  65.     switch(key->button_id)
  66.     {
  67.         case 1 : printf("\r\nKEY1 : Long Press Start"); break;
  68.         case 2 : printf("\r\nKEY2 : Long Press Start"); break;
  69.         default: break;
  70.     }
  71. }

  72. void KEY_LongPressHoldHandler(void *btn)
  73. {
  74.     Button *key = btn;

  75.     switch(key->button_id)
  76.     {
  77.         case 1 : printf("\r\nKEY1 : Long Press Hold"); break;
  78.         case 2 : printf("\r\nKEY2 : Long Press Hold"); break;
  79.         default: break;
  80.     }
  81. }

  82. void KEY_Init(void)
  83. {
  84.     GPIO_InitTypeDef GPIO_InitStruct;

  85.     __RCC_GPIOA_CLK_ENABLE();

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

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

  93.     button_attach(&KEY1, PRESS_DOWN,        KEY_PressDownHandler);
  94.     button_attach(&KEY1, PRESS_UP,          KEY_PressUpHandler);
  95.     button_attach(&KEY1, PRESS_REPEAT,      KEY_PressRepeatHandler);
  96.     button_attach(&KEY1, SINGLE_CLICK,      KEY_SingleClickHandler);
  97.     button_attach(&KEY1, DOUBLE_CLICK,      KEY_DoubleClickHandler);
  98.     button_attach(&KEY1, LONG_PRESS_START,  KEY_LongPressStartHandler);
  99.     button_attach(&KEY1, LONG_PRESS_HOLD,   KEY_LongPressHoldHandler);

  100.     button_attach(&KEY2, SINGLE_CLICK,      KEY_SingleClickHandler);

  101.     button_start(&KEY1);
  102.     button_start(&KEY2);

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

  105. void KEY_MultiTimerCallback(MultiTimer *timer, void *userData)
  106. {
  107.     button_ticks();

  108.     MultiTimerStart(&KEY_MultiTimer, 5, KEY_MultiTimerCallback, "KEY Scan");
  109. }
复制代码


运行结果
1.png


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,费话不多说,直接上代码:
  1. Shell shell;

  2. char shellBuffer[512];

  3. signed short shellPortRead(char *data, unsigned short len)
  4. {
  5.     return len;
  6. }

  7. signed short shellPortWrite(char *data, unsigned short len)
  8. {
  9.     for(unsigned short i = 0; i < len; i++)
  10.     {
  11.         USART_SendData_8bit(CW_UART1, *data++);
  12.         while(USART_GetFlagStatus(CW_UART1, USART_FLAG_TXE) == RESET);
  13.     }

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

  15.     return len;
  16. }

  17. void shellPortInit(void)
  18. {
  19.     GPIO_InitTypeDef  GPIO_InitStructure;
  20.     USART_InitTypeDef USART_InitStructure;

  21.     RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_GPIOA,   ENABLE);
  22.     RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_UART1, ENABLE);

  23.     PA08_AFx_UART1TXD();
  24.     PA09_AFx_UART1RXD();

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

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

  32.     USART_InitStructure.USART_BaudRate = 115200;
  33.     USART_InitStructure.USART_Over     = USART_Over_16;
  34.     USART_InitStructure.USART_Source   = USART_Source_PCLK;
  35.     USART_InitStructure.USART_UclkFreq = 64000000;
  36.     USART_InitStructure.USART_StartBit = USART_StartBit_FE;
  37.     USART_InitStructure.USART_StopBits = USART_StopBits_1;
  38.     USART_InitStructure.USART_Parity   = USART_Parity_No ;
  39.     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  40.     USART_InitStructure.USART_Mode                 = USART_Mode_Rx | USART_Mode_Tx;
  41.     USART_Init(CW_UART1, &USART_InitStructure);         

  42.     NVIC_SetPriority(UART1_IRQn, 0);
  43.     NVIC_EnableIRQ(UART1_IRQn);

  44.     USART_ITConfig(CW_UART1, USART_IT_RC, ENABLE);

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

  49. void shellPortHandler(void)
  50. {
  51.     if(USART_GetITStatus(CW_UART1, USART_IT_RC) != RESET)
  52.     {
  53.         shellHandler(&shell, USART_ReceiveData_8bit(CW_UART1));
  54.         USART_ClearITPendingBit(CW_UART1, USART_IT_RC);
  55.     }
  56. }

  57. int fputc(int ch, FILE *f)
  58. {
  59.     USART_SendData_8bit(CW_UART1, (uint8_t)ch);
  60.     while(USART_GetFlagStatus(CW_UART1, USART_FLAG_TXE)  == RESET);

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

  62.     return ch;
  63. }
复制代码


运行结果
2.png


工程源码
Template.zip (4.87 MB, 下载次数: 35)


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

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

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

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

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

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

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

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

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

  28.         default:
  29.             break;
  30.     }
  31. }

  32. ……
  33.     button_attach(&KEY1, PRESS_DOWN,        KEY_Handler);
  34.     button_attach(&KEY1, PRESS_UP,          KEY_Handler);
  35.     button_attach(&KEY1, PRESS_REPEAT,      KEY_Handler);
  36.     button_attach(&KEY1, SINGLE_CLICK,      KEY_Handler);
  37.     button_attach(&KEY1, DOUBLE_CLICK,      KEY_Handler);
  38.     button_attach(&KEY1, LONG_PRESS_START,  KEY_Handler);
  39.     button_attach(&KEY1, LONG_PRESS_HOLD,   KEY_Handler);

  40.     button_attach(&KEY2, SINGLE_CLICK,      KEY_Handler);
  41. ……
复制代码

但是在实际调试的过程中我们又遇到问题,发现上面这种写法,PRESS_REPEAT的回调函数没有执行……通过分析发现问题出现在MultiButton的如下的语句部分:
  1. ……
  2.         case 2:
  3.             if(handle->button_level == handle->active_level)    //press down again
  4.             {
  5.                 handle->event = (uint8_t)PRESS_DOWN;
  6.                 EVENT_CB(PRESS_DOWN);
  7.                 handle->repeat++;
  8.                 EVENT_CB(PRESS_REPEAT);                         // repeat hit
  9.                 handle->ticks = 0;
  10.                 handle->state = 3;
  11.             }
  12.             else if(handle->ticks > SHORT_TICKS)                //released timeout
  13.             {
  14.                 if(handle->repeat == 1)
  15.                 {
  16.                     handle->event = (uint8_t)SINGLE_CLICK;
  17.                     EVENT_CB(SINGLE_CLICK);
  18.                 }
  19.                 else if(handle->repeat == 2)
  20.                 {
  21.                     handle->event = (uint8_t)DOUBLE_CLICK;
  22.                     EVENT_CB(DOUBLE_CLICK);                     // repeat hit
  23.                 }

  24.                 handle->state = 0;
  25.             }
  26.             break;
  27. ……
复制代码

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

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

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

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

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

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

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

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

  25.         default:
  26.             break;
  27.     }
  28. }

  29. void KEY_RepeatHandler(void *btn)
  30. {
  31.     Button *key = btn;

  32.     switch(key->button_id)
  33.     {
  34.         case 1 : printf("\r\nKEY1 : PRESS_REPEAT"); break;
  35.         case 2 : printf("\r\nKEY2 : PRESS_REPEAT"); break;
  36.         default: break;
  37.     }
  38. }
  39. ……
  40.     button_attach(&KEY1, PRESS_DOWN,        KEY_Handler);
  41.     button_attach(&KEY1, PRESS_UP,          KEY_Handler);
  42.     button_attach(&KEY1, PRESS_REPEAT,      KEY_RepeatHandler);
  43.     button_attach(&KEY1, SINGLE_CLICK,      KEY_Handler);
  44.     button_attach(&KEY1, DOUBLE_CLICK,      KEY_Handler);
  45.     button_attach(&KEY1, LONG_PRESS_START,  KEY_Handler);
  46.     button_attach(&KEY1, LONG_PRESS_HOLD,   KEY_Handler);

  47.     button_attach(&KEY2, SINGLE_CLICK,      KEY_Handler);
  48. ……
复制代码

这个时候,所有的处理就全都正常了……为了少写一些函数,竟然发现了这个注意点!跟大家分享一下……
chenjun89 发表于 2022-7-3 10:04 来自手机 | 显示全部楼层
下载看看,谢谢分享。
www5911839 发表于 2022-7-3 11:43 | 显示全部楼层
感谢大佬分享好资料,内容详尽,太辛苦了

740071911 发表于 2022-7-3 21:09 | 显示全部楼层
Letter-shell加进去有什么好处
weifeng90 发表于 2022-7-4 07:49 来自手机 | 显示全部楼层
哈哈,letter-shell好用。
 楼主| xld0932 发表于 2022-7-4 08:36 | 显示全部楼层
740071911 发表于 2022-7-3 21:09
Letter-shell加进去有什么好处

可以通过命令行调用,运行程序中的函数
740071911 发表于 2022-7-4 08:47 | 显示全部楼层
xld0932 发表于 2022-7-4 08:36
可以通过命令行调用,运行程序中的函数

在实际项目中,比如呢
 楼主| xld0932 发表于 2022-7-4 09:30 | 显示全部楼层
740071911 发表于 2022-7-4 08:47
在实际项目中,比如呢

看你具体应用,就是开放调试、调用接口
fentianyou 发表于 2022-7-9 12:20 | 显示全部楼层
这个检测性能怎么样  
 楼主| xld0932 发表于 2022-7-9 16:21 | 显示全部楼层
fentianyou 发表于 2022-7-9 12:20
这个检测性能怎么样

使用很方便,功能很强大
alvpeg 发表于 2022-8-19 21:06 | 显示全部楼层
MultiButton是什么?   
 楼主| xld0932 发表于 2022-8-22 09:09 | 显示全部楼层
alvpeg 发表于 2022-8-19 21:06
MultiButton是什么?

一个开源的软件,实现按键事件处理的框架
wilhelmina2 发表于 2022-9-5 17:24 | 显示全部楼层
RT-Thread 软件包里也有一个开源的按键库 MultiButton
E=MC2U 发表于 2022-9-5 19:03 | 显示全部楼层
看spec,IO 带有硬件 key debounce 功能,硬件自带过滤噪声硬件,唯一不好的是没有硬件支持key board。
Bowclad 发表于 2022-9-5 21:36 | 显示全部楼层
确实没有硬件支持key board很难受
vivilyly 发表于 2022-9-7 20:42 | 显示全部楼层
实现多个按钮提交的方法
modesty3jonah 发表于 2022-9-7 21:08 | 显示全部楼层
MultiButton | 一个小巧简单易用的事件驱动型按键驱动
phoenixwhite 发表于 2022-9-8 12:19 | 显示全部楼层
可以简化你的程序结构  
belindagraham 发表于 2022-9-8 13:31 | 显示全部楼层
同一个函数做不同的处理?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:King.Xu

77

主题

3023

帖子

38

粉丝
快速回复 在线客服 返回列表 返回顶部