• 【嵌入式】使用MultiButton开源库驱动按键并控制多级界面切换


    目录

    一 背景说明

    二 参考资料

    三 MultiButton开源库移植

    四 设计实现--驱动按键

    五 设计实现--界面处理


    一 背景说明

            需要做一个通过不同按键控制多级界面切换以及界面动作的程序。

            查阅相关资料,发现网上大多数的应用都比较繁琐,且对于多级界面的切换逻辑可读性较差。所幸找到一篇使用开源库 MultiButton 来驱动按键,并控制多级界面切换的博文。按图索骥实现了预期的需求。

             开源库 MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,作者 0x1abin。这个项目非常精简,只有两个文件,可无限量扩展按键,按键事件的回调异步处理方式可以简化程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。

            MultiButton 支持如下的按钮事件:

            MultiButton的状态机如下:

    参考资料

            【1】MultiButton开源库:mirrors / 0x1abin / MultiButton · GitCode

            【2】MultiButton博文:MultiButton | 一个小巧简单易用的事件驱动型按键驱动模块-CSDN博客

            【3】MultiTimer开源库:mirrors / 0x1abin / MultiTimer · GitCode

            【4】MultiTimer博文:【嵌入式开源库】MultiTimer 的使用,一款可无限扩展的软件定时器_multi_timer-CSDN博客

            【5】MultiButton+MultiTimer+菜单操作博文:开源按键组件MultiButton支持菜单操作(事件驱动型)-阿里云开发者社区

            【注】:我下面的实现没有用到MultiTimer,仅单列出来备查。

    三 MultiButton开源库移植

            从上面的开源库或者github下载该开源库,主要用到就两个文件 multi_button.c/multi_button.h 。将这两个文件直接添加到自己的工程中,并关联头文件。

            到这边编译应该没有问题。

    四 设计实现--驱动按键

            【1】首先初始化自己用到的几个按键GPIO口:

    1. void KNOB_Init(void)
    2. {
    3. GPIO_InitTypeDef GPIO_InitStructure;
    4. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
    5. GPIO_InitStructure.GPIO_Pin = KNOB_1_PIN | KNOB_2_PIN | KNOB_3_PIN | KNOB_4_PIN;
    6. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
    7. GPIO_Init(KNOB_PORT, &GPIO_InitStructure);
    8. }

            【2】由于这边用到了四个按键,申请四个按键结构:

    1. struct Button knob_1;
    2. struct Button knob_2;
    3. struct Button knob_3;
    4. struct Button knob_4;

            【3】编写回调函数,绑定按键的GPIO电平读取接口:

    1. u8 knobRead(u8 button_id)
    2. {
    3. switch(button_id)
    4. {
    5. case 0:
    6. return GPIO_ReadInputDataBit(KNOB_PORT,KNOB_1_PIN);
    7. case 1:
    8. return GPIO_ReadInputDataBit(KNOB_PORT,KNOB_2_PIN);
    9. case 2:
    10. return GPIO_ReadInputDataBit(KNOB_PORT,KNOB_3_PIN);
    11. case 3:
    12. return GPIO_ReadInputDataBit(KNOB_PORT,KNOB_4_PIN);
    13. default:
    14. return 0;
    15. }
    16. }

            【4】关联 MultiButton ,使用上面的按键结构以及回调函数初始化按键对象:

    1. button_init(&knob_1, knobRead, 0, 0);
    2. button_init(&knob_2, knobRead, 0, 1);
    3. button_init(&knob_3, knobRead, 0, 2);
    4. button_init(&knob_4, knobRead, 0, 3);

            【5】注册按键事件(根据实际需要注册按键事件,不必一次性全注册,我这边只用到点按和长按,所以只注册了 SINGLE_CLICK 和 LONG_PRESS_START 两个事件)。

                    其中的回调函数 knobCallback_1/2/3/4 先空着,后面需要结合界面切换来实现:

    1. button_attach(&knob_1, SINGLE_CLICK, knobCallback_1);
    2. button_attach(&knob_1, LONG_PRESS_START, knobCallback_1);
    3. button_attach(&knob_2, SINGLE_CLICK, knobCallback_2);
    4. button_attach(&knob_2, LONG_PRESS_START, knobCallback_2);
    5. button_attach(&knob_3, SINGLE_CLICK, knobCallback_3);
    6. button_attach(&knob_3, LONG_PRESS_START, knobCallback_3);
    7. button_attach(&knob_4, SINGLE_CLICK, knobCallback_4);
    8. button_attach(&knob_4, LONG_PRESS_START, knobCallback_4);

            【6】启动按键:

    1. button_start(&knob_1);
    2. button_start(&knob_2);
    3. button_start(&knob_3);
    4. button_start(&knob_4);

            【7】将上面【4】【5】【6】的三个步骤整个成一个按键注册接口:

    1. void KNOB_Reg(void)
    2. {
    3. button_init(&knob_1, knobRead, 0, 0);
    4. button_init(&knob_2, knobRead, 0, 1);
    5. button_init(&knob_3, knobRead, 0, 2);
    6. button_init(&knob_4, knobRead, 0, 3);
    7. button_attach(&knob_1, SINGLE_CLICK, knobCallback_1);
    8. button_attach(&knob_1, LONG_PRESS_START, knobCallback_1);
    9. button_attach(&knob_2, SINGLE_CLICK, knobCallback_2);
    10. button_attach(&knob_2, LONG_PRESS_START, knobCallback_2);
    11. button_attach(&knob_3, SINGLE_CLICK, knobCallback_3);
    12. button_attach(&knob_3, LONG_PRESS_START, knobCallback_3);
    13. button_attach(&knob_4, SINGLE_CLICK, knobCallback_4);
    14. button_attach(&knob_4, LONG_PRESS_START, knobCallback_4);
    15. button_start(&knob_1);
    16. button_start(&knob_2);
    17. button_start(&knob_3);
    18. button_start(&knob_4);
    19. }

            【8】至此,按键驱动还不能生效,还需要添加一个心跳,一般采用5ms间隔定时器来循环调用这个心跳函数,定时器相关函数如下:

    1. //Timer14 5ms定时器
    2. #define TIMER14_ARR (500-1)
    3. #define TIMER14_PSC (960-1)
    4. void Timer14_Config(void)
    5. {
    6. TIM_TimeBaseInitTypeDef TIM_StructInit;
    7. NVIC_InitTypeDef NVIC_InitStructure;
    8. RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM14, ENABLE);//使能定时器时钟
    9. //定时器基础配置
    10. TIM_StructInit.TIM_Period = TIMER14_ARR; //自动重装值
    11. TIM_StructInit.TIM_Prescaler = TIMER14_PSC; //预分频系数
    12. TIM_StructInit.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频
    13. TIM_StructInit.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
    14. TIM_StructInit.TIM_RepetitionCounter = 0; //不重复计数
    15. TIM_TimeBaseInit(TIM14, &TIM_StructInit);
    16. //NVIC中断配置
    17. NVIC_InitStructure.NVIC_IRQChannel = TIM14_IRQn;
    18. NVIC_InitStructure.NVIC_IRQChannelPriority = 3; //数字越小优先级越高
    19. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    20. NVIC_Init(&NVIC_InitStructure);
    21. TIM_ClearFlag(TIM14, TIM_FLAG_Update);
    22. TIM_ITConfig(TIM14, TIM_IT_Update, ENABLE); //使能更新中断
    23. TIM_Cmd(TIM14, ENABLE);
    24. }
    25. extern void button_ticks(void);
    26. void TIM14_IRQHandler(void)
    27. {
    28. if(TIM_GetITStatus(TIM14, TIM_IT_Update) != RESET)
    29. {
    30. button_ticks(); //旋钮驱动心跳
    31. TIM_ClearITPendingBit(TIM14, TIM_IT_Update);
    32. }
    33. }

            【9】在主函数的初始化中加上上面几个接口:

    1. void main(void)
    2. {
    3. //定时器初始化
    4. Timer14_Config();
    5. //旋钮初始化/注册
    6. KNOB_Init();
    7. KNOB_Reg();
    8. while(1)
    9. {
    10. //...
    11. }
    12. }

            至此,MultiButton 开源库移植完毕,并将所用的四个按钮关联到 MultiButton ,按键事件待扩展。

    五 设计实现--界面处理

            【1】新建头文件,新增界面相关的结构体定义等:

    1. typedef enum tagMenuTree //菜单树
    2. {
    3. MENU_MAIN = 0,
    4. MEUN_LOG
    5. }MENU_TREE;
    6. typedef enum tagEventCode //事件值
    7. {
    8. NULL_EVENT = 0,
    9. KNOB_1_SHORT = 1,
    10. KNOB_1_LONG = 2,
    11. KNOB_2_SHORT = 3,
    12. KNOB_2_LONG = 4,
    13. KNOB_3_SHORT = 5,
    14. KNOB_3_LONG = 6,
    15. KNOB_4_SHORT = 7,
    16. KNOB_4_LONG = 8
    17. }EVENT_CODE;
    18. typedef struct tagMenuInfo //界面信息
    19. {
    20. u8 cur_page; //正在执行的界面
    21. u8 knb_evnt; //当前触发的事件
    22. }MENU_INFO;
    23. extern MENU_INFO menu;
    24. extern void Menu_Init(MENU_INFO *handle, u8 p_page, u8 p_evnt);
    25. extern void Set_Menu(MENU_INFO *handle, u8 p_page);
    26. extern u8 Get_Menu(MENU_INFO *handle);
    27. extern void Set_Event_Code(MENU_INFO *handle, u8 p_evnt);
    28. extern int Get_Event_Code(MENU_INFO *handle);
    29. extern void Menu_Handler(MENU_INFO *handle);

            【2】新建源文件,新增界面相关的接口函数等:

    1. /**************************************************************************
    2. * 函数名称: Menu_Init
    3. * 功能描述: 菜单初始化
    4. **************************************************************************/
    5. void Menu_Init(MENU_INFO *handle, u8 p_page, u8 p_evnt)
    6. {
    7. memset(handle, 0, sizeof(MENU_INFO));
    8. handle->cur_page = p_page;
    9. handle->knb_evnt = p_evnt;
    10. }
    11. /**************************************************************************
    12. * 函数名称: Set_Menu/Get_Menu
    13. * 功能描述: 菜单跳转/获取当前菜单
    14. **************************************************************************/
    15. void Set_Menu(MENU_INFO *handle, u8 p_page)
    16. {
    17. handle->cur_page = p_page;
    18. }
    19. u8 Get_Menu(MENU_INFO *handle)
    20. {
    21. return handle->cur_page;
    22. }
    23. /**************************************************************************
    24. * 函数名称: Set_Event_Code/Get_Event_Code
    25. * 功能描述: 设置当前事件值/获取当前事件值
    26. **************************************************************************/
    27. void Set_Event_Code(MENU_INFO *handle, u8 p_evnt)
    28. {
    29. handle->knb_evnt = p_evnt;
    30. }
    31. int Get_Event_Code(MENU_INFO *handle)
    32. {
    33. return handle->knb_evnt;
    34. }

            【3】结合上述菜单处理函数,关联“设计实现--驱动按键”中的【5】,完善 knobCallback_1/2/3/4 的实现。

                    主要逻辑就是将按键的动作,通过回调,传递给菜单结构 menu (单列出knobCallback_1,其他按钮的回调一样实现):

    1. void knobCallback_1(void *p_btn)
    2. {
    3. u8 btn_event_val;
    4. btn_event_val = get_button_event((struct Button *)p_btn);
    5. switch(btn_event_val)
    6. {
    7. case SINGLE_CLICK:
    8. Set_Event_Code(&menu, KNOB_1_SHORT);
    9. break ;
    10. case LONG_PRESS_START:
    11. Set_Event_Code(&menu, KNOB_1_LONG);
    12. break ;
    13. default:
    14. break ;
    15. }
    16. }

            【4】菜单处理函数 Menu_Handler 的实现:

    1. void Menu_Handler(MENU_INFO *handle)
    2. {
    3. switch(handle->cur_page)
    4. {
    5. case MENU_MAIN:
    6. menuMainHandle(handle->knb_evnt);
    7. break ;
    8. case MEUN_LOG:
    9. menuLogHandle(handle->knb_evnt);
    10. break ;
    11. default:
    12. break ;
    13. }
    14. Set_Event_Code(handle, NULL_EVENT); //及时将事件清除,防止重复触发
    15. }

            其中,menuMainHandle/menuLogHandle 就是每个界面的具体实现了:

    1. void menuMainHandle(u8 p_evnt)
    2. {
    3. cleanAll(); //清屏
    4. //主界面显示
    5. switch(p_evnt)
    6. {
    7. case KNOB_1_SHORT:
    8. break ;
    9. case KNOB_1_LONG:
    10. Set_Menu(&menu, MEUN_LOG); //进入登录界面
    11. break ;
    12. default:
    13. break;
    14. }
    15. }
    1. void menuLogHandle(u8 p_evnt)
    2. {
    3. cleanAll(); //清屏
    4. //登录界面的显示
    5. switch(p_evnt)
    6. {
    7. case KNOB_2_SHORT:
    8. break ;
    9. case KNOB_2_LONG:
    10. Set_Menu(&menu, MENU_MAIN); //返回主界面
    11. break ;
    12. default:
    13. break;
    14. }
    15. }

            【5】在主函数的初始化中加上上面界面初始化接口,同时界面处理函数置于主循环中执行:

    1. void main(void)
    2. {
    3. //定时器初始化
    4. Timer14_Config();
    5. //旋钮初始化/注册
    6. KNOB_Init();
    7. KNOB_Reg();
    8. //界面初始化
    9. Menu_Init(&menu, MENU_MAIN, NULL_EVENT);
    10. while(1)
    11. {
    12. Menu_Handler(&menu); //界面处理函数
    13. LCD_Update(); //用缓存刷新屏幕
    14. //...
    15. }
    16. }

            至此,完成了通过 MultiButton 开源库驱动按键并控制多级界面切换的工作。

            上述DEMO中,上电默认进入主界面,可以通过长按 knob_1 进入登陆界面。在登陆界面中,通过长按 knob_2 返回主界面(长按的时间可以在 multi_button.h 中设置)

  • 相关阅读:
    基于JavaWeb+SSM+购物系统微信小程序的设计和实现
    这些并发容器的坑,你要谨记!
    漫画:程序员要不要去考证?
    基于SpringBoot+vue的文件管理系统
    用 Github Codespaces 免费搭建本地开发测试环境
    threadlocal详解
    微服务最强理论基础,堪称绝妙心法
    很多Oracle中的SQL语句在EF中写不出来
    绘制X-Bar-S和X-Bar-R图,监测过程,计算CPK过程能力指数
    如何分析软件测试中发现的Bug!
  • 原文地址:https://blog.csdn.net/sinat_33408502/article/details/133349515