【嵌入式】使用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 中设置)。
相关文章:

【嵌入式】使用MultiButton开源库驱动按键并控制多级界面切换
目录 一 背景说明 二 参考资料 三 MultiButton开源库移植 四 设计实现--驱动按键 五 设计实现--界面处理 一 背景说明 需要做一个通过不同按键控制多级界面切换以及界面动作的程序。 查阅相关资料,发现网上大多数的应用都比较繁琐,且对于多级界面的…...

【数据结构】树的概念理解和性质推导(保姆级详解,小白必看系列)
目录 一、前言 🍎 为什么要学习非线性结构 ---- 树(Tree) 💦 线性结构的优缺点 💦 优化方案 ----- 树(Tree) 💦 树的讲解流程 二、树的概念及结构 🍐 树的概念 &…...

融合之力:数字孪生、人工智能和数据分析的创新驱动
数字孪生、人工智能(AI)和数据分析是当今科技领域中的三个重要概念,它们之间存在着紧密的关联和互动,共同推动了许多领域的创新和发展。 一、概念 数字孪生是一种数字化的模拟技术,它通过复制现实世界中的物理实体、…...
Spring的注解开发-Spring配置类的开发
Bean配置类的注解开发 Component等注解替代了<bean>标签,但像<import>、<context:componentScan>等非<bean>标签怎样去使用注解去替代呢?定义一个配置类替代原有的xml配置文件,<bean>标签以外的标签ÿ…...

Linux系统编程系列之进程间通信-信号量组
一、什么是信号量组 信号量组是信号量的一种, 是system-V三种IPC对象之一,是进程间通信的一种方式。 二、信号量组的特性 信号量组不是用来传输数据的,而是作为“旗语”,用来协调各进程或者线程工作的。信号量组可以一次性在其内…...

centos 6使用yum安装软件
1. 执行以下命令,查看当前操作系统 CentOS 版本。 cat /etc/centos-release返回结果如下图所示,则说明当前操作系统版本为 CentOS 6.9。 2. 执行以下命令,编辑 CentOS-Base.repo 和CentOS-Epel.repo文件。 vim /etc/yum.repos.d/CentOS-Bas…...

maven无法下载时的解决方法——笔记
右键项目然后点击创建setting.xml(因为现在创建了,所以没显示了,可以直接点击打开setting.xml) 然后添加 <mirror><id>nexus-aliyun</id><mirrorOf>*,!jeecg,!jeecg-snapshots</mirrorOf><name…...
Java Spring Boot 开发框架
Spring Boot是一种基于Java编程语言的开发框架,它的目标是简化Java应用程序的开发过程。Spring Boot提供了一种快速、易于使用的方式来创建独立的、生产级别的Java应用程序。本文将介绍Spring Boot的特性、优势以及如何使用它来开发高效、可靠的应用程序。 一、简介…...
Pytorch学习记录-1-张量
1. 张量 (Tensor): 数学中指的是多维数组; torch.Tensor data: 被封装的 Tensor dtype: 张量的数据类型 shape: 张量的形状 device: 张量所在的设备,GPU/CPU requires_grad: 指示是否需要计算梯度 grad: data 的梯度 grad_fn: 创建 Tensor 的 Functio…...

paddle2.3-基于联邦学习实现FedAVg算法-CNN
目录 1. 联邦学习介绍 2. 实验流程 3. 数据加载 4. 模型构建 5. 数据采样函数 6. 模型训练 1. 联邦学习介绍 联邦学习是一种分布式机器学习方法,中心节点为server(服务器),各分支节点为本地的client(设备&#…...
nuiapp保存canvas绘图
要保存一个 Canvas 绘图,可以使用以下步骤: 获取 Canvas 元素和其绘图上下文: var canvas document.getElementById("myCanvas"); var ctx canvas.getContext("2d");使用 Canvas 绘图 API 绘制图形。 使用 toDataUR…...

Object.defineProperty()方法详解,了解vue2的数据代理
假期第一篇,对于基础的知识点,我感觉自己还是很薄弱的。 趁着假期,再去复习一遍 Object.defineProperty(),对于这个方法,更多的还是停留在面试的时候,面试官问你vue2和vue3区别的时候,不免要提一提这个方法…...

Linux 磁盘管理
Linux 系统的磁盘管理直接关系到整个系统的性能表现。磁盘管理常用三个命令为: df、du 和 fdisk。 df df(英文全称:disk free)。df 命令用于显示磁盘空间的使用情况,包括文件系统的挂载点、总容量、已用空间、可用空间…...

大数据与人工智能的未来已来
大数据与人工智能的定义 大数据: 大数据指的是规模庞大、复杂性高、多样性丰富的数据集合。这些数据通常无法通过传统的数据库管理工具来捕获、存储、管理和处理。大数据的特点包括"3V": 大量(Volume):大数…...

【AI视野·今日Robot 机器人论文速览 第四十一期】Tue, 26 Sep 2023
AI视野今日CS.Robotics 机器人学论文速览 Tue, 26 Sep 2023 Totally 73 papers 👉上期速览✈更多精彩请移步主页 Daily Robotics Papers Extreme Parkour with Legged Robots Authors Xuxin Cheng, Kexin Shi, Ananye Agarwal, Deepak Pathak人类可以通过以高度动态…...

[NOIP2012 提高组] 开车旅行
[NOIP2012 提高组] 开车旅行 题目描述 小 A \text{A} A 和小 B \text{B} B 决定利用假期外出旅行,他们将想去的城市从 $1 $ 到 n n n 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 …...

数据库设计流程---以案例熟悉
案例名字:宠物商店系统 课程来源:点击跳转 信息->概念模型->数据模型->数据库结构模型 将现实世界中的信息转换为信息世界的概念模型(E-R模型) 业务逻辑 构建 E-R 图 确定三个实体:用户、商品、订单...
Miniconda创建paddlepaddle环境
1、conda env list 2、conda create --name paddle_env python3.8 --channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ 3、activate paddle_env 4、python -m pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple 5、pip install "p…...

postgresql实现单主单从
实现步骤 1.主库创建一个有复制权限的用户 CREATE ROLE 用户名login # 有登录权限的角色即是用户replication #复制权限 encrypted password 密码;2.主库配置开放从库外部访问权限 修改 pg_hba.conf 文件 (相当于开放防火墙) # 类型 数据库 …...

提取PDF数据:Documents for PDF ( GcPdf )
在当今数据驱动的世界中,从 PDF 文档中无缝提取结构化表格数据已成为开发人员的一项关键任务。借助GrapeCity Documents for PDF ( GcPdf ),您可以使用 C# 以编程方式轻松解锁这些 PDF 中隐藏的信息宝藏。 考虑一下 PDF(最常用的文档格式之一…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...