Регистрация данных при просмотре меню на ЖК-экране

Я новичок в Arduino и не особенно разбираюсь в коде ;) . Я разрабатываю устройство SD регистрации температуры от 2-х термопар. Аппаратное обеспечение работает хорошо, и сейчас я пытаюсь усовершенствовать использование ЖК-дисплея с меню.

Я хочу иметь возможность во время записи данных на SD-карту (скажем, каждую секунду) просматривать меню, чтобы:

  • Просмотреть текущую температуру

  • см. максимальное значение измерений

  • видеть, что время уже истекло

  • и т. д.

Я использовал код меню из (https://www.instructables.com/id /Arduino-Uno-Menu-Template/)

Дело в том, что когда я вхожу в подменю (menuItem()), пока я не выхожу из этого подменю, код больше не зацикливается, поэтому регистрация данных будет приостановлена.

Итак, мой вопрос: как мне организовать свой код, чтобы иметь возможность выполнять повторяющуюся задачу в фоновом режиме (регистрация температуры) во время просмотра меню, предоставляющего информацию и/или предлагающего действия (остановить регистрацию данных, сброс и т. д.).

>

Вот код из инструкций, модифицированный для использования с ЖК-экраном adafruit:

/********************************************* **********************************************
Название: Экранное меню ЖК-кнопок
Автор: Пол Сиверт
Создано: 14 июня 2016 г.
Последнее изменение: 14 июня 2016 г.
Версия: 1.0
Примечания: Этот код предназначен для использования с Arduino Uno и ЖК-экраном/кнопочным экраном.
Цель состоит в том, чтобы каждый мог использовать эту программу, чтобы дать им возможность начать
программа с полнофункциональным меню с минимальными доработками
требуется пользователю.
Лицензия: Эта программа является свободным программным обеспечением. Вы можете распространять его и/или изменять
это в соответствии с условиями Стандартной общественной лицензии GNU, опубликованной
Фонд свободного программного обеспечения, либо версия 3 Лицензии, либо
(по вашему выбору) любая более поздняя версия.
Данная программа распространяется в надежде, что она будет полезна,
но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемой гарантии
ТОВАРНАЯ ПРИГОДНОСТЬ или ПРИГОДНОСТЬ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ. См.
Стандартная общественная лицензия GNU для более подробной информации.
************************************************* *************************************/
/*
Эта программа предназначена для того, чтобы максимально приблизить вас к готовому меню для стандартного ЖК-экрана/кнопочного экрана Arduino Uno. Единственные необходимые модификации
нужно добавить элементы меню в главное меню (массив MenuItems), а затем изменить/настроить приведенные ниже функции void для каждого из этих вариантов.
*/

// Вы можете иметь до 10 пунктов меню в приведенном ниже массиве MenuItems[] без необходимости вообще менять базовое программирование. Назовите их как хотите. Помимо 10 предметов, вам придется добавить дополнительные «кейсы» в переключатель/кейс.
// раздел функцииoperMainMenu() ниже. Вам также придется добавить в программу дополнительные функции void (например, MenuItem11, MenuItem12 и т. д.).
String menuItems[] = {"ITEM 1", "ITEM 2", "ITEM 3", "ITEM 4", "ITEM 5", "ITEM 6"};

// Переменные кнопки навигации
int readKey;

// Переменные управления меню
int menuPage = 0;
int maxMenuPages = round(((sizeof(menuItems) / sizeof(String)) / 2) + .5);
int cursorPosition = 0;

// Создает 3 специальных символа для отображения меню
byte downArrow[8] = {
  0b00100, // *
  0b00100, // *
  0b00100, // *
  0b00100, // *
  0b00100, // *
  0b10101, // * * *
  0b01110, // ***
  0b00100  // *
};

byte upArrow[8] = {
  0b00100, // *
  0b01110, // ***
  0b10101, // * * *
  0b00100, // *
  0b00100, // *
  0b00100, // *
  0b00100, // *
  0b00100  // *
};

byte menuCursor[8] = {
  B01000, // *
  B00100, // *
  B00010, // *
  B00001, // *
  B00010, // *
  B00100, // *
  B01000, // *
  B00000  //
};

#include <Wire.h>
#include <LiquidCrystal.h>

// Установка контактов экрана ЖК-дисплея
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>

Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

void setup() {

  // Инициализирует последовательную связь
  Serial.begin(9600);

  // Инициализирует и очищает ЖК-экран
  lcd.begin(16, 2);
  lcd.clear();

  // Создает байт для трех пользовательских символов
  lcd.createChar(0, menuCursor);
  lcd.createChar(1, upArrow);
  lcd.createChar(2, downArrow);
}
///////////////////////////////////////////////
void loop() {
  mainMenuDraw();
  drawCursor();
  operateMainMenu();

  Serial.print("Loop");
}
////////////////////////////////////////////////
// Эта функция сгенерирует 2 пункта меню, которые могут поместиться на экране. Они будут меняться по мере прокрутки меню. Стрелки вверх и вниз укажут ваше текущее положение в меню.
void mainMenuDraw() {
  Serial.print(menuPage);
  lcd.clear();
  lcd.setCursor(1, 0);
  lcd.print(menuItems[menuPage]);
  lcd.setCursor(1, 1);
  lcd.print(menuItems[menuPage + 1]);
  if (menuPage == 0) {
    lcd.setCursor(15, 1);
    lcd.write(byte(2));
  } else if (menuPage > 0 and menuPage < maxMenuPages) {
    lcd.setCursor(15, 1);
    lcd.write(byte(2));
    lcd.setCursor(15, 0);
    lcd.write(byte(1));
  } else if (menuPage == maxMenuPages) {
    lcd.setCursor(15, 0);
    lcd.write(byte(1));
  }
}

// При вызове эта функция сотрет текущий курсор и перерисует его на основе переменных курсораPosition и MenuPage.
void drawCursor() {
  for (int x = 0; x < 2; x++) {     // Удаляет текущий курсор
    lcd.setCursor(0, x);
    lcd.print(" ");
  }

  // Меню настроено как прогрессивное (menuPage 0 = Item 1 и Item 2, MenuPage 1 = Item 2 и Item 3, MenuPage 2 = Item 3 и Item 4), поэтому
  // чтобы определить, где должен находиться курсор, вам нужно увидеть, находитесь ли вы на нечетной или четной странице меню и в нечетной или четной позиции курсора.
  if (menuPage % 2 == 0) {
    if (cursorPosition % 2 == 0) {  // Если страница меню четная и позиция курсора четная, это означает, что курсор должен находиться в строке 1
      lcd.setCursor(0, 0);
      lcd.write(byte(0));
    }
    if (cursorPosition % 2 != 0) {  // Если страница меню четная, а позиция курсора нечетная, это означает, что курсор должен находиться в строке 2
      lcd.setCursor(0, 1);
      lcd.write(byte(0));
    }
  }
  if (menuPage % 2 != 0) {
    if (cursorPosition % 2 == 0) {  // Если страница меню нечетная, а позиция курсора четная, это означает, что курсор должен находиться на строке 2
      lcd.setCursor(0, 1);
      lcd.write(byte(0));
    }
    if (cursorPosition % 2 != 0) {  // Если страница меню нечетная и позиция курсора нечетная, это означает, что курсор должен находиться в строке 1
      lcd.setCursor(0, 0);
      lcd.write(byte(0));
    }
  }
}


void operateMainMenu() {
  int activeButton = 0;
  while (activeButton == 0) {
    int buttons;

    buttons = lcd.readButtons();

    switch (buttons) {
      case 0: // Когда кнопка возвращает значение 0, никаких действий не предпринимается
        break;
      case 2:  // Этот кейс выполнится, если будет нажата кнопка «вперед»
        buttons = 0;
        switch (cursorPosition) { // Выбранный здесь регистр зависит от того, на какой странице меню вы находитесь и где находится курсор.
          case 0:
            menuItem1();
            break;
          case 1:
            menuItem2();
            break;
          case 2:
            menuItem3();
            break;


        }
        activeButton = 2;
        mainMenuDraw();
        drawCursor();
        break;
      case 8:
        buttons = 0;
        if (menuPage == 0) {
          cursorPosition = cursorPosition - 1;
          cursorPosition = constrain(cursorPosition, 0, ((sizeof(menuItems) / sizeof(String)) - 1));
        }
        if (menuPage % 2 == 0 and cursorPosition % 2 == 0) {
          menuPage = menuPage - 1;
          menuPage = constrain(menuPage, 0, maxMenuPages);
        }

        if (menuPage % 2 != 0 and cursorPosition % 2 != 0) {
          menuPage = menuPage - 1;
          menuPage = constrain(menuPage, 0, maxMenuPages);
        }

        cursorPosition = cursorPosition - 1;
        cursorPosition = constrain(cursorPosition, 0, ((sizeof(menuItems) / sizeof(String)) - 1));

        mainMenuDraw();
        drawCursor();
        activeButton = 2;
        break;
      case 4:
        buttons = 0;
        if (menuPage % 2 == 0 and cursorPosition % 2 != 0) {
          menuPage = menuPage + 1;
          menuPage = constrain(menuPage, 0, maxMenuPages);
        }

        if (menuPage % 2 != 0 and cursorPosition % 2 == 0) {
          menuPage = menuPage + 1;
          menuPage = constrain(menuPage, 0, maxMenuPages);
        }

        cursorPosition = cursorPosition + 1;
        cursorPosition = constrain(cursorPosition, 0, ((sizeof(menuItems) / sizeof(String)) - 1));
        mainMenuDraw();
        drawCursor();
        activeButton = 2;
        break;
    }
  }
}


// Если для более чем одного пункта меню есть общие инструкции по использованию, вы можете вызвать эту функцию из подменю
// меню, чтобы сделать работу немного проще. Если у вас нет общих инструкций или слов в нескольких меню
// Я бы просто удалил эту пустоту. Вы также должны удалить вызовы функции drawInstructions() из функций подменю.
void drawInstructions() {
  lcd.setCursor(0, 1); // Устанавливаем курсор на нижнюю строку
  lcd.print("Use ");
  lcd.print(byte(1)); // Стрелка вверх
  lcd.print("/");
  lcd.print(byte(2)); // Кнопка "Стрелка вниз
  lcd.print(" buttons");
}

void menuItem1() { // Функция выполняется, когда вы выбираете первый пункт главного меню
  int activeButton = 0;

  lcd.clear();
  lcd.setCursor(3, 0);
  lcd.print("Sub Menu 1");

  while (activeButton == 0) {
    int buttons;

    buttons = lcd.readButtons();
    switch (buttons) {
      case 16:  // Этот случай будет выполнен, если будет нажата кнопка «назад»
        buttons = 0;
        activeButton = 1;
        break;
    }
  }
}

void menuItem2() { // Функция выполняется, когда вы выбираете второй элемент главного меню
  int activeButton = 0;

  lcd.clear();
  lcd.setCursor(3, 0);
  lcd.print("Sub Menu 2");

  while (activeButton == 0) {
    int buttons;

    buttons = lcd.readButtons();
    switch (buttons) {
      case 16:  // Этот случай будет выполнен, если будет нажата кнопка «назад»
        buttons = 0;
        activeButton = 1;
        break;
    }
  }
}

void menuItem3() { // Функция выполняется, когда вы выбираете третий элемент главного меню
  int activeButton = 0;

  lcd.clear();
  lcd.setCursor(3, 0);
  lcd.print("Sub Menu 3");

  while (activeButton == 0) {
    int buttons;

    buttons = lcd.readButtons();
    switch (buttons) {
      case 16:  // Этот случай будет выполнен, если будет нажата кнопка «назад»
        buttons = 0;
        activeButton = 1;
        break;
    }
    }
  }

Благодарю вас за помощь и надеюсь, что она будет полезна другим

, 👍1


1 ответ


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

3

Ключевым моментом является то, что код меню никогда не должен ждать, как это делает функция operateMainMenu(). Вместо этого он должен проверить кнопки, быстро выполнить требуемую задачу, если таковая имеется, и вернуться. Это означает, что он зависит от очень частого вызова, поэтому он может быстро реагировать на кнопки, но также должен быстро возвращаться, когда нечего делать. Обычно эти вызовы выполняются из функции loop().

Посмотрите на пример Arduino «BlinkWithoutDelay». В этом примере все решается путем чтения времени - millis(), где в вашем случае вы будете принимать решение путем тестирования и реагирования на кнопки. Просто помните, никогда не зацикливайтесь на ожидании ввода; просто проверьте его один раз и действуйте в соответствии с ним или просто уходите, если его там нет.

Ваша функция loop() не будет ничего делать, кроме как даст каждой «рабочей» процедуре возможность что-то сделать:

void loop(){
   menuRun();
   dataLogRun();
}

, например. Каждая из этих функций часто получает возможность выполнить одну из своих задач (или нет) и вернуться, так что следующая получает шанс. Подобный цикл выполняется очень часто, когда нечего делать, а это означает, что и функция меню, и функция регистрации данных могут очень быстро реагировать, когда что-то необходимо.

,

Спасибо за ответ! У меня не было времени тестировать, но я понял концепцию. Я отвечу вам, если что-то не сработает., @Vincent

Конечно - и дайте нам знать, как это работает., @JRobert

Я упростил меню (без подменю), и оно в основном работает. Теперь у меня проблема с памятью, сбой ведения журнала SD из-за нехватки памяти (предупреждение IDE при компиляции). При удалении ненужной строки кода (Serial. print) удается открыть и записать текстовый файл, но когда я пытаюсь прочитать текстовый файл на последовательном мониторе, он отображает квадраты?!! Но что касается логики вашего ответа, я понял и смог ее применить. Было бы интересно, если бы я опубликовал свой код?, @Vincent