avatar

松果工作室

欢迎光临

  • 首页
  • freeRTOS
  • ESP
  • 开发手册
  • 快速笔记
  • 个人收藏
  • 工具
Home 多级菜单
文章

多级菜单

Posted 2025-02-13 Updated 2025-03- 29
By YCP
42~54 min read

使用方法

  • 以下四个函数分别放进各自按键的回调中,点击一次调用相关函数

    void menu_up();
    void menu_down();
    void menu_confirm();
    void menu_back();
    
  • 以下四个函数选择性进行重定义,作用为进入末级功能页面后,此时按键不再归菜单驱动接管,需要用户自行定义按键功能,驱动已经定义了确认按键的作用:进行开启或关闭菜单驱动与按键的关联

    __attribute__((weak)) void menu_down_refunc();
    __attribute__((weak)) void menu_up_refunc();
    __attribute__((weak)) void menu_back_refunc();
    __attribute__((weak)) void menu_confirm_refunc();
    
  • 原驱动已包含命令行的菜单指示,用户可重定义以下函数进行转为图形化

    __attribute__((weak)) void lcd_display_add(Menu_t *menu) {
    }
    
  • 开启或关闭菜单与按键的关联

    void write_menu_status(MENU_STATUS status) {
      menuStatus = status;
    }
    
  • 定义进入功能页面后要显示的页面

    void write_function_page(uint8_t index)
    
#pragma once
#include "main.h"

typedef struct MenuItem {
    const char *name;
    void (*task)();
    struct Menu *submenu;
} MenuItem_t;

typedef struct Menu {
    const char *name;
    MenuItem_t *items;
    int item_count;
    struct Menu *parent;
} Menu_t;

typedef enum{
    MENU_OPEN,
    MENU_CLOSE
}MENU_STATUS;

extern MENU_STATUS menuStatus;
extern Menu_t *current_menu;

//显示菜单页面,引出的原因是在初始化菜单之后需要手动显示第一个页面
void lcd_display();

//打开或者关闭多级菜单功能
//一般在进入末级菜单后会退出多级菜单框架,然后原有的按钮也会赋予不同的功能,
//因为要用这些按键干别的事而不是控制菜单
void write_menu_status(MENU_STATUS status);
MENU_STATUS read_menu_status();

//在退出多级菜单之后,进入功能操作页面要选择第几个页面
void write_function_page(uint8_t index);
uint8_t read_function_page();

//在退出多级菜单之后,进入功能操作页面,要把原来的按键重新给定义
__attribute__((weak)) void menu_down_refunc();
__attribute__((weak)) void menu_up_refunc();
__attribute__((weak)) void menu_back_refunc();
__attribute__((weak)) void menu_confirm_refunc();
__attribute__((weak)) void lcd_display_add(Menu_t *menu);
#include "menu.h"
#include <stdio.h>

Menu_t *current_menu;
int current_item_index = 0;
MENU_STATUS menuStatus = MENU_OPEN;
uint8_t function_page_index = 0;

void write_function_page(uint8_t index) {
    function_page_index = index;
}
uint8_t read_function_page() {
    return function_page_index;
}

/*
 * [ycp250315]:功能菜单页面启用状态
 */
void write_menu_status(MENU_STATUS status) {
    menuStatus = status;
}

MENU_STATUS read_menu_status() {
    return menuStatus;
}

__attribute__((weak)) void menu_down_refunc() {

}

__attribute__((weak)) void menu_up_refunc() {

}

__attribute__((weak)) void menu_back_refunc() {

}

__attribute__((weak)) void menu_confirm_refunc() {
    write_menu_status(MENU_OPEN);
    write_function_page(0);
    lcd_display();
}

__attribute__((weak)) void lcd_display_add(Menu_t *menu) {

}

void lcd_display() {
    printf("\n=== %s ===\n", current_menu->name);
    for (int i = 0; i < current_menu->item_count; i++) {
        printf("%s %s",
               (i == current_item_index) ? ">" : " ",
               current_menu->items[i].name);

        // 显示子菜单提示
        if (current_menu->items[i].submenu) {
            printf(" →");
        }
        printf("\n");
    }
    printf("-----------------\n");
    lcd_display_add(current_menu);
}

void display_menu() {
    lcd_display();
}

void menu_up() {
    if (read_menu_status() == MENU_OPEN) {
        if (current_item_index > 0) {
            current_item_index--;
        }
        display_menu();
    } else if (read_menu_status() == MENU_CLOSE) {
        menu_up_refunc();
    }
}

void menu_down() {
    if (read_menu_status() == MENU_OPEN) {
        if (current_item_index < current_menu->item_count - 1) {
            current_item_index++;
        } else {
            current_item_index = 0;
        }
        display_menu();
    } else if (read_menu_status() == MENU_CLOSE) {
        menu_down_refunc();
    }
}
void menu_confirm() {
    if (read_menu_status() == MENU_OPEN) {
        MenuItem_t *current_item = &current_menu->items[current_item_index];

        if (current_item->task) {
            current_item->task();
        }
        if (current_item->submenu) {
            printf("进入 %s\n", current_item->submenu->name);
            current_menu = current_item->submenu;
            current_item_index = 0;
        }
        lcd_display();
    } else if (read_menu_status() == MENU_CLOSE) {
        //write_menu_status(MENU_OPEN);
        menu_confirm_refunc();
    }
}

void menu_back() {
    if (read_menu_status() == MENU_OPEN) {
        if (current_menu->parent) {
            printf("返回 %s\n", current_menu->parent->name);
            current_menu = current_menu->parent;
            current_item_index = 0;
        }
        lcd_display();
    } else if (read_menu_status() == MENU_CLOSE) {
        menu_back_refunc();
    }
}

使用方法

#include "default_task_menu.h"
#include "main.h"
#include "menu.h"
#include "stdio.h"
#include "fakertos.h"
#include "hal_gpio.h"

//结构: page -> page_items -> son page

/*
 * [ycp250315]:页面
 * 命名方式:层级_序号
 */
Menu_t root;

Menu_t page1_1;
Menu_t page1_2;
Menu_t page1_3;
Menu_t page1_4;

Menu_t page2_1;
Menu_t page2_2;

void change_baud(){
    write_menu_status(MENU_CLOSE);
    write_function_page(1);
    printf("enter func page\r\n");
}

/*
 * [ycp250315]:页面的子页面
 * 命名方式:层级_序号
 */
MenuItem_t root_items[4] = {
        {"lcd_display_1", NULL, NULL},
        {"lcd_display_2", NULL, NULL},
        {"lcd_display_3", NULL, NULL},
        {"settings", NULL, &page1_4}
};
MenuItem_t page1_4_items[2] = {
        {"commu", NULL, &page2_1},
        {"testx", change_baud, NULL}
};
MenuItem_t page2_1_items[2] = {
        {"change_baud", change_baud, NULL},
        {"change_stop", NULL, NULL}
};


void init_menus() {
    /*
     * [ycp250315]:根页面
     */
    root = (Menu_t) {
            "root",
            root_items,
            sizeof(root_items)/sizeof(root_items[0]),
            NULL
    };

    /*
     * [ycp250315]:1级页面
     */
    page1_1 = (Menu_t) {
            "display1",
            NULL,
            0,
            &root
    };

    page1_2 = (Menu_t) {
            "display2",
            NULL,
            0,
            &root
    };

    page1_3 = (Menu_t) {
            "display3",
            NULL,
            0,
            &root
    };

    page1_4 = (Menu_t) {
            "settings",
            page1_4_items,
            sizeof(page1_4_items)/sizeof(page1_4_items[0]),
            &root
    };

    /*
     * [ycp250315]:2级页面
     */
    page2_1 = (Menu_t) {
            "communication",
            page2_1_items,
            sizeof(page2_1_items)/sizeof(page2_1_items[0]),
            &page1_4
    };

    page2_2 = (Menu_t) {
            "test_xxx",
            NULL,
            0,
            &page1_4
    };
}

//放在初始化
void Default_Menu_Ev_Init() {
    init_menus();
    current_menu = &root;
    lcd_display();
}

int blink(){
    hal_gpio_trig_level(HAL_GPIOA,5);
    return 0;
}
//这里放到循环函数里面,100ms循环一次,
//通过别处调用write_function_page(x),这里的页面就会改变
void Default_menu_Ev_Process() {
    switch (read_function_page()) {
        case 0:
            break;
        case 1:
            rtosTask_A(blink,1000,millis());
            break;
        case 2:
            break;
        case 3:
            break;
        case 4:
            break;
        case 5:
            break;
    }
}

层级结构

截屏2025-03-15 下午7.15.20.png

License:  CC BY 4.0
Share

Further Reading

OLDER

ESP32(五) ESP32 OTA

NEWER

ESP32(四) STA & AP

Recently Updated

  • ESP32(八) 简单的webserver
  • ESP32(七) NVS
  • ESP32(四) STA & AP
  • 多级菜单
  • ESP32(五) ESP32 OTA

Trending Tags

WCH Linux Elec freeRTOS STM ESP Flutter Others SwiftUI

Contents

©2025 松果工作室. Some rights reserved.

Using the Halo theme Chirpy