【CW32F030CxTx StartKit测评】03.基于MultiButton的按键检测
本帖最后由 xld0932 于 2022-7-2 19:46 编辑概述CW32F030CxTx StartKit开发板板载了三个按键,一个是用于复位芯片的RESET按键,另外两个是可用户自定义的编程按键,通过原理图知道KEY1/KEY2分别与PA1/PA2相连接,本文将结合开源的MultiButton软件库现实现这两个按键的多态功能;为了能够实时监测/查看按键状态,我们还配置了USART串口实现了printf的功能,当然为了后面的调试方便,基于USART还移植了Letter-shell_3.x软件库,为后面调试、或者是发送shell命令调用软件代码功能先做好准备,当前本文先讲述一下移植,具体应用到后面使用的时候再详细描述。
MultiButtonMultiButton是一个小巧装简单易用的事件驱动型按键驱动模块,使用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.xLetter-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;
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_InitTypeDefGPIO_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;
}
运行结果
工程源码
在上面程序中给按键的事件添加回调函数时,每一个事件都对应了一个函数,这样就导致需要实现的函数太多了……是不是可以通过一个函数来解决呢,后来发现我们可以通过event这个属性来进行判断,将所有的event回调函数放到一个函数里来……然后根据button->event的属性值来区别处理……实现如下:void KEY_Handler(void *btn)
{
Button *key = btn;
char *KEY_NAME= {"NONE", "KEY1", "KEY2"};
switch(key->event)
{
case PRESS_DOWN:
printf("\r\n%s : PRESS_DOWN", KEY_NAME);
break;
case PRESS_UP:
printf("\r\n%s : PRESS_UP", KEY_NAME);
break;
case PRESS_REPEAT:
printf("\r\n%s : PRESS_REPEAT", KEY_NAME);
break;
case SINGLE_CLICK:
printf("\r\n%s : SINGLE_CLICK", KEY_NAME);
break;
case DOUBLE_CLICK:
printf("\r\n%s : DOUBLE_CLICK", KEY_NAME);
break;
case LONG_PRESS_START:
printf("\r\n%s : LONG_PRESS_START", KEY_NAME);
break;
case LONG_PRESS_HOLD:
printf("\r\n%s : LONG_PRESS_HOLD",KEY_NAME);
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= {"NONE", "KEY1", "KEY2"};
switch(key->event)
{
case PRESS_DOWN:
printf("\r\n%s : PRESS_DOWN", KEY_NAME);
break;
case PRESS_UP:
printf("\r\n%s : PRESS_UP", KEY_NAME);
break;
case SINGLE_CLICK:
printf("\r\n%s : SINGLE_CLICK", KEY_NAME);
break;
case DOUBLE_CLICK:
printf("\r\n%s : DOUBLE_CLICK", KEY_NAME);
break;
case LONG_PRESS_START:
printf("\r\n%s : LONG_PRESS_START", KEY_NAME);
break;
case LONG_PRESS_HOLD:
printf("\r\n%s : LONG_PRESS_HOLD",KEY_NAME);
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);
……
这个时候,所有的处理就全都正常了……为了少写一些函数,竟然发现了这个注意点!跟大家分享一下……
下载看看,谢谢分享。 感谢大佬分享好资料,内容详尽,太辛苦了
Letter-shell加进去有什么好处 哈哈,letter-shell好用。 740071911 发表于 2022-7-3 21:09
Letter-shell加进去有什么好处
可以通过命令行调用,运行程序中的函数 xld0932 发表于 2022-7-4 08:36
可以通过命令行调用,运行程序中的函数
在实际项目中,比如呢 740071911 发表于 2022-7-4 08:47
在实际项目中,比如呢
看你具体应用,就是开放调试、调用接口 这个检测性能怎么样 fentianyou 发表于 2022-7-9 12:20
这个检测性能怎么样
使用很方便,功能很强大{:biggrin:} MultiButton是什么? alvpeg 发表于 2022-8-19 21:06
MultiButton是什么?
一个开源的软件,实现按键事件处理的框架 RT-Thread 软件包里也有一个开源的按键库 MultiButton 看spec,IO 带有硬件 key debounce 功能,硬件自带过滤噪声硬件,唯一不好的是没有硬件支持key board。 确实没有硬件支持key board很难受 实现多个按钮提交的方法 MultiButton | 一个小巧简单易用的事件驱动型按键驱动 可以简化你的程序结构 同一个函数做不同的处理?
页:
[1]
2