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

目录

[一 背景说明](#一 背景说明)

[二 参考资料](#二 参考资料)

[三 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口:

cpp 复制代码
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】由于这边用到了四个按键,申请四个按键结构:

cpp 复制代码
struct Button knob_1;
struct Button knob_2;
struct Button knob_3;
struct Button knob_4;

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

cpp 复制代码
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 ,使用上面的按键结构以及回调函数初始化按键对象:

cpp 复制代码
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 先空着,后面需要结合界面切换来实现:

cpp 复制代码
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】启动按键:

cpp 复制代码
button_start(&knob_1);
button_start(&knob_2);
button_start(&knob_3);
button_start(&knob_4);

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

cpp 复制代码
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间隔定时器来循环调用这个心跳函数,定时器相关函数如下:

cpp 复制代码
//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】在主函数的初始化中加上上面几个接口:

cpp 复制代码
void main(void)
{
    //定时器初始化
    Timer14_Config();
    //旋钮初始化/注册
    KNOB_Init();
    KNOB_Reg();

    while(1)
    {
        //...
    }
}

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

五 设计实现--界面处理

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

cpp 复制代码
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】新建源文件,新增界面相关的接口函数等:

cpp 复制代码
/**************************************************************************
* 函数名称: 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,其他按钮的回调一样实现):

cpp 复制代码
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 的实现:

cpp 复制代码
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 就是每个界面的具体实现了:

cpp 复制代码
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;
    }
}
cpp 复制代码
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】在主函数的初始化中加上上面界面初始化接口,同时界面处理函数置于主循环中执行:

cpp 复制代码
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 中设置)。

相关推荐
Hello_Embed3 分钟前
libmodbus 移植 STM32(基础篇)
笔记·stm32·单片机·学习·modbus
qq_397562311 小时前
QT工程 , 生成别的电脑运行的exe程序
嵌入式硬件·qt
qqssss121dfd2 小时前
STM32H750XBH6的ETH模块移植LWIP
网络·stm32·嵌入式硬件
wAIxiSeu3 小时前
Github开源项目推荐
开源·github
开源能源管理系统4 小时前
MyEMS开源能源管理系统赋能化纤织造产业绿色转型
开源·能源·能源管理系统·零碳工厂
想放学的刺客4 小时前
单片机嵌入式试题(第27期)设计可移植、可配置的外设驱动框架的关键要点
c语言·stm32·单片机·嵌入式硬件·物联网
zhangfeng11334 小时前
ModelScope(魔搭社区)介绍与模型微调全指南 中国版Hugging Face GPU租借平台 一站式开源模型社区与服务平台
人工智能·开源
天昊吖4 小时前
stc8H启用DMA发送后 卡住【踩坑日志】
单片机
李永奉4 小时前
杰理芯片SDK开发-ENC双麦降噪配置/调试教程
人工智能·单片机·嵌入式硬件·物联网·语音识别
BackCatK Chen4 小时前
第 1 篇:软件视角扫盲|TMC2240 软件核心特性 + 学习路径(附工具清单)
c语言·stm32·单片机·学习·电机驱动·保姆级教程·tmc2240