【嵌入式】使用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口:

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

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

struct Button knob_1;
struct Button knob_2;
struct Button knob_3;
struct Button knob_4;

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

u8 knobRead(u8 button_id)
{switch(button_id){case 0:return GPIO_ReadInputDataBit(KNOB_PORT,KNOB_1_PIN);case 1:return GPIO_ReadInputDataBit(KNOB_PORT,KNOB_2_PIN);case 2:return GPIO_ReadInputDataBit(KNOB_PORT,KNOB_3_PIN);case 3:return GPIO_ReadInputDataBit(KNOB_PORT,KNOB_4_PIN);default:return 0;}
}

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

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

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

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

button_attach(&knob_1, SINGLE_CLICK,     knobCallback_1);
button_attach(&knob_1, LONG_PRESS_START, knobCallback_1);
button_attach(&knob_2, SINGLE_CLICK,     knobCallback_2);
button_attach(&knob_2, LONG_PRESS_START, knobCallback_2);
button_attach(&knob_3, SINGLE_CLICK,     knobCallback_3);
button_attach(&knob_3, LONG_PRESS_START, knobCallback_3);
button_attach(&knob_4, SINGLE_CLICK,     knobCallback_4);
button_attach(&knob_4, LONG_PRESS_START, knobCallback_4);

        【6】启动按键:

button_start(&knob_1);
button_start(&knob_2);
button_start(&knob_3);
button_start(&knob_4);

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

void KNOB_Reg(void)
{button_init(&knob_1, knobRead, 0, 0);button_init(&knob_2, knobRead, 0, 1);button_init(&knob_3, knobRead, 0, 2);button_init(&knob_4, knobRead, 0, 3);button_attach(&knob_1, SINGLE_CLICK,     knobCallback_1);button_attach(&knob_1, LONG_PRESS_START, knobCallback_1);button_attach(&knob_2, SINGLE_CLICK,     knobCallback_2);button_attach(&knob_2, LONG_PRESS_START, knobCallback_2);button_attach(&knob_3, SINGLE_CLICK,     knobCallback_3);button_attach(&knob_3, LONG_PRESS_START, knobCallback_3);button_attach(&knob_4, SINGLE_CLICK,     knobCallback_4);button_attach(&knob_4, LONG_PRESS_START, knobCallback_4);button_start(&knob_1);button_start(&knob_2);button_start(&knob_3);button_start(&knob_4);
}

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

//Timer14 5ms定时器
#define TIMER14_ARR  (500-1)
#define TIMER14_PSC  (960-1)void Timer14_Config(void)
{TIM_TimeBaseInitTypeDef TIM_StructInit;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM14, ENABLE);//使能定时器时钟//定时器基础配置TIM_StructInit.TIM_Period = TIMER14_ARR;            //自动重装值TIM_StructInit.TIM_Prescaler = TIMER14_PSC;         //预分频系数TIM_StructInit.TIM_ClockDivision = TIM_CKD_DIV1;    //时钟分频TIM_StructInit.TIM_CounterMode = TIM_CounterMode_Up;//向上计数TIM_StructInit.TIM_RepetitionCounter = 0;           //不重复计数TIM_TimeBaseInit(TIM14, &TIM_StructInit);//NVIC中断配置NVIC_InitStructure.NVIC_IRQChannel = TIM14_IRQn;NVIC_InitStructure.NVIC_IRQChannelPriority = 3;     //数字越小优先级越高NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);TIM_ClearFlag(TIM14, TIM_FLAG_Update);TIM_ITConfig(TIM14, TIM_IT_Update, ENABLE);          //使能更新中断TIM_Cmd(TIM14, ENABLE);
}extern void button_ticks(void);
void TIM14_IRQHandler(void)
{if(TIM_GetITStatus(TIM14, TIM_IT_Update) != RESET){button_ticks();     //旋钮驱动心跳TIM_ClearITPendingBit(TIM14, TIM_IT_Update);}
}

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

void main(void)
{//定时器初始化Timer14_Config();//旋钮初始化/注册KNOB_Init();KNOB_Reg();while(1){//...}
}

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

五 设计实现--界面处理

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

typedef enum tagMenuTree    //菜单树
{MENU_MAIN = 0,MEUN_LOG
}MENU_TREE;typedef enum tagEventCode   //事件值
{NULL_EVENT = 0,KNOB_1_SHORT = 1,KNOB_1_LONG  = 2,KNOB_2_SHORT = 3,KNOB_2_LONG = 4,KNOB_3_SHORT = 5,KNOB_3_LONG = 6,KNOB_4_SHORT = 7,KNOB_4_LONG = 8
}EVENT_CODE;typedef struct tagMenuInfo  //界面信息
{u8 cur_page;    //正在执行的界面u8 knb_evnt;    //当前触发的事件
}MENU_INFO;
extern MENU_INFO menu;extern void Menu_Init(MENU_INFO *handle, u8 p_page, u8 p_evnt);
extern void Set_Menu(MENU_INFO *handle, u8 p_page);
extern u8 Get_Menu(MENU_INFO *handle);
extern void Set_Event_Code(MENU_INFO *handle, u8 p_evnt);
extern int Get_Event_Code(MENU_INFO *handle);
extern void Menu_Handler(MENU_INFO *handle);

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

/**************************************************************************
* 函数名称: Menu_Init
* 功能描述: 菜单初始化
**************************************************************************/
void Menu_Init(MENU_INFO *handle, u8 p_page, u8 p_evnt)
{memset(handle, 0, sizeof(MENU_INFO));handle->cur_page = p_page;handle->knb_evnt = p_evnt;
}/**************************************************************************
* 函数名称: Set_Menu/Get_Menu
* 功能描述: 菜单跳转/获取当前菜单
**************************************************************************/
void Set_Menu(MENU_INFO *handle, u8 p_page)
{handle->cur_page = p_page;
}u8 Get_Menu(MENU_INFO *handle)
{return handle->cur_page;
}/**************************************************************************
* 函数名称: Set_Event_Code/Get_Event_Code
* 功能描述: 设置当前事件值/获取当前事件值
**************************************************************************/
void Set_Event_Code(MENU_INFO *handle, u8 p_evnt)
{handle->knb_evnt = p_evnt;
}int Get_Event_Code(MENU_INFO *handle)
{return handle->knb_evnt;
}

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

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

void knobCallback_1(void *p_btn)
{u8 btn_event_val; btn_event_val = get_button_event((struct Button *)p_btn); switch(btn_event_val){case SINGLE_CLICK:Set_Event_Code(&menu, KNOB_1_SHORT);break ;case LONG_PRESS_START:Set_Event_Code(&menu, KNOB_1_LONG);break ;default:break ;}
}

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

void Menu_Handler(MENU_INFO *handle)
{switch(handle->cur_page){case MENU_MAIN:menuMainHandle(handle->knb_evnt);break ;case MEUN_LOG:menuLogHandle(handle->knb_evnt);break ;default:break ;}Set_Event_Code(handle, NULL_EVENT);     //及时将事件清除,防止重复触发
}

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

void menuMainHandle(u8 p_evnt)
{cleanAll();  //清屏//主界面显示switch(p_evnt){case KNOB_1_SHORT:break ;case KNOB_1_LONG:Set_Menu(&menu, MEUN_LOG);  //进入登录界面break ;default:break;}
}
void menuLogHandle(u8 p_evnt)
{cleanAll();  //清屏//登录界面的显示switch(p_evnt){case KNOB_2_SHORT:break ;case KNOB_2_LONG:Set_Menu(&menu, MENU_MAIN);  //返回主界面break ;default:break;}
}

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

void main(void)
{//定时器初始化Timer14_Config();//旋钮初始化/注册KNOB_Init();KNOB_Reg();//界面初始化Menu_Init(&menu, MENU_MAIN, NULL_EVENT);while(1){Menu_Handler(&menu);  //界面处理函数LCD_Update();  //用缓存刷新屏幕//...}
}

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/146623.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

ahk系列——ahk_v2实现win10任意界面ocr

前言: 不依赖外部api接口,界面简洁,翻译快速,操作简单, 有网络就能用 、还可以把ocr结果非中文翻译成中文、同样可以识别中英日韩等60多个国家语言并翻译成中文,十分的nice 1、所需环境 windows10及其以上…

软件设计师_数据库系统_学习笔记

文章目录 3.1 数据库模式3.1.1 三级模式 两级映射3.1.2 数据库设计过程 3.2 ER模型3.3 关系代数与元组演算3.4 规范化理论3.5 并发控制3.6 数据库完整性约束3.7 分布式数据库3.8 数据仓库与数据挖掘 3.1 数据库模式 3.1.1 三级模式 两级映射 内模式直接与物理数据库相关联的 定…

如何初始化一个vue项目

如何初始化一个vue项目 安装 vue-cli 后 ,终端执行 vue ui npm install vue-cli --save-devCLI 服务 | Vue CLI (vuejs.org) 等一段时间后 。。。 进入项目仪表盘 设置其他模块 项目构建后目录 vue.config.js 文件相关配置 官方vue.config.js 参考文档 https://cli.vuejs.o…

嵌入式Linux应用开发-基础知识-第十八章系统对中断的处理②

嵌入式Linux应用开发-基础知识-第十八章系统对中断的处理② 第十八章 Linux系统对中断的处理 ②18.3 Linux中断系统中的重要数据结构18.3.1 irq_desc数组18.3.2 irqaction结构体18.3.3 irq_data结构体18.3.4 irq_domain结构体18.3.5 irq_chip结构体 18.4 在设备树中指定中断_在…

区块链(8):p2p去中心化之websoket服务端实现业务逻辑

1 业务逻辑 例如 peer1和peer2之间相互通信 peer1通过onopen{ write(Mesage(QUERY_LATEST))} 向peer2发送消息“我要最新的区块”。 peer2通过onMessage收到消息,通过handleMessage方法对消息进行处理。 handleMessage根据消息类型进行处理 RESPONSE_BLOCKCHAIN:返回区块链…

基于Java的游戏检索系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言用户功能已注册用户的功能后台功能管理员功能具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博…

BI神器Power Query(25)-- 使用PQ实现表格多列转换(1/3)

实例需求:原始表格包含多列属性数据,现在需要将不同属性分列展示在不同的行中,att1、att3、att5为一组,att2、att3、att6为另一组,数据如下所示。 更新表格数据 原始数据表: Col1Col2Att1Att2Att3Att4Att5Att6AAADD…

嵌入式Linux应用开发-基础知识-第十八章系统对中断的处理①

嵌入式Linux应用开发-基础知识-第十八章系统对中断的处理 第十八章 Linux 系统对中断的处理①18.1 进程、线程、中断的核心:栈18.1.1 ARM 处理器程序运行的过程18.1.2 程序被中断时,怎么保存现场18.1.3 进程、线程的概念 18.2 Linux系统对中断处理的演进…

【教学类-36-10】20230908方脸爷爷和圆脸奶奶(midjounery-niji)(中班:《我爱我家》数:连线、涂色)

背景需求: 领导们鼓动我去参加上海市高级职称评审(科研成果比较多),为下一轮保教主任评高级“探探路”。虽然自我感觉道行浅薄,无缘高级,但领导给机会,自然要参与一下,努力了解整个…

【Python】返回指定时间对应的时间戳

使用模块datetime,附赠一个没啥用的“时间推算”功能(获取n天后对应的时间 代码: import datetimedef GetTimestamp(year,month,day,hour,minute,second,*,relativeNone,timezoneNone):#返回指定时间戳。指定relative时进行时间推算"""根…

架构师习题--嵌入式习题

架构师习题--嵌入式习题 可靠度:是单个系统的可靠性 避错和容错 N版本程序设计是静态 恢复块是动态 恢复块是主机坏了调用备用机,每次只有单机运行 N版本是N机器同时运行 恢复块是主机坏了调用备用机,后向恢复到之前的状态 N版主直接向前走

Purism 推出注重隐私的 Linux 平板电脑

导读一款昂贵的 Linux 平板电脑,注重安全和隐私。让我们拭目以待。 Purism 是一家日益流行的计算机硬件产品制造商,专门提供配备注重隐私的开源 Linux 发行版的笔记本电脑、台式机和移动设备。 最近,他们发布了一款新产品 Librem 11 平板电…

SmartX 边缘计算解决方案:简单稳定,支持各类应用负载

在《一文了解近端边缘 IT 基础架构技术需求》文章中,我们为大家分析了边缘应用对 IT 基础架构的技术要求,以及为什么超融合架构是支持边缘场景的最佳选择。值得一提的是,IDC 近日发布的《中国软件定义存储(SDS)及超融合…

Centos7配置firewalld防火墙规则

这里写自定义目录标题 欢迎使用Markdown编辑器一、简单介绍二、特点和功能2.1、区域(Zone)2.2、运行时和永久配置2.3、服务和端口2.4、动态更新2.5、连接跟踪2.6、D-Bus接口 三、设置规则3.1、启动防火墙服务3.2、新建防火墙规则的服务,添加端…

objective-c 基础学习

目录 第一节:OC 介绍 ​​第二节:Fundation 框架 ​第三节:NSLog 相对于print 的增强 ​第四节:NSString ​第五节:oc新增数据类型 第六节: 类和对象 ​类的方法的声明与实现 ​第七节:类…

多叉树+图实现简单业务流程

文章目录 场景整体架构流程业务界面技术细节小结 场景 这次遇到一个需求,大致就是任务组织成方案,方案组织成预案,预案可裁剪调整.预案关联事件等级配置,告警触发预案产生事件.然后任务执行是有先后的,也就是有流程概念. 整体架构流程 方案管理、预案管理构成任务流程的基础条…

28 drf-Vue个人向总结-1

文章目录 前后端分离开发展示项目项补充知识开发问题浏览器解决跨域问题 drf 小tips设置资源root目录使用自定义的user表设置资源路径media数据库补充删除表中数据单页面与多页面模式过滤多层自关联后端提交的数据到底是什么jwt token登录设置普通的 token 原理使用流程解析 jw…

Day_17> 动态内存管理

目录 1.为什么存在动态内存分配? 2.动态内存函数的介绍 malloc calloc realloc 3.常见的动态内存错误 1.对NULL指针的解引用操作 2.对动态开辟空间的越界访问 3.对非动态开辟内存使用free释放 4.使用free释放一块动态开辟内存的一部分 5.对同一块动态内…

QCefView 简介

什么是QCefView QCefView 是为 Qt 开发的一个封装集成了CEF(Chromium Embedded Framework)库的Wdiget UI组件。使用QCefView可以充分发挥CEF丰富强大的Web能力,快速开发混合架构的应用程序。它不需要开发者理解CEF的细节,能够在Qt中更容易的使用CEF&…

深入了解 Linux 中的 AWK 命令:文本处理的瑞士军刀

简介 在Linux和Unix操作系统中,文本处理是一个常见的任务。AWK命令是一个强大的文本处理工具,专门进行文本截取和分析,它允许你在文本文件中查找、过滤、处理和格式化数据。本文将深入介绍Linux中的AWK命令,让你了解其基本用法和…