Arduino Uno多Switch Case冲突问题:按钮控制权切换失效求助
解决Arduino菜单与子功能按钮冲突问题
嘿,我看了你的代码,马上就发现了核心问题:你的子功能逻辑只被调用了一次,之后就再也没机会运行了,而且主菜单的逻辑还在抢占按钮输入,导致子功能的switch完全没机会响应后续的按钮操作。别担心,咱们一步步修复它:
问题根源分析
- 你的
loop()里只反复调用mainMenu(),而Select()子功能函数只在主菜单按下SELECT键时,被menuAction()调用了一次——之后loop就再也没执行过Select(),自然子功能的按钮处理逻辑就停了。 mainMenu()里的按钮读取逻辑,即使在playing=true时,虽然导航代码不执行,但整个函数还是在跑,可能会干扰子功能的按钮状态读取。
修复方案
1. 重构Loop逻辑,按状态分流
把loop改成根据playing状态,决定是运行主菜单还是子功能,这样两者的逻辑就不会互相抢占了:
void loop() { if (playing) { // 处于子功能状态时,持续运行子功能逻辑 Select(); } else { // 处于主菜单状态时,运行主菜单逻辑 mainMenu(); } }
2. 修改主菜单的进入子功能逻辑
原来的menuAction()只需要在按下SELECT时切换状态,不需要持续调用,所以直接在mainMenu()里处理状态切换,同时初始化子功能的显示:
void mainMenu(){ int navigate = lcd.readButtons(); if(choosing){ if (BUTTON_DOWN & navigate){ menu = (menu >=2 ? 1 : menu+1); // 防止menu越界 displayMenu(); delay(200); } if (BUTTON_UP & navigate){ menu = (menu <=1 ? 2 : menu-1); // 防止menu越界 displayMenu(); delay(200); } if (BUTTON_SELECT & navigate){ playing = true; choosing = false; // 根据当前菜单选项初始化子功能 if(menu ==1){ lcd.clear(); choice =1; // 回到默认选择S SelectDisplay(size_S, size_M); // 显示子功能初始界面 } delay(200); } } }
3. 修复子功能的按钮处理与退出逻辑
给子功能加上回到主菜单的机制(比如按下SELECT键确认后返回),同时确保按钮逻辑持续运行:
// 去掉没用的参数,直接用全局变量 void Select(){ uint8_t buttons, changes; buttons = lcd.readButtons(); changes = oldButtons & (~buttons); // 检测按钮释放事件(避免长按重复触发) switch (choice){ case 1: // 选择S if (changes & BUTTON_UP){ size_S = (size_S == 10 ? 4 : size_S+1); SelectDisplay(size_S, size_M); // 更新显示 }else if (changes & BUTTON_DOWN){ size_S = (size_S == 4 ? 10 : size_S-1); SelectDisplay(size_S, size_M); // 更新显示 }else if (changes & BUTTON_RIGHT){ choice= 2; SelectDisplay(size_S, size_M); // 更新显示 } break; case 2: // 选择M if (changes & BUTTON_UP){ size_M = (size_M == 4 ? 2 : size_M+1); SelectDisplay(size_S, size_M); // 更新显示 }else if (changes & BUTTON_DOWN){ size_M = (size_M == 2 ? 4 : size_M-1); SelectDisplay(size_S, size_M); // 更新显示 }else if (changes & BUTTON_LEFT){ choice = 1; SelectDisplay(size_S, size_M); // 更新显示 } break; } // 按下SELECT键,保存选择并返回主菜单 if (changes & BUTTON_SELECT){ choice_S = size_S; choice_M = size_M; // 可选:保存到EEPROM EEPROM.write(0, size_S); EEPROM.write(1, size_M); EEPROM.commit(); // 切换回主菜单状态 playing = false; choosing = true; displayMenu(); // 重新显示主菜单 } oldButtons = buttons; }
4. 清理冗余代码
可以删掉menuAction()函数,因为现在不需要它了——状态切换和子功能调用都由loop和mainMenu直接处理。
完整修改后的代码
#include <Wire.h> #include <Adafruit_RGBLCDShield.h> #include <utility/Adafruit_MCP23017.h> #include <EEPROM.h> Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); // 状态变量:区分主菜单/子功能 bool choosing = true; bool playing = false; // 子功能参数 int size_S = 4; int size_M = 2; uint8_t choice = 1; uint8_t oldButtons = 0; char buffer[12]; // 保存用户最终选择 int choice_S; int choice_M; // 主菜单选项 int menu = 1; // 显示子功能界面 void SelectDisplay(int size_S, int size_M){ lcd.setCursor(0,0); sprintf(buffer, "S:%02d M:%02d", size_S, size_M); lcd.print(buffer); lcd.setCursor(0,1); lcd.print((choice==1 ? "Choose S" : "Choose M")); } // 子功能逻辑:选择S和M void Select(){ uint8_t buttons, changes; buttons = lcd.readButtons(); changes = oldButtons & (~buttons); switch (choice){ case 1: // 选择S if (changes & BUTTON_UP){ size_S = (size_S == 10 ? 4 : size_S+1); SelectDisplay(size_S, size_M); }else if (changes & BUTTON_DOWN){ size_S = (size_S == 4 ? 10 : size_S-1); SelectDisplay(size_S, size_M); }else if (changes & BUTTON_RIGHT){ choice= 2; SelectDisplay(size_S, size_M); } break; case 2: // 选择M if (changes & BUTTON_UP){ size_M = (size_M == 4 ? 2 : size_M+1); SelectDisplay(size_S, size_M); }else if (changes & BUTTON_DOWN){ size_M = (size_M == 2 ? 4 : size_M-1); SelectDisplay(size_S, size_M); }else if (changes & BUTTON_LEFT){ choice = 1; SelectDisplay(size_S, size_M); } break; } // 确认选择并返回主菜单 if (changes & BUTTON_SELECT){ choice_S = size_S; choice_M = size_M; EEPROM.write(0, size_S); EEPROM.write(1, size_M); EEPROM.commit(); playing = false; choosing = true; displayMenu(); } oldButtons = buttons; } // 显示主菜单 void displayMenu(){ lcd.clear(); switch(menu){ case 1: lcd.setCursor(0,0); lcd.print(">Play"); lcd.setCursor(0,1); lcd.print(" Characters"); break; case 2: lcd.setCursor(0,0); lcd.print(" Play"); lcd.setCursor(0,1); lcd.print(">Characters"); break; // 可以添加更多菜单选项 } } // 主菜单导航逻辑 void mainMenu(){ int navigate = lcd.readButtons(); if(choosing){ if (BUTTON_DOWN & navigate){ menu = (menu >=2 ? 1 : menu+1); displayMenu(); delay(200); } if (BUTTON_UP & navigate){ menu = (menu <=1 ? 2 : menu-1); displayMenu(); delay(200); } if (BUTTON_SELECT & navigate){ playing = true; choosing = false; if(menu ==1){ lcd.clear(); choice =1; SelectDisplay(size_S, size_M); } delay(200); } } } void setup() { Serial.begin(9600); lcd.begin(16,2); displayMenu(); // 可选:从EEPROM加载之前的设置 size_S = EEPROM.read(0); size_M = EEPROM.read(1); // 确保加载的值在合法范围内 size_S = (size_S <4 || size_S >10) ?4 : size_S; size_M = (size_M <2 || size_M >4) ?2 : size_M; } void loop() { if (playing) { Select(); } else { mainMenu(); } }
关键修复点总结
- 状态隔离:用
playing和choosing变量明确区分主菜单和子功能状态,loop根据状态执行对应逻辑,避免互相干扰 - 持续执行:子功能的按钮处理逻辑在
playing=true时被loop持续调用,确保能响应每一次按钮操作 - 退出机制:给子功能添加了返回主菜单的逻辑,按下SELECT键即可保存选择并返回
- 边界处理:给菜单选项和参数值添加了边界检查,防止出现非法值
内容的提问来源于stack exchange,提问作者Talinwan




