Переключение задач

Важнейшим инструментом по организации логики работы программы является так называемый конечный автомат, он же машина состояний (англ. State Machine) – значение, которое имеет заранее известный набор состояний и программа работает по-разному на каждом из них. Звучит сложно, но на самом деле речь идёт об операторе switch (урок про оператор выбора) и переменной, которая переключается кнопкой или по таймеру. Значение этой переменной – код или номер состояния, задачи, режима, эффекта – в данном случае это всё синонимы. Например:

if (клик по кнопке 1) mode++;
if (клик по кнопке 2) mode--;

switch (mode) {
  case 0:
    // задача 0
    break;
  case 1:
    // задача 1
    break;
  case 2:
    // задача 2
    break;
  // .........
}
Переменная режима должна быть знаковой (int) чтобы избежать переполнения в обратную сторону при получении отрицательного значения!!!

Таким образом организуется выбор и выполнение выбранных участков кода, переключение режимов работы, эффектов и так далее.

Переключение режима


Переключение переменной mode может быть сделано не просто так, как в примере выше, тут есть варианты:

  • Ограничить диапазон переменной mode по минимальному номеру задачи (обычно 0) и максимальному (количество задач - 1).
  • Сделать переключение с последней задачи на первую и наоборот, т.е. “закольцевать” изменение.

В рассмотренных ниже примерах MODESколичество задач, то есть при 10 задачах переменная будет принимать значения от 0 до 9.

Ограничение


if (mode < MODES - 1) mode++;   // на увеличение
if (mode > 0) mode--;           // на уменьшение

Переключение


if (++mode >= MODES) mode = 0;     // на увеличение
if (--mode < 0) mode = MODES - 1;  // на уменьшение

Рассмотрим несколько готовых примеров на базе библиотеки GyverButton:

Переключение режимов работы кнопкой. Постоянное выполнение
#define BTN_PIN 3    // кнопка подключена сюда (BTN_PIN --- КНОПКА --- GND)
#define MODE_AM 5    // количество режимов

#include <EncButton.h>
// моя библиотека для более удобной работы с кнопкой
// скачать мождно здесь https://github.com/GyverLibs/EncButton

EncButton<EB_TICK, BTN_PIN> btn;

byte mode = 0;       // переменная режима

void setup() {
  Serial.begin(9600);
}

void loop() {
  btn.tick();
  if (btn.click()) {
    if (++mode >= MODE_AM) mode = 0;
  }

  // свитч крутится в цикле loop и задачи постоянно вызываются
  switch (mode) {
    case 0: task_0();
      break;
    case 1: task_1();
      break;
    case 2: task_2();
      break;
    case 3: task_3();
      break;
    case 4: task_4();
      break;
  }
}

// наши задачи
void task_0() {
  Serial.println("Task 0");
}
void task_1() {
  Serial.println("Task 1");
}
void task_2() {
  Serial.println("Task 2");
}
void task_3() {
  Serial.println("Task 3");
}
void task_4() {
  Serial.println("Task 4");
}
Переключение режимов работы кнопкой. Однократное выполнение
#define BTN_PIN 3    // кнопка подключена сюда (BTN_PIN --- КНОПКА --- GND)
#define MODE_AM 5    // количество режимов

#include <EncButton.h>
// моя библиотека для более удобной работы с кнопкой
// скачать мождно здесь https://github.com/GyverLibs/EncButton

EncButton<EB_TICK, BTN_PIN> btn;

byte mode = 0;       // переменная режима

void setup() {
  Serial.begin(9600);
}

void loop() {
  btn.tick();
  if (btn.click()) {
    if (++mode >= MODE_AM) mode = 0;

    // переключение и вызов происходит только при нажатии
    switch (mode) {
      case 0: task_0();
        break;
      case 1: task_1();
        break;
      case 2: task_2();
        break;
      case 3: task_3();
        break;
      case 4: task_4();
        break;
    }
  }
}

// наши задачи
void task_0() {
  Serial.println("Task 0");
}
void task_1() {
  Serial.println("Task 1");
}
void task_2() {
  Serial.println("Task 2");
}
void task_3() {
  Serial.println("Task 3");
}
void task_4() {
  Serial.println("Task 4");
}

Массив функций


Выше мы рассмотрели переключение задач при помощи оператора switch. Это классическая, громоздкая и всем понятная конструкция. Но что если у нас будет 10 задач, или 50? Например эффекты для светодиодной ленты. Можно вообще избавиться от switch и реализовать переключение задач гораздо более компактно – при помощи массива указателей на функции (урок про указатели).

Рассмотрим пример, в котором задачи переключаются по таймеру с периодом 1 секунда. Функция текущей активной задачи просто вызывается в основном цикле программы.

// задачи, которые будут выполняться
void task1() {
}
void task2() {
}
void task3() {
}

// количество задач
#define T_AMOUNT 3

// массив функций, передаём имена функций задач
void (*func[])() = {task1, task2, task3};

void setup() {
}

uint32_t tmrSwitch;   // таймер
byte task;            // номер текущей задачи

void loop() {
  // таймер
  if (millis() - tmrSwitch >= 1000) {
    tmrSwitch = millis();
    if (++task >= T_AMOUNT) task = 0; // переключаем по кругу
  }

  func[task]();   // вызов текущей функции-задачи
}

И всё! Пример очень легко масштабируется для любого количества задач, достаточно создать дополнительные функции и поместить их в массив, а также изменить количество задач для переключения.

Флаги


Логические переменные, или флаги, являются очень важным инструментом организации логики работы программы. В глобальном флаге можно хранить “состояние” составляющих программы, и они будут известны во всей программе и во всей же программе могут быть изменены. Немного утрированный пример:

boolean flag = false;

void loop() {
  // если был клик по кнопке, поднять флаг
  if (buttonClick()) flag = true;

  if (flag) {
    // какой-то код
  }
}

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

boolean flag = false;

void loop() {
  // если был клик по кнопке, поднять флаг
  if (buttonClick()) flag = true;

  if (flag) {
    flag = false;
    // выполнится один раз после клика по кнопке
  }
}

Также флаг можно инвертировать, что позволяет генерировать последовательность 10101010 для переключения каких-то двух состояний:

boolean flag = false;

void loop() {
  // допустим, условие выполняется периодчисеки по таймеру
  if (timerElapsed()) {
    flag = !flag; // инвертировать флаг

    // например, нужно передавать в функцию два значения,
    // чередуя их по таймеру
    setSomeValue(flag ? 10 : 200);
  }
}

Флаги – очень мощный инструмент, не забывайте о них!

Видео


Полезные страницы


  • Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
  • Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
  • Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
  • Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
  • Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
  • Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
  • Поддержать автора за работу над уроками
  • Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту (alex@alexgyver.ru)
5/5 - (1 голос)
Назад Многозадачность в Arduino
Вперёд Избавляемся от циклов
Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии