123下一页
返回列表 发新帖我要提问本帖赏金: 30.00元(功能说明)

[APM32F4] 基于APM32F407移植使用MultiButton按键驱动模块

[复制链接]
 楼主| luobeihai 发表于 2024-12-23 01:07 | 显示全部楼层 |阅读模式
<
本帖最后由 luobeihai 于 2024-12-23 01:12 编辑

#申请原创# @21小跑堂

1. MultiButton简单介绍

MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。

MultiButton 作者的 github 仓库地址:https://github.com/0x1abin/MultiButton

支持的按键事件有:

image-20241223004221767.png

2. 在APM32F407中移植MultiButton

MultiButton 使用起来非常简单,用户只需要提供一个读取按键电平的函数即可。可以在裸机中使用,也可以在 RTOS 中使用。大致的使用步骤:

  • 定义一个按键对象
  • 初始化按键按键对象,绑定按键的读取电平函数,提供按键的触发条件(是低电平触发还是高电平触发)
  • 注册按键事件回调函数(如果不使用回调编程方法,可以不用注册)
  • 启动按键
  • 间隔 5-20ms 扫描按键处理函数,可以使用定时器中断每隔 5-20ms 扫描一次,或者使用 RTOS 创建间隔 5-20ms 的按键扫描任务。

移植前,先准备一份可以运行的工程源码。这个我们直接使用极海官方的SDK的RT-Thread例程就行,或者其他例程都行,自行把控。只要例程可以保证初始化了自己板子上的按键 GPIO 口,以及可以打印输出串口(需要用到串口打印输出调试信息)即可。

详细过程不多介绍,下面只添加 MultiButton 的应用层代码。

2.1 添加按键电平读取函数

按键电平读取需要先初始化IO口等操作,根据自己的板子进行。

  1. // GPIO初始化
  2. void key_gpio_config(void)
  3. {
  4.     GPIO_Config_T gpioConfigStruct;
  5.    
  6.     /* Enable GPIO clock */
  7.     RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB);

  8.     /* KEY1/2/3 : PB0/1/2 */
  9.     GPIO_ConfigStructInit(&gpioConfigStruct);
  10.     gpioConfigStruct.pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2;
  11.     gpioConfigStruct.mode = GPIO_MODE_IN;
  12.     gpioConfigStruct.pupd  = GPIO_PUPD_UP;
  13.     GPIO_Config(GPIOB, &gpioConfigStruct);
  14. }

  15. // 电平读取函数
  16. uint8_t read_key_gpio(uint8_t key_id)
  17. {
  18.     uint8_t temp = 1;
  19.    
  20.     switch(key_id)
  21.     {
  22.         case 0:
  23.             temp = GPIO_ReadInputBit(GPIOB, GPIO_PIN_0);
  24.             break;
  25.         case 1:
  26.             temp = GPIO_ReadInputBit(GPIOB, GPIO_PIN_1);
  27.             break;
  28.         case 2:
  29.             temp = GPIO_ReadInputBit(GPIOB, GPIO_PIN_2);
  30.             break;
  31.         default:
  32.             temp = 1;
  33.             break;
  34.     }
  35.    
  36.     return temp;
  37. }

2.2 定义按键结构体,初始化按键

一个按键需要定义一个结构体实例。

  1. /* 定义两个按键结构体待使用 */
  2. static struct Button key1;
  3. static struct Button key2;
  4. static struct Button key3;

  5. /* 第三个参数0,代表按键是低电平触发 */
  6. button_init(&key1, read_key_gpio, 0, KEY1_ID);
  7. button_init(&key2, read_key_gpio, 0, KEY2_ID);
  8. button_init(&key3, read_key_gpio, 0, KEY3_ID);

2.3 注册按键事件回调函数

下面以key1按键为例,编写按键的回调函数。如果有多个按键的话,可以通过 get_button_event 函数获取到底是哪个按键按下的,然后再进行对应处理即可。

  1. void key1_press_down_callback(void *button)
  2. {
  3.         printf("************* key1 press down *************\n");
  4. }

  5. void key1_long_press_start_callback(void *button)
  6. {
  7.         printf("************* key1 long press start *************\n");
  8. }

  9. /* 如果有多种事件回调函数需要注册的话,可以所有事件集中在一个回调函数中判断,只写一个事件回调函数即可 */
  10. void key1_callback(void *button)
  11. {
  12.     uint32_t btn_event_val;
  13.    
  14.     btn_event_val = get_button_event((struct Button *)button);
  15.    
  16.     switch(btn_event_val)
  17.     {
  18.             case PRESS_DOWN:
  19.                 printf("************* key1 press down! *************\n");
  20.                     break;
  21.         
  22.             case PRESS_UP:
  23.                 printf("************* key1 press up! *************\n");
  24.                     break;
  25.         
  26.             case PRESS_REPEAT:
  27.                 printf("************* key1 press repeat! *************\n");
  28.                     break;
  29.         
  30.             case SINGLE_CLICK:
  31.                 printf("************* key1 single click! *************\n");
  32.                     break;
  33.         
  34.             case DOUBLE_CLICK:
  35.                 printf("************* key1 double click! *************\n");
  36.                     break;
  37.         
  38.             case LONG_PRESS_START:
  39.                 printf("************* key1 long press start! *************\n");
  40.                            break;
  41.         
  42.             case LONG_PRESS_HOLD:
  43.                 printf("************* key1 long press hold! *************\n");
  44.                     break;
  45.         }
  46. }

  47. button_attach(&key1, PRESS_DOWN, key1_press_down_callback);
  48. button_attach(&key1, LONG_PRESS_START, key1_long_press_start_callback);

注册了回调函数之后,当检测到按键触发了相应事件时,就会调用用户注册的事件回调函数。

2.4 启动按键

  1. button_start(&key1);
  2. button_start(&key2);
  3. button_start(&key2);

2.5 间隔5-20ms循环调用button_ticks()函数

如果是裸机的话,那么可以在定时器中断中间隔调用 button_ticks() 函数,又或者在 main 函数的 while(1) 主循环中,延时 5-20ms 再调用,代码如下:

  1. while(1)
  2. {
  3.     button_ticks();
  4.     delay_ms(20);
  5. }

对于按键扫描,我使用了 FreeRTOS 创建了一个按键扫描任务,在这个任务中不断调用  button_ticks() 函数。代码如下:

  1. void button_scan_task(void *argument)
  2. {
  3.     /* USER CODE BEGIN StartDefaultTask */
  4.     /* Infinite loop */
  5.     for(;;)
  6.     {
  7.         button_ticks();
  8.         osDelay(20);
  9.     }
  10.     /* USER CODE END StartDefaultTask */
  11. }

  12. /* 创建按键扫描任务 */
  13. xTaskCreate((TaskFunction_t )button_scan_task,       //任务函数
  14.             (const char*    )"button_scan_task",     //任务名称
  15.             (uint16_t       )128,                                 //任务堆栈大小
  16.             (void*          )NULL,                              //传递给任务函数的参数
  17.             (UBaseType_t    )1,                                        //任务优先级
  18.             (TaskHandle_t*  )NULL);                              //任务句柄

思考:这里我创建了一个按键扫描任务,是每隔 20ms 就扫描一次,我在想不断的进行按键扫描,总感觉有点浪费 CPU 资源。所以,是否可以使用中断的方式,等到按键按下触发了中断之后,由按键中断释放信号量唤醒这个按键扫描任务才进行按键扫描。这样就不用每隔 20ms 就扫描一次了,等按键按下触发了中断之后才来扫描。

有了这个想法,我马上就写代码进行验证,遗憾的是,并没有出现我想象的结果:按键按下就会运行相应的按键事件回调函数。实验过程中发现,按键中断确实是唤醒了按键扫描任务的,但并没有调用按键事件回调函数。我猜问题应该是运行 button_ticks() 这个函数的次数太少了,因为按键中断按下一次就触发一次中断,也就是按下一次只是运行一次 button_ticks() 这个函数,所以可能没有达到事件触发的条件吧?

3. 测试验证

当按键按下时,就会调用我们注册的事件回调函数,我注册了两个事件回调函数:按下事件、长按事件。在里面都只是打印输出,打开串口可以看到下面打印信息:

image-20220829154000937.png

4. 不使用事件回调方法使用MultiButton

我们在使用 MultiButton 的时候,也可以不使用事件回调的方式。我们可以在 while(1) 死循环里面查询按键事件。

比如,我可以创建一个按键事件的处理任务,在这个任务里面,我只是不断去检测是否有按键事件发生。

  1. /* 按键事件处理任务函数 */
  2. void StartDefaultTask(void *argument)
  3. {
  4.         static uint8_t btn1_event_val = NONE_PRESS;
  5.         
  6.         /* USER CODE BEGIN StartDefaultTask */
  7.         /* Infinite loop */
  8.         for(;;)
  9.         {
  10.                 if (btn1_event_val != get_button_event(&key0))
  11.                 {
  12.                         btn1_event_val = get_button_event(&key0);
  13.                         if (btn1_event_val == PRESS_DOWN)
  14.                         {
  15.                                 printf("********** 按键按下 **********\n");
  16.                         }
  17.                         else if (btn1_event_val == PRESS_UP)
  18.                         {
  19.                                 printf("********** 按键弹起 **********\n");
  20.                         }
  21.                         else if (btn1_event_val == LONG_PRESS_START)
  22.                         {
  23.                                 printf("********** 按键长按 **********\n");
  24.                         }
  25.                 }
  26.                 osDelay(1);
  27.         }
  28.         /* USER CODE END StartDefaultTask */
  29. }

编译后,测试结果如下:

image-20220829155100937.png

下面附件是MultiButton的工程源码,以供大家参考。

APM32F4xx_SDK_V1.4_MultiButton.zip (1.64 MB, 下载次数: 5)



打赏榜单

21小跑堂 打赏了 30.00 元 2024-12-27
理由:恭喜通过原创审核!期待您更多的原创作品~~

评论

在APM32F407上移植MultiButton按键驱动,简化按键程序,实现复杂的按键判断,让代码的业务逻辑更加清晰。  发表于 2024-12-27 15:02
pixhw 发表于 2025-1-5 09:41 | 显示全部楼层
GitHub: https://github.com/0x1abin/MultiButton
GitCode: https://gitcode.net/mirrors/0x1abin/MultiButton
1988020566 发表于 2025-1-5 13:47 | 显示全部楼层
希望使用中断方式来检测按键              
beacherblack 发表于 2025-1-5 17:05 | 显示全部楼层
提高代码可维护性和结构清晰度的有效方法
qiufengsd 发表于 2025-1-5 18:23 | 显示全部楼层
电平阈值史多少?              
timfordlare 发表于 2025-1-5 18:48 | 显示全部楼层
按键在按下和松开时可能会产生抖动,导致多次错误的按键状态变化。
ccook11 发表于 2025-1-5 19:16 | 显示全部楼层
使用简单的延时来过滤抖动信号。              
mickit 发表于 2025-1-5 19:37 | 显示全部楼层
如何判断是长按还是短按呢              
modesty3jonah 发表于 2025-1-5 20:06 | 显示全部楼层
优化MultiButton库的扫描频率和中断处理逻辑。
 楼主| luobeihai 发表于 2025-1-5 22:06 | 显示全部楼层
1988020566 发表于 2025-1-5 13:47
希望使用中断方式来检测按键

我当时就马上尝试过中断的方式,但是好像不太行。可能是我的中断实现方式还有点问题吧
 楼主| luobeihai 发表于 2025-1-5 22:09 | 显示全部楼层
mickit 发表于 2025-1-5 19:37
如何判断是长按还是短按呢

长按短按这个好办啊,记录按下的时间区分就行。比如你设置按下超过3秒就是长按
[鑫森淼焱垚] 发表于 2025-1-6 10:24 | 显示全部楼层
好文,多谢分享
Annie556 发表于 2025-1-9 12:32 | 显示全部楼层
看起来这个操作还是不错的啊
Carmen7 发表于 2025-1-9 16:01 | 显示全部楼层
其实自己写这种按键函数也行,不过按照事件型操作的话,比较容易而且规范
Amonologue独白 发表于 2025-1-9 18:36 | 显示全部楼层
挺好,这种按键驱动算是开源的吗?
1988020566 发表于 2025-1-9 20:11 | 显示全部楼层
需要注意按键的去抖动处理?              
cashrwood 发表于 2025-1-9 20:55 | 显示全部楼层
MultiButton的回调函数是否正确实现
Estelle1999 发表于 2025-1-9 20:58 | 显示全部楼层
我想说,裸机用这种按键驱动模块比较好,还是用外部中断来做比较好?
sanfuzi 发表于 2025-1-9 21:19 | 显示全部楼层
APM32F407可以设置几个按键?
ulystronglll 发表于 2025-1-9 21:47 | 显示全部楼层
如何调用函数判断事件呢
              
您需要登录后才可以回帖 登录 | 注册

本版积分规则

23

主题

101

帖子

4

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