[方案相关] 做单片机按键切换UI菜单,如何做更加简洁?

[复制链接]
619|2
gejigeji521 发表于 2025-8-17 21:25 | 显示全部楼层 |阅读模式
在单片机中实现简洁的按键切换UI菜单,核心在于状态机设计和数据结构优化。以下是一个经过实践验证的简洁方案:

1. 核心数据结构
  1. typedef struct {
  2.     const char* title;      // 菜单标题
  3.     void (*display)(void);  // 显示函数指针
  4.     void (*enter)(void);    // 进入菜单函数
  5.     uint8_t parent;         // 父菜单索引
  6.     uint8_t children[4];    // 子菜单索引数组
  7.     uint8_t child_count;    // 子菜单数量
  8. } MenuItem;

  9. // 菜单数组定义
  10. MenuItem menuTree[] = {
  11.     // ID, 标题,      显示函数,  进入函数,   父ID,   子菜单ID数组
  12.     {0, "主菜单",   showMain,   NULL,     0,     {1,2,3},     3},
  13.     {1, "设置",     showConfig, enterConfig, 0,     {4,5},       2},
  14.     {2, "信息",     showInfo,   NULL,     0,     { },         0},
  15.     {3, "控制",     showControl,enterCtrl, 0,     {6},         1},
  16.     {4, "时间设置", showTime,   NULL,     1,     { },         0},
  17.     {5, "参数设置", showParam,  NULL,     1,     { },         0},
  18.     {6, "电机控制", showMotor,  NULL,     3,     { },         0}
  19. };
2. 状态管理变量
  1. uint8_t currentMenu = 0;     // 当前菜单ID
  2. uint8_t prevMenu = 0;        // 上级菜单ID
  3. uint8_t menuPosition = 0;    // 当前菜单中的选项位置
3. 按键处理状态机

  1. void handleKeyPress(KeyCode key) {
  2.     switch(key) {
  3.         case KEY_UP:
  4.             menuPosition = (menuPosition > 0) ? menuPosition-1 :
  5.                           menuTree[currentMenu].child_count-1;
  6.             break;
  7.             
  8.         case KEY_DOWN:
  9.             menuPosition = (menuPosition+1) % menuTree[currentMenu].child_count;
  10.             break;
  11.             
  12.         case KEY_ENTER:
  13.             prevMenu = currentMenu;
  14.             currentMenu = menuTree[currentMenu].children[menuPosition];
  15.             if(menuTree[currentMenu].enter)
  16.                 menuTree[currentMenu].enter();
  17.             menuPosition = 0;  // 重置子菜单位置
  18.             break;
  19.             
  20.         case KEY_BACK:
  21.             if(currentMenu != prevMenu) {
  22.                 currentMenu = prevMenu;
  23.                 prevMenu = menuTree[currentMenu].parent;
  24.             }
  25.             break;
  26.     }
  27.     refreshDisplay();  // 刷新显示
  28. }
  1. void refreshDisplay() {
  2.     // 1. 清屏
  3.     LCD_Clear();
  4.    
  5.     // 2. 显示当前菜单标题
  6.     LCD_Print(menuTree[currentMenu].title, 0, 0);
  7.    
  8.     // 3. 显示子菜单项(带>指示当前选项)
  9.     for(uint8_t i=0; i<menuTree[currentMenu].child_count; i++) {
  10.         uint8_t childID = menuTree[currentMenu].children[i];
  11.         char prefix = (i == menuPosition) ? '>' : ' ';
  12.         LCD_Printf("%c %s", prefix, menuTree[childID].title);
  13.     }
  14.    
  15.     // 4. 调用自定义显示函数(如需要额外显示内容)
  16.     if(menuTree[currentMenu].display) {
  17.         menuTree[currentMenu].display();
  18.     }
  19. }
  1. // UP键处理:
  2. menuPosition = (menuPosition > 0) ? menuPosition-1 : max-1;

  3. // DOWN键处理:
  4. menuPosition = (menuPosition+1) % max;



 楼主| gejigeji521 发表于 2025-8-17 21:26 | 显示全部楼层
扩展

增加菜单深度标记(如需限制深度):
  1. uint8_t menuDepth = 0;
  2. #define MAX_DEPTH 5
添加菜单缓存机制(针对慢速显示设备):
  1. static uint8_t lastMenu = 0xFF;
  2. if(lastMenu != currentMenu) {
  3.     // 仅当菜单变化时重绘
  4.     fullRedraw();
  5.     lastMenu = currentMenu;
  6. }

 楼主| gejigeji521 发表于 2025-8-17 21:26 | 显示全部楼层
感兴趣的可以试试这种方案。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

198

主题

2509

帖子

8

粉丝
快速回复 在线客服 返回列表 返回顶部