Not bad. The implementation of multi-level menu is so simple!

abstract

The product is user-oriented. Users only need functions and good interactive interface. Multi-level menu plays an important role in it. Limited keys realize complex interface and are linked layer by layer. The marking method is very difficult for menu management, and they will be dizzy if they have more... Here is a fairly good multi-level menu

The framework itself is not complex. Small partners can directly look at the source code. If there are any problems, they can communicate together

Hardware connection

STM32F407ZGT6

ST7789 LCD screen (SPI1, multiplex development board NRF24L01 interface)

3 keys PC7, PC8, PC9

code implementation

The implementation is relatively simple. I directly enter the code part. I use three keys. The functions are: up (increase the value), down (decrease the value) and confirm

First, define a structure, which contains five members

1. cur: current menu index number

2. Left: the index number corresponding to the left key

3. OK: the index number corresponding to the confirmation part

4. Right: the index number corresponding to the right key

5. The current index corresponds to the menu interface (or function) to be executed

typedef struct
{
    uint8_t cur;                     //Current index number
    uint8_t left;                    //left   Index number of
    uint8_t ok;                      //OK   Index number of
    uint8_t right;                   //right   Index number of
    void (*current_operation)(void); //Currently executing function
} KEY_TABLE;

Then define a structure of key values and menu index numbers:

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;

Assign up and down index values to each submenu

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;

Using the structure mentioned above, you only need to fill in the corresponding index number, press the key and find the interface to jump according to the index number

const KEY_TABLE k_table[] =
    {
        {0, 2, 3, 1, (*MainMenuTask)}, //0 --- startup main menu

        {1, 2, 3, 1, (*MenuSearchDown)}, //1 --- level 1 menu down
        {2, 2, 3, 1, (*MenuSearchUP)},   //2 --- level 1 menu up
        {3, 0, 3, 3, (*MenuSelect)},     //3 --- level 1 menu confirmation

        {4, 0, 4, 4, (*Psd_Auth_Process)}, //4 --- Level 2 password authentication menu

        {5, 7, 8, 6, (*SysMenu_Process)},   //5 --- Level 2 system setting menu, not used
        {6, 7, 8, 6, (*Sys_SearchDown)},    //6 --- Level 2 system setting down
        {7, 7, 8, 6, (*Sys_SearchUP)},      //7 --- Level 2 system setting up
        {8, 7, 8, 6, (*SubMenu_SysSelect)}, //8 --- confirmation of level 2 system setting menu

        {9, 11, 12, 10, (*ComMenu_Process)},   //9 --- detailed display of level 1 communication menu, not used
        {10, 11, 12, 10, (*Com_SearchDown)},   //10 --- Level 2 communication setting down
        {11, 11, 12, 10, (*Com_SearchUP)},     //11 --- Level 2 communication setting up
        {12, 3, 12, 12, (*SubMenu_ComSelect)}, //12 --- confirmation of level 1 communication menu selection

        {13, 15, 3, 14, (*GamesMenu_Process)},   //13 --- Level 2 system setting menu, not used
        {14, 15, 3, 14, (*Game_SearchDown)},    //14 --- Level 2 system setting down
        {15, 15, 3, 14, (*Game_SearchUP)},      //15 --- Level 2 system setting up
        {16, 3, 8, 6, (*SubMenu_GameSelect)}, //16 --- confirmation of level 2 system setting menu

        {17, 18, 5, 17, (*Current_RangeDown)}, //17 -- level 3 system setting current range setting -- reducing current range
        {18, 18, 5, 17, (*Current_RangeUP)},   //18 --- current range setting of level 3 system -- increase the current range

        {19, 20, 5, 19, (*Voltage_RangeDown)}, //19 -- level 3 system setting current range setting -- reducing current range
        {20, 20, 5, 19, (*Voltage_RangeUP)},   //20 --- Level 3 system setting current range setting -- increase the current range

        {21, 22, 5, 21, (*DisMode_SearchDown)}, //Level 21 -- 3 system setting light mode
        {22, 22, 5, 21, (*DisMode_SearchUP)},   //22 --- Level 3 system setting light mode up

        {23, 24, 5, 23, (*Lightness_RangeDown)}, //23 -- level 3 system setting light brightness setting -- increase brightness
        {24, 24, 5, 23, (*Lightness_RangeUP)},   //24 --- Level 3 system setting light brightness setting -- reduce brightness

        {25, 26, 5, 25, (*Temperature_RangeDown)}, //25 -- level 3 system setting temperature setting -- increase temperature
        {26, 26, 5, 25, (*Temperature_RangeUP)},   //26 --- Level 3 system setting -- temperature reduction

        {27, 28, 5, 27, (*NetMode_SerchDown)},   //27 -- level 3 system setting network mode selection
        {28, 28, 5, 27, (*NetMode_SerchUP)},     //28 --- Level 3 system setting network mode selection
};

The contents to be displayed in the menu are defined as pointer array

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",
};

...

Key part:

#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) //A key is pressed
        { 
            key_LOR[i].T_press++;

            if (key_LOR[i].T_press > 600)  //Long key
            {
       
            }
        }
        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;
}

Then press the query key to process the corresponding index value:

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);
}

There is a point that needs attention. When we generally use it, when we just display information, that is, we don't do other operations and display some version information, we don't want it to respond by pressing the return key. Otherwise, pressing it will refresh once. The experience is very bad, so we need to do some processing. In short, the index values of the two times are the same, No longer respond

However, in some cases, we are always in the same interface, such as flip up and flip down. We are always in the same parent interface. At this time, we need to respond to complete the sub item selection

    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;
    }

In fact, there are so many frameworks. It's very simple. The main purpose of writing the program is to clarify your thoughts. You can easily write multi-level menus by connecting the interface switching and conversion indexes

Experience exchange

Welcome to add brother Xiaofei's friends. You can also join the group to communicate. You are welcome to meet more excellent colleagues

Tags: Embedded system stm32

Posted on Sun, 19 Sep 2021 21:12:31 -0400 by Gonwee