You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Arduino Uno多Switch Case冲突问题:按钮控制权切换失效求助

解决Arduino菜单与子功能按钮冲突问题

嘿,我看了你的代码,马上就发现了核心问题:你的子功能逻辑只被调用了一次,之后就再也没机会运行了,而且主菜单的逻辑还在抢占按钮输入,导致子功能的switch完全没机会响应后续的按钮操作。别担心,咱们一步步修复它:

问题根源分析

  1. 你的loop()里只反复调用mainMenu(),而Select()子功能函数只在主菜单按下SELECT键时,被menuAction()调用了一次——之后loop就再也没执行过Select(),自然子功能的按钮处理逻辑就停了。
  2. 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();
  }
}

关键修复点总结

  • 状态隔离:用playingchoosing变量明确区分主菜单和子功能状态,loop根据状态执行对应逻辑,避免互相干扰
  • 持续执行:子功能的按钮处理逻辑在playing=true时被loop持续调用,确保能响应每一次按钮操作
  • 退出机制:给子功能添加了返回主菜单的逻辑,按下SELECT键即可保存选择并返回
  • 边界处理:给菜单选项和参数值添加了边界检查,防止出现非法值

内容的提问来源于stack exchange,提问作者Talinwan

火山引擎 最新活动