Обход одного "временного" потока

У меня есть таймер, который выводится на дисплей I2C, основной код ведёт его отсчёт до нуля. Логика кода естественно заключается в том что с задержкой в секунду выводится на экран seconds--; Но вот эта одна секунда мешает считывание кнопки с панели, а мне надо чтобы вот эта секунда ожидания не блокировала код, я не знаю как боле понятно объяснить. Я может быть и попробовал бы колхозить через 2 потока, но в ардуино их не предусмотрено.

Подкиньте идеи

while (minutes >= 1) {

  char key = keypad.getKey();
  if (key) {
    Serial.print(key);
    Serial.print(" ");
    if (key >= '1' && key <= '9') {
      int digit = key - '0';
    }
  }

  lcd.setCursor(4, 1);

  lcd.print(minutes <= 10 ? "0" : "");
  lcd.print(minutes - 1);
  lcd.print(" :: ");

  lcd.print(seconds < 10 ? "0" : "");
  lcd.print(seconds);

  delay(1000);

  seconds--;

  if (seconds < 0) {
    seconds = 59;
    minutes--;
  }
}

Ответы (1 шт):

Автор решения: Герман Борисов

В таких случаях делается цикл опроса и ожидания, аналогичный главной оконной функции в программах для Windows.

Организуется он как бесконечный цикл, в котором подряд идут несколько блоков вида: Если условие N, вызвать функцию N. И никаких пауз в теле этого цикла.

В случае с arduino сам цикл можно не организовывать, так как функция loop() и так является телом бесконечного цикла. И лучше использовать её, так как между вызовами loop() происходит внутренняя работа с Serial.

Теперь о том как проверить условие «с прошлого срабатывания прошло определённое время» (в вашем случае секунда).

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

Также где-нибудь в цикле создаём локальную переменную «текущее время», и каждую итерацию пишем в неё значение функции millis()

И условие «с прошлого срабатывания прошло определённое время» превращается в «текущее время больше или равно времени ожидаемого следующего срабатывания»

Примерно так:

unsigned long next_second;
unsigned long current_time;

loop() {
  current_time = millis();

  char key = keypad.getKey();
  if (key) {
    Serial.print(key);
    Serial.print(" ");
    if (key >= '1' && key <= '9') {
      int digit = key - '0';
    }
  }
  
  if (minutes >= 1 && next_second <= current_time) {
    lcd.setCursor(4, 1);

    lcd.print(minutes <= 10 ? "0" : "");
    lcd.print(minutes - 1);
    lcd.print(" :: ");

    lcd.print(seconds < 10 ? "0" : "");
    lcd.print(seconds);

    // никакого delay !!!!
    // вместо него вычисляем время следующего срабатывания
    next_second = next_second + 1000;

    seconds--;

    if (seconds < 0) {
      seconds = 59;
      minutes--;
    }
  }
}

Значение переменной next_second инициализировать текущим временем + 1000 в тот же момент, когда устанавливаете новое значение для minutes.

Кроме того, что работа с кнопками не будет зависать, этот способ имеет ещё два заметных плюса:

  1. так как delay() у вас даёт паузу не меньше чем на секунду, а вывод на экран тоже занимает время, то у вашего скетча время постепенно уплывает
  2. Если пользователь будет непрерывно жать на кнопку больше 2 секунд, то мой скетч выполнит seconds-- для каждой пропущенной секунды.

Но есть и серьёзный минус: значение переменных current_time и next_second переполнятся примерно за 2 месяца, и проверка next_second <= current_time может сработать некорректно . Если предполагается непрерывная работа, придётся усложнить скетч.

→ Ссылка