不错哦|多级菜单实现起来就是这么简单!

摘要

产品是面向用户的,用户需要的仅仅是功能以及有好的交互界面,多级菜单在其中扮演着重要的角色,有限的按键实现复杂的界面,层层相扣,标志法对于菜单的管理相当费劲,多了自己都会被自己绕晕...下面介绍一种还算不错的多级菜单是实现方式

这个框架本身不复杂,小伙伴们可以直接看源码,有什么问题,可以一起交流

硬件连接

STM32F407ZGT6

ST7789 LCD屏(SPI1,复用开发板NRF24L01接口)

3个按键 PC7、PC8、PC9

代码实现

实现起来比较简单,直接进入代码部分,我使用的是3个按键,功能分别是:上翻(增大数值)、下翻(减小数值)、确认

先来定义一个结构体,结构体中包含了5个成员

1、cur:当前的菜单索引号

2、left:左按键对应的索引号

3、OK:确认件对应的索引号

4、right:右键对应的索引号

5、当前索引对应要执行的菜单界面(或功能)

typedef struct
{
    uint8_t cur;                     //当前索引号
    uint8_t left;                    //left 的索引号
    uint8_t ok;                      //OK 的索引号
    uint8_t right;                   //right 的索引号
    void (*current_operation)(void); //当前执行的函数
} KEY_TABLE;

再定义一个关于按键键值,菜单索引号的结构体:

typedef struct
{
    uint16_t key_value;
    uint8_t key_new[3];
    uint8_t key_left;
    uint8_t key_right;
    uint8_t key_ok;
    uint8_t func_index;
    uint8_t func_index_last;
} Menu_Para;

为每一个子菜单分配上翻、下翻索引值

typedef struct
{
    uint8_t Down_index;
    uint8_t Up_index;

    uint8_t Sys_Down_index;
    uint8_t Sys_Up_index;

    uint8_t Com_Down_index;
    uint8_t Com_Up_index;

    uint8_t Game_Down_index;
    uint8_t Game_Up_index;

    uint8_t Sys_DisModeDown_index;
    uint8_t Sys_DisModeUp_index;

    uint8_t Sys_NetModeDown_index;
    uint8_t Sys_NetModeUp_index;

} MenuSerch_HandleTypeDef;

利用上面第一个提到的结构体,只需要把对应的索引号填入即可,按键按下根据索引号去查找需要跳转的界面

const KEY_TABLE k_table[] =
    {
        {0, 2, 3, 1, (*MainMenuTask)}, //0---开机主菜单

        {1, 2, 3, 1, (*MenuSearchDown)}, //1---1级菜单下翻
        {2, 2, 3, 1, (*MenuSearchUP)},   //2---1级菜单上翻
        {3, 0, 3, 3, (*MenuSelect)},     //3---1级菜单确认

        {4, 0, 4, 4, (*Psd_Auth_Process)}, //4---2级密码认证菜单

        {5, 7, 8, 6, (*SysMenu_Process)},   //5---2级系统设置菜单,未使用
        {6, 7, 8, 6, (*Sys_SearchDown)},    //6---2级系统设置下翻
        {7, 7, 8, 6, (*Sys_SearchUP)},      //7---2级系统设置上翻
        {8, 7, 8, 6, (*SubMenu_SysSelect)}, //8---2级系统设置菜单确认

        {9, 11, 12, 10, (*ComMenu_Process)},   //9---1级通讯菜单明细显示,未使用
        {10, 11, 12, 10, (*Com_SearchDown)},   //10---2级通讯设置下翻
        {11, 11, 12, 10, (*Com_SearchUP)},     //11---2级通讯设置上翻
        {12, 3, 12, 12, (*SubMenu_ComSelect)}, //12---1级通讯菜单选择确认

        {13, 15, 3, 14, (*GamesMenu_Process)},   //13---2级系统设置菜单,未使用
        {14, 15, 3, 14, (*Game_SearchDown)},    //14---2级系统设置下翻
        {15, 15, 3, 14, (*Game_SearchUP)},      //15---2级系统设置上翻
        {16, 3, 8, 6, (*SubMenu_GameSelect)}, //16---2级系统设置菜单确认

        {17, 18, 5, 17, (*Current_RangeDown)}, //17--3级系统设置电流范围设置--减小电流范围
        {18, 18, 5, 17, (*Current_RangeUP)},   //18---3级系统设置电流范围设置--增大电流范围

        {19, 20, 5, 19, (*Voltage_RangeDown)}, //19--3级系统设置电流范围设置--减小电流范围
        {20, 20, 5, 19, (*Voltage_RangeUP)},   //20---3级系统设置电流范围设置--增大电流范围

        {21, 22, 5, 21, (*DisMode_SearchDown)}, //21--3级系统设置灯光模式下翻
        {22, 22, 5, 21, (*DisMode_SearchUP)},   //22---3级系统设置灯光模式上翻

        {23, 24, 5, 23, (*Lightness_RangeDown)}, //23--3级系统设置灯光亮度设置--增大亮度
        {24, 24, 5, 23, (*Lightness_RangeUP)},   //24---3级系统设置灯光亮度设置--减小亮度

        {25, 26, 5, 25, (*Temperature_RangeDown)}, //25--3级系统设置温度设置--增大温度
        {26, 26, 5, 25, (*Temperature_RangeUP)},   //26---3级系统设置温度设置--减小温度

        {27, 28, 5, 27, (*NetMode_SerchDown)},   //27--3级系统设置网络模式选择
        {28, 28, 5, 27, (*NetMode_SerchUP)},     //28---3级系统设置网络模式选择
};

菜单要显示的内容,我们定义为指针数组形式

const uint8_t *MenuItem[MainItemsCount] = {
    "Psd Authen",
    "Sys Settings",
    "Com Settings",
    "User Games",
    "About Me",
    "About Version",
};

const uint8_t *Com_SubItem[Sys_SubMenuItems] = {
    "Slave Addr:",
    "BoundRate:",
    "reserved",
    "reserved",
    "reserved",
    "reserved",
};

const uint8_t *Game_SubItem[Game_SubMenuItems] = {
    "Greedy snake ",
    "Super Marie ",
    "Sokoban",
    "Double Dragon",
    "Metal Slug ",
    "Contra Force",
};

...

按键部分:

#define KEYPINS GPIOC->IDR

KEY_DATA key_LOR[3];
Menu_Para Menu_Parameter;

unsigned int key_read(void)
{
    unsigned char i;

    Menu_Parameter.key_value = KEYPINS;
    Menu_Parameter.key_value >>= 6;
    Menu_Parameter.key_value &= 0x000000E;
    for (i = 0; i < 3; i++)
    {
        if ((Menu_Parameter.key_value & (0x02 << i)) == 0) //有按键按下
        { 
            key_LOR[i].T_press++;

            if (key_LOR[i].T_press > 600)  //长按键
            {
       
            }
        }
        else
        {
            if (key_LOR[i].T_press > 100)
            {
                key_LOR[i].press = 1; //short press
                Menu_Parameter.key_new[i] = 1;
                key_to_lcd();
            }

            key_LOR[i].T_press = 0;
            key_LOR[i].L_press = 0;
            key_LOR[i].press = 0;
        }
    }
    return 1;
}

然后是查询按键按下对应的索引值处理:

void key_to_lcd(void)
{
    Menu_Parameter.key_left = Menu_Parameter.key_new[0];
    Menu_Parameter.key_ok = Menu_Parameter.key_new[1];
    Menu_Parameter.key_right = Menu_Parameter.key_new[2];

    memset(Menu_Parameter.key_new, 0, 3);

    if (Menu_Parameter.key_left == 1)
    {
        Menu_Parameter.func_index = k_table[Menu_Parameter.func_index].left;
    }
    if (Menu_Parameter.key_right == 1)
    {
        Menu_Parameter.func_index = k_table[Menu_Parameter.func_index].right;
    }
    if (Menu_Parameter.key_ok == 1)
    {
        Menu_Parameter.func_index = k_table[Menu_Parameter.func_index].ok;
    }
    if (Menu_Parameter.func_index_last == 1 ||
        Menu_Parameter.func_index_last == 2 ||
        Menu_Parameter.func_index_last == 6 ||
        Menu_Parameter.func_index_last == 7 ||
        Menu_Parameter.func_index_last == 10 ||
        Menu_Parameter.func_index_last == 11 ||
      Menu_Parameter.func_index_last == 14 ||
        Menu_Parameter.func_index_last == 15 ||
        Menu_Parameter.func_index_last == 17 ||
        Menu_Parameter.func_index_last == 18 ||
        Menu_Parameter.func_index_last == 19 ||
        Menu_Parameter.func_index_last == 20 ||
        Menu_Parameter.func_index_last == 21 ||
        Menu_Parameter.func_index_last == 22 ||
        Menu_Parameter.func_index_last == 23 ||
        Menu_Parameter.func_index_last == 24 ||
        Menu_Parameter.func_index_last == 25 ||
        Menu_Parameter.func_index_last == 26 ||
        Menu_Parameter.func_index_last == 27 ||
        Menu_Parameter.func_index_last == 28

    )
    {
        current_operation_index = k_table[Menu_Parameter.func_index].current_operation;
        (*current_operation_index)();

        Menu_Parameter.func_index_last = Menu_Parameter.func_index;
    }
    else if (Menu_Parameter.func_index != Menu_Parameter.func_index_last)
    {
        current_operation_index = k_table[Menu_Parameter.func_index].current_operation;
        (*current_operation_index)();

        Menu_Parameter.func_index_last = Menu_Parameter.func_index;
    }

    //UI_printf("\r\n func_index_last is:%d", Menu_Parameter.func_index_last);
    //UI_printf("\r\n Down_index is:%d", MenuSerchPara.Down_index);
}

有个需要注意的地方,我们一般使用的时候,在一个仅仅是显示信息,即不做其他操作,显示一些版本信息的时候,我们按除了返回键是不想让他响应的,否则,按一下就会刷新一次,体验很不好,所以需要做一下处理,简单来说就是,两次的索引值相同,就不再响应

但是一些情况下,我们是一直处于同一个界面,比如上翻、下翻,始终是处于同一个父界面,这时候需要响应以完成子项选择

    if (Menu_Parameter.func_index_last == 1 ||
        Menu_Parameter.func_index_last == 2 ||
        Menu_Parameter.func_index_last == 6 ||
        Menu_Parameter.func_index_last == 7 ||
        Menu_Parameter.func_index_last == 10 ||
        Menu_Parameter.func_index_last == 11 ||
      Menu_Parameter.func_index_last == 14 ||
        Menu_Parameter.func_index_last == 15 ||
        Menu_Parameter.func_index_last == 17 ||
        Menu_Parameter.func_index_last == 18 ||
        Menu_Parameter.func_index_last == 19 ||
        Menu_Parameter.func_index_last == 20 ||
        Menu_Parameter.func_index_last == 21 ||
        Menu_Parameter.func_index_last == 22 ||
        Menu_Parameter.func_index_last == 23 ||
        Menu_Parameter.func_index_last == 24 ||
        Menu_Parameter.func_index_last == 25 ||
        Menu_Parameter.func_index_last == 26 ||
        Menu_Parameter.func_index_last == 27 ||
        Menu_Parameter.func_index_last == 28

    )
    {
        current_operation_index = k_table[Menu_Parameter.func_index].current_operation;
        (*current_operation_index)();

        Menu_Parameter.func_index_last = Menu_Parameter.func_index;
    }

其实整个框架就这么多,很简单,编写程序主要是理清思绪,界面的切换、转换索引相连,就可以很轻松的编写多级菜单了

经验交流

欢迎添加小飞哥好友,也可以进群一起交流,欢迎优秀的你结识更多优秀的同行者

不错哦|多级菜单实现起来就是这么简单!_第1张图片

你可能感兴趣的:(CubeMX与HAL库系列教程,STM32,嵌入式,多级菜单,stm32,嵌入式)