Arduino Micro: 69 должен отображаться, когда я нажимаю 1 на клавиатуре, однако появляется какое-то странное число (в данном случае 15).

// **У МЕНЯ ТАКЖЕ ПОЛУЧАЮТСЯ СТРАННЫЕ БЕЗУМНЫЕ ЗАДЕРЖКИ ПРИ НАЖАТИИ КНОПОК В МЕНЮ РЕЖИМОВ**

/*
Система управления запуском Джебедайи для космической программы Кербал
Альфа-сборка 0.70
Проект с открытым исходным кодом Джона Сеонга
*/

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>

const byte ROWS = 4;
const byte COLS = 3;

char keys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};

int timeV, thrustV, massV, gravityV;

byte rowPins[ROWS] = {12, 11, 10, 9};
byte colPins[COLS] = {8, 7, 6};

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

bool keyDetect = false;
bool menuKeyDetect = false;
bool goHomeDetect = false;

bool countDownDetect = false;
bool countDownOutput = false;
bool twrCalDetect = false;

String timeValue, thrustValue, massValue, gravityValue;

LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

void setup() {
  Serial.begin(9600);
  lcd.begin(16, 2);
  lcd.clear();

  Startup();

  timeV = constrain(timeV, 15, 120);
}

void loop() {
  PressKey();

  if (menuKeyDetect == true) {
    PressMenuKey();
  }

  if (goHomeDetect == true) {
    GoHome();
  }

  if (countDownDetect == true) {
    CountDownSequence();
  }

  if (countDownOutput == true) {
    CountDownOutputSequence();
  }
}

// Страницы & Разделы
void Startup() {
  lcd.print("Jeb's Launch");

  lcd.setCursor(0, 1);
  lcd.print("Control System");

  delay(3000);

  lcd.clear();
  lcd.print("Alpha Build 0.70");

  lcd.setCursor(0, 1);
  lcd.print(" STUDIO HORIZON");

  delay(3000);

  lcd.clear();
  lcd.print("Welcome, Kerman");

  lcd.setCursor(0, 1);
  lcd.print("Press any key...");

  keyDetect = true;
}

void ModeMenu() {
  menuKeyDetect = true;

  lcd.clear();
  lcd.print("1. Countdown");

  lcd.setCursor(0, 1);
  lcd.print("2. TWR");
}

void CountDown() {
  lcd.print("Time (sec): ");

  lcd.setCursor(0, 1);
  lcd.print("# to continue...");

  goHomeDetect = true;
  countDownDetect = true;
}

void TwrCal() {
  lcd.print("Thrust: ");

  lcd.setCursor(0, 1);
  lcd.print("# to continue...");

  goHomeDetect = true;
}

// Действия & Поведение
void PressKey() {
  char key = keypad.getKey();
  if (key) {
    Serial.println(key);
    if (keyDetect == true) {
      lcd.clear();
      ModeMenu();
      keyDetect = false;
    }
  }
}

void PressMenuKey() {
  char key = keypad.getKey();
  if (key == '1') {
    Serial.println(key);
    if (menuKeyDetect == true) {
      lcd.clear();
      CountDown();
      menuKeyDetect = false;
    }
  } else if (key == '2') {
    Serial.println(key);
    if (menuKeyDetect == true) {
      lcd.clear();
      TwrCal();
      menuKeyDetect = false;
    }
  }
}

void GoHome() {
  char key = keypad.getKey();
  if (key == '*') {
    lcd.clear();
    ModeMenu();
  }
}

void CountDownSequence() {
  char key = keypad.getKey();
  
  if (key == '0') {
    timeV = timeV + 0;
  }

  if (key == '1') {
    timeV = 69;
  }

  countDownDetect = false;
  countDownOutput = true;
}

void CountDownOutputSequence() {
    lcd.setCursor(13, 0);
    Serial.println(timeV);
    lcd.print(timeV);
}

, 👍1


1 ответ


Лучший ответ:

0

Согласно документации, функция getKey() неблокирующий. Это означает, что он не ожидает нажатия клавиши: вместо этого он немедленно возвращает значение независимо от того, была нажата клавиша или нет. Если никакая клавиша не была нажата (т.е. большую часть времени), она возвращает NUL символ, который имеет нулевое числовое значение и оценивается как false в логический контекст. Отсюда идиома:

char key = keypad.getKey();
if (key) {  // если клавиша действительно была нажата
    // справиться
}

Функция CountDownSequence() не учитывает это:

void CountDownSequence() {
  char key = keypad.getKey();  // нет проверки на NUL
  
  if (key == '0') {
    timeV = timeV + 0;  // это вообще ни на что не влияет
  }

  if (key == '1') {
    timeV = 69;  // это то, что вы ожидаете выполнить
  }

  countDownDetect = false;  // эти две строки сообщают остальным
  countDownOutput = true;   // программа, на которой мы закончили.
}

Поскольку key почти наверняка будет '\0', программа перейдет к следующий шаг без инициализации timeV.

В качестве примечания я рекомендую вам попробовать переписать свою программу как конечный конечный автомат. Одна переменная state сделает его менее сбивает с толку и легче понять, чем текущие коллекции логические значения.


Правка: расширение идеи конечного автомата. я попробовал реализация вашей концепции на его основе.

Для работы с системой меню, где пользователь может перемещаться по нескольким экранные «страницы», самый очевидный первый подход — иметь одно состояние за отображаемую страницу. Для этой программы эти состояния могут быть:

  • МЕНЮ, предлагающее перейти к одному из следующих двух страницы
  • SET_COUNT позволяет пользователю установить продолжительность обратного отсчета
  • TWR позволяет ему вычислить отношение тяги к весу
  • COUNT_DOWN фактически запускает таймер обратного отсчета

В дальнейшем я добавил дополнительное переходное состояние под названием START. который идет прямо перед MENU. Это не обязательно, и это только цель состоит в том, чтобы избежать повторения кода, имея один переход (STARTMENU), отвечающие за фактическое рисование меню.

При реализации конечного автомата моей первой рекомендацией будет можно начать с рисования диаграммы состояний, подобной этой:

Диаграмма состояния системы управления запуском Джебедайи

На этой диаграмме каждый переход помечен событием, которое запускает Это. События представляют собой нажатия клавиш, за исключением «завершено», что означает, что обратный отсчет сделан. Диаграмма состояний может стать значительно больше сложнее, чем это, когда вы добавляете новые функции. Однако это очень хороший способ проектировать вашу систему с точки зрения пользователя взаимодействие.

После того как вы довольны диаграммой состояний, кодирование становится просто делом. преобразования его в конструкцию switch/case с одним case на государство. Вот мой взгляд на это:

/*
   Jebediah's Launch Control System for Kerbal Space Program
   Alpha Build 0.70
   An Open-Source Project by John Seong
*/

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>

const byte ROWS = 4;
const byte COLS = 3;

char keys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};

byte rowPins[ROWS] = {12, 11, 10, 9};
byte colPins[COLS] = {8, 7, 6};

Keypad keypad=Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

// Показать двухстрочное сообщение на ЖК-дисплее.
void lcdShow(const char *line0, const char *line1) {
  lcd.clear();
  lcd.print(line0);
  lcd.setCursor(0, 1);
  lcd.print(line1);
}

void setup() {
  Serial.begin(9600);
  lcd.begin(16, 2);
  lcdShow("Jeb's Launch", "Control System");
  delay(3000);
  lcdShow("Alpha Build 0.70", " STUDIO HORIZON");
  delay(3000);
  lcdShow("Welcome, Kerman", "Press any key...");
  while (!keypad.getKey()) ;  // ждем нажатия клавиши
}

void loop() {
  static enum {START, MENU, SET_COUNT, COUNT_DOWN, TWR} state = START;
  static uint32_t last_second;  // значение millis() за последнюю полную секунду
  static int count;             // значение обратного отсчета

  char key = keypad.getKey();

  switch (state) {
    case START:  // переходное состояние
      lcdShow("1. Countdown", "2. TWR");
      state = MENU;
      /* fallthrough */
    case MENU:
      if (key == '1') {  // Обратный отсчет
        lcdShow("Enter time", "seconds: ");
        count = 0;
        state = SET_COUNT;
      } else if (key == '2') {  // TWR
        lcdShow("Thrust:", "# to continue...");
        state = TWR;
      }
      break;
    case SET_COUNT:
      if (key >= '0' && key <= '9' && count <= 99) {
        lcd.print(key);
        count = 10 * count + (key - '0');
      } else if (key == '#') {
        lcdShow("Take off in", "     seconds");
        // Принудительное обновление при вводе COUNT_DOWN:
        last_second = millis() - 1000;
        count++;
        state = COUNT_DOWN;
      } else if (key == '*') {
        state = START;
      }
      break;
    case COUNT_DOWN:
      if (millis() - last_second >= 1000) {
        last_second += 1000;
        count--;
        if (count == 0) {
          Serial.println("Take off!");
        } else if (count < 0) {
          state = START;
          break;
        }
        lcd.setCursor(1, 1);
        lcd.print(count<10 ? "  " : count<100 ? " " : "");  // накладка
        lcd.print(count);
      } else if (key == '*') {
        state = START;
      }
      break;
    case TWR:
      if (key == '*' || key == '#') {
        state = START;
      }
      break;
  }
}

Обратите внимание на комментарий «fallthrough» между START и MENU. Этот указывает на то, что отсутствие break в этом месте предназначено: обработка случая MENU последует сразу за обработкой случай START в той же итерации цикла. Цель падения через, чтобы не потерять текущее нажатие клавиши, если таковое имеется. Если это не важно, вы можете поставить здесь обычный break и отложить обработку MENU к следующей итерации цикла.

Обратите внимание, что нажатия клавиш проверяются тестами вида if (key == SOME_VALUE), что означает отсутствие необходимости в дополнительном if (key) для проверки фактического нажатия клавиши.

,