当前位置: 首页 > 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;最常用的文档格式之一…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...

Vue3 PC端 UI组件库我更推荐Naive UI

一、Vue3生态现状与UI库选择的重要性 随着Vue3的稳定发布和Composition API的广泛采用&#xff0c;前端开发者面临着UI组件库的重新选择。一个好的UI库不仅能提升开发效率&#xff0c;还能确保项目的长期可维护性。本文将对比三大主流Vue3 UI库&#xff08;Naive UI、Element …...

链式法则中 复合函数的推导路径 多变量“信息传递路径”

非常好&#xff0c;我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题&#xff0c;统一使用 二重复合函数&#xff1a; z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y))​ 来全面说明。我们会展示其全微分形式&#xff08;偏导…...

CppCon 2015 学习:Reactive Stream Processing in Industrial IoT using DDS and Rx

“Reactive Stream Processing in Industrial IoT using DDS and Rx” 是指在工业物联网&#xff08;IIoT&#xff09;场景中&#xff0c;结合 DDS&#xff08;Data Distribution Service&#xff09; 和 Rx&#xff08;Reactive Extensions&#xff09; 技术&#xff0c;实现 …...

Docker环境下安装 Elasticsearch + IK 分词器 + Pinyin插件 + Kibana(适配7.10.1)

做RAG自己打算使用esmilvus自己开发一个&#xff0c;安装时好像网上没有比较新的安装方法&#xff0c;然后找了个旧的方法对应试试&#xff1a; &#x1f680; 本文将手把手教你在 Docker 环境中部署 Elasticsearch 7.10.1 IK分词器 拼音插件 Kibana&#xff0c;适配中文搜索…...

第2课 SiC MOSFET与 Si IGBT 静态特性对比

2.1 输出特性对比 2.2 转移特性对比 2.1 输出特性对比 器件的输出特性描述了当温度和栅源电压(栅射电压)为某一具体数值时,漏极电流(集电极电流...