当前位置: 首页 > news >正文

【嵌入式】使用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>标签&#xff0c;但像<import>、<context:componentScan>等非<bean>标签怎样去使用注解去替代呢&#xff1f;定义一个配置类替代原有的xml配置文件&#xff0c;<bean>标签以外的标签&#xff…...

Linux系统编程系列之进程间通信-信号量组

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

centos 6使用yum安装软件

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

maven无法下载时的解决方法——笔记

右键项目然后点击创建setting.xml&#xff08;因为现在创建了&#xff0c;所以没显示了&#xff0c;可以直接点击打开setting.xml&#xff09; 然后添加 <mirror><id>nexus-aliyun</id><mirrorOf>*,!jeecg,!jeecg-snapshots</mirrorOf><name…...

Java Spring Boot 开发框架

Spring Boot是一种基于Java编程语言的开发框架&#xff0c;它的目标是简化Java应用程序的开发过程。Spring Boot提供了一种快速、易于使用的方式来创建独立的、生产级别的Java应用程序。本文将介绍Spring Boot的特性、优势以及如何使用它来开发高效、可靠的应用程序。 一、简介…...

Pytorch学习记录-1-张量

1. 张量 (Tensor): 数学中指的是多维数组&#xff1b; torch.Tensor data: 被封装的 Tensor dtype: 张量的数据类型 shape: 张量的形状 device: 张量所在的设备&#xff0c;GPU/CPU requires_grad: 指示是否需要计算梯度 grad: data 的梯度 grad_fn: 创建 Tensor 的 Functio…...

paddle2.3-基于联邦学习实现FedAVg算法-CNN

目录 1. 联邦学习介绍 2. 实验流程 3. 数据加载 4. 模型构建 5. 数据采样函数 6. 模型训练 1. 联邦学习介绍 联邦学习是一种分布式机器学习方法&#xff0c;中心节点为server&#xff08;服务器&#xff09;&#xff0c;各分支节点为本地的client&#xff08;设备&#…...

nuiapp保存canvas绘图

要保存一个 Canvas 绘图&#xff0c;可以使用以下步骤&#xff1a; 获取 Canvas 元素和其绘图上下文&#xff1a; var canvas document.getElementById("myCanvas"); var ctx canvas.getContext("2d");使用 Canvas 绘图 API 绘制图形。 使用 toDataUR…...

Object.defineProperty()方法详解,了解vue2的数据代理

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

Linux 磁盘管理

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

大数据与人工智能的未来已来

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

【AI视野·今日Robot 机器人论文速览 第四十一期】Tue, 26 Sep 2023

AI视野今日CS.Robotics 机器人学论文速览 Tue, 26 Sep 2023 Totally 73 papers &#x1f449;上期速览✈更多精彩请移步主页 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 决定利用假期外出旅行&#xff0c;他们将想去的城市从 $1 $ 到 n n n 编号&#xff0c;且编号较小的城市在编号较大的城市的西边&#xff0c;已知各个城市的海拔高度互不相同&#xff0c;记城市 …...

数据库设计流程---以案例熟悉

案例名字&#xff1a;宠物商店系统 课程来源&#xff1a;点击跳转 信息->概念模型->数据模型->数据库结构模型 将现实世界中的信息转换为信息世界的概念模型&#xff08;E-R模型&#xff09; 业务逻辑 构建 E-R 图 确定三个实体&#xff1a;用户、商品、订单...

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 文件 &#xff08;相当于开放防火墙&#xff09; # 类型 数据库 …...

提取PDF数据:Documents for PDF ( GcPdf )

在当今数据驱动的世界中&#xff0c;从 PDF 文档中无缝提取结构化表格数据已成为开发人员的一项关键任务。借助GrapeCity Documents for PDF ( GcPdf )&#xff0c;您可以使用 C# 以编程方式轻松解锁这些 PDF 中隐藏的信息宝藏。 考虑一下 PDF&#xff08;最常用的文档格式之一…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

Qemu arm操作系统开发环境

使用qemu虚拟arm硬件比较合适。 步骤如下&#xff1a; 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载&#xff0c;下载地址&#xff1a;https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...

DBLP数据库是什么?

DBLP&#xff08;Digital Bibliography & Library Project&#xff09;Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高&#xff0c;数据库文献更新速度很快&#xff0c;很好地反映了国际计算机科学学术研…...

五子棋测试用例

一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏&#xff0c;有着深厚的文化底蕴。通过将五子棋制作成网页游戏&#xff0c;可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家&#xff0c;都可以通过网页五子棋感受到东方棋类…...