本帖最后由 luobeihai 于 2024-12-23 01:12 编辑
#申请原创# @21小跑堂
1. MultiButton简单介绍
MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。
MultiButton 作者的 github 仓库地址:https://github.com/0x1abin/MultiButton
支持的按键事件有:
2. 在APM32F407中移植MultiButton
MultiButton 使用起来非常简单,用户只需要提供一个读取按键电平的函数即可。可以在裸机中使用,也可以在 RTOS 中使用。大致的使用步骤:
定义一个按键对象 初始化按键按键对象,绑定按键的读取电平函数,提供按键的触发条件(是低电平触发还是高电平触发) 注册按键事件回调函数(如果不使用回调编程方法,可以不用注册) 启动按键 间隔 5-20ms 扫描按键处理函数,可以使用定时器中断每隔 5-20ms 扫描一次,或者使用 RTOS 创建间隔 5-20ms 的按键扫描任务。
移植前,先准备一份可以运行的工程源码。这个我们直接使用极海官方的SDK的RT-Thread例程就行,或者其他例程都行,自行把控。只要例程可以保证初始化了自己板子上的按键 GPIO 口,以及可以打印输出串口(需要用到串口打印输出调试信息)即可。
详细过程不多介绍,下面只添加 MultiButton 的应用层代码。
2.1 添加按键电平读取函数
按键电平读取需要先初始化IO口等操作,根据自己的板子进行。
- // GPIO初始化
- void key_gpio_config(void)
- {
- GPIO_Config_T gpioConfigStruct;
-
- /* Enable GPIO clock */
- RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB);
- /* KEY1/2/3 : PB0/1/2 */
- GPIO_ConfigStructInit(&gpioConfigStruct);
- gpioConfigStruct.pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2;
- gpioConfigStruct.mode = GPIO_MODE_IN;
- gpioConfigStruct.pupd = GPIO_PUPD_UP;
- GPIO_Config(GPIOB, &gpioConfigStruct);
- }
- // 电平读取函数
- uint8_t read_key_gpio(uint8_t key_id)
- {
- uint8_t temp = 1;
-
- switch(key_id)
- {
- case 0:
- temp = GPIO_ReadInputBit(GPIOB, GPIO_PIN_0);
- break;
- case 1:
- temp = GPIO_ReadInputBit(GPIOB, GPIO_PIN_1);
- break;
- case 2:
- temp = GPIO_ReadInputBit(GPIOB, GPIO_PIN_2);
- break;
- default:
- temp = 1;
- break;
- }
-
- return temp;
- }
2.2 定义按键结构体,初始化按键
一个按键需要定义一个结构体实例。
- /* 定义两个按键结构体待使用 */
- static struct Button key1;
- static struct Button key2;
- static struct Button key3;
- /* 第三个参数0,代表按键是低电平触发 */
- button_init(&key1, read_key_gpio, 0, KEY1_ID);
- button_init(&key2, read_key_gpio, 0, KEY2_ID);
- button_init(&key3, read_key_gpio, 0, KEY3_ID);
2.3 注册按键事件回调函数
下面以key1按键为例,编写按键的回调函数。如果有多个按键的话,可以通过 get_button_event 函数获取到底是哪个按键按下的,然后再进行对应处理即可。
- void key1_press_down_callback(void *button)
- {
- printf("************* key1 press down *************\n");
- }
- void key1_long_press_start_callback(void *button)
- {
- printf("************* key1 long press start *************\n");
- }
- /* 如果有多种事件回调函数需要注册的话,可以所有事件集中在一个回调函数中判断,只写一个事件回调函数即可 */
- void key1_callback(void *button)
- {
- uint32_t btn_event_val;
-
- btn_event_val = get_button_event((struct Button *)button);
-
- switch(btn_event_val)
- {
- case PRESS_DOWN:
- printf("************* key1 press down! *************\n");
- break;
-
- case PRESS_UP:
- printf("************* key1 press up! *************\n");
- break;
-
- case PRESS_REPEAT:
- printf("************* key1 press repeat! *************\n");
- break;
-
- case SINGLE_CLICK:
- printf("************* key1 single click! *************\n");
- break;
-
- case DOUBLE_CLICK:
- printf("************* key1 double click! *************\n");
- break;
-
- case LONG_PRESS_START:
- printf("************* key1 long press start! *************\n");
- break;
-
- case LONG_PRESS_HOLD:
- printf("************* key1 long press hold! *************\n");
- break;
- }
- }
- button_attach(&key1, PRESS_DOWN, key1_press_down_callback);
- button_attach(&key1, LONG_PRESS_START, key1_long_press_start_callback);
注册了回调函数之后,当检测到按键触发了相应事件时,就会调用用户注册的事件回调函数。
2.4 启动按键
- button_start(&key1);
- button_start(&key2);
- button_start(&key2);
2.5 间隔5-20ms循环调用button_ticks()函数
如果是裸机的话,那么可以在定时器中断中间隔调用 button_ticks() 函数,又或者在 main 函数的 while(1) 主循环中,延时 5-20ms 再调用,代码如下:
- while(1)
- {
- button_ticks();
- delay_ms(20);
- }
对于按键扫描,我使用了 FreeRTOS 创建了一个按键扫描任务,在这个任务中不断调用 button_ticks() 函数。代码如下:
- void button_scan_task(void *argument)
- {
- /* USER CODE BEGIN StartDefaultTask */
- /* Infinite loop */
- for(;;)
- {
- button_ticks();
- osDelay(20);
- }
- /* USER CODE END StartDefaultTask */
- }
- /* 创建按键扫描任务 */
- xTaskCreate((TaskFunction_t )button_scan_task, //任务函数
- (const char* )"button_scan_task", //任务名称
- (uint16_t )128, //任务堆栈大小
- (void* )NULL, //传递给任务函数的参数
- (UBaseType_t )1, //任务优先级
- (TaskHandle_t* )NULL); //任务句柄
思考:这里我创建了一个按键扫描任务,是每隔 20ms 就扫描一次,我在想不断的进行按键扫描,总感觉有点浪费 CPU 资源。所以,是否可以使用中断的方式,等到按键按下触发了中断之后,由按键中断释放信号量唤醒这个按键扫描任务才进行按键扫描。这样就不用每隔 20ms 就扫描一次了,等按键按下触发了中断之后才来扫描。
有了这个想法,我马上就写代码进行验证,遗憾的是,并没有出现我想象的结果:按键按下就会运行相应的按键事件回调函数。实验过程中发现,按键中断确实是唤醒了按键扫描任务的,但并没有调用按键事件回调函数。我猜问题应该是运行 button_ticks() 这个函数的次数太少了,因为按键中断按下一次就触发一次中断,也就是按下一次只是运行一次 button_ticks() 这个函数,所以可能没有达到事件触发的条件吧?
3. 测试验证
当按键按下时,就会调用我们注册的事件回调函数,我注册了两个事件回调函数:按下事件、长按事件。在里面都只是打印输出,打开串口可以看到下面打印信息:
4. 不使用事件回调方法使用MultiButton
我们在使用 MultiButton 的时候,也可以不使用事件回调的方式。我们可以在 while(1) 死循环里面查询按键事件。
比如,我可以创建一个按键事件的处理任务,在这个任务里面,我只是不断去检测是否有按键事件发生。
- /* 按键事件处理任务函数 */
- void StartDefaultTask(void *argument)
- {
- static uint8_t btn1_event_val = NONE_PRESS;
-
- /* USER CODE BEGIN StartDefaultTask */
- /* Infinite loop */
- for(;;)
- {
- if (btn1_event_val != get_button_event(&key0))
- {
- btn1_event_val = get_button_event(&key0);
- if (btn1_event_val == PRESS_DOWN)
- {
- printf("********** 按键按下 **********\n");
- }
- else if (btn1_event_val == PRESS_UP)
- {
- printf("********** 按键弹起 **********\n");
- }
- else if (btn1_event_val == LONG_PRESS_START)
- {
- printf("********** 按键长按 **********\n");
- }
- }
- osDelay(1);
- }
- /* USER CODE END StartDefaultTask */
- }
编译后,测试结果如下:
下面附件是MultiButton的工程源码,以供大家参考。
APM32F4xx_SDK_V1.4_MultiButton.zip
(1.64 MB, 下载次数: 5)
|