View Categories

Суперцикл loop()

loop #

Как обсуждалось ранее, функция loop вызывается на всём протяжении работы программы. Как часто она это делает? По сути - с максимальной скоростью, на которую способен процессор. "Вне" этой функции он выполняет некоторые системные задачи, поэтому loop вызывается с чуть меньшей скоростью - порядка десятков и сотен тысяч раз в секунду. "Под капотом" Arduino фреймворка основная программа выглядит примерно так - программа делает какие-то свои дела и вызывает наш loop:

// main.cpp

int main() {
    // системный код
    setup();

    for (;;) {
        // системный код
        loop();
    }
}

Если писать без Arduino-фреймворка, то ваша программа скорее всего будет выглядеть именно так - сначала код, выполянемый при запуске, затем суперцикл.

Одна задача #

В однозадачной программе всё довольно просто - действия выполняются по порядку и не мешают друг другу. В такой программе можно использовать простые, удобные и понятные блокирующие конструкции: delay, циклы с задержками, циклы ожидания - всё то, что нельзя использовать в многозадачной программе с суперциклом. Более того - если переходить на операционную систему, то там в задаче тоже можно всё это использовать и не задумываться об остальных задачах - это забота ОС, она позволяет писать более простой код, который будет работать.

Задержка #

Например, классическое мигание светодиодом через задержку delay() - задержка тормозит выполнение программы, чтобы задать нужную скорость переключения светодиода:

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
}

void blinkLED() {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(500);
    digitalWrite(LED_BUILTIN, LOW);
    delay(500);
}

void loop() {
    blinkLED();
}

Задержка позволяет сделать программу простой и понятной - она работает последовательно: так, как написана.

Ожидание #

Или опрос кнопки (кнопка подключена на D3 и GND), в котором нужно поймать сам факт нажатия: например дождаться его в цикле, просигналить и точно так же дождаться отпускания:

#define BTN_PIN 3

void setup() {
    Serial.begin(115200);
    pinMode(BTN_PIN, INPUT_PULLUP);
}

void checkButton() {
    while (digitalRead(BTN_PIN));       // пока кнопка не нажата
    Serial.println("Кнопка нажата");

    while (!digitalRead(BTN_PIN));      // пока кнопка нажата
    //Serial.println("Кнопка отпущена");
}

void loop() {
    checkButton();
}

Аналогично задержке, цикл ожидания позволяет сделать программу простой и понятной - она работает последовательно.

Но если мы захотим соединить две этих задачи, то ничего не получится - кнопка будет мешать светодиоду.

// ...

void loop() {
    blinkLED();
    checkButton();
}

Даже если получится решить ситуацию с кнопкой - ей начнёт мешать светодиод: пока программа ждёт задержку светодиода - можно пропустить нажатие кнопки!

Вычисления #

Следующий момент - долгие сложные вычисления. Начнём с простого - будем увеличивать число с шагом 1 и выводить его в порт:

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

int x = 0;

void loop() {
    x++;
    Serial.println(x);
}

Если открыть монитор порта - можно увидеть, как с огромной скоростью появляются числа. В данном случае вывод в порт будет занимать больше времени, чем увеличение числа. Теперь давайте представим, что нам нужно 500 раз вычислить синус. Чтобы компилятор не оптимизировал вычисления - считать будем синус случайного числа:

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

void calcSin() {
    float x = 0;

    for (int i = 0; i < 500; i++) {
        x += sin(random(100));
    }

    Serial.println(x);
}

void loop() {
    calcSin();
}

Запустите данный пример - новые значения будут появляться в мониторе буквально пару раз в секунду! Вычисления занимают очень много времени, если запустить эту задачу одновременно со светодиодом или кнопкой - всё опять же сломается.

Несколько задач #

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

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

В следующих уроках этой темы рассмотрим типовые конструкции и алгоритмы, позволяющие писать асинхронный код для суперцикла и обходить блокирующие участки. Код станет сложнее и объёмнее - но это плата за универсальность и многозадачность:

Тикер #

Во многих библиотеках используется "тикер" - неблокирующая функция, которую нужно вызывать как можно чаще - в loop. Внутри неё реализована вся логика, необходимая для работы библиотеки в условиях суперцикла. Это может быть опрос кнопки, счёт времени или проверка соединений клиентов с вебсервером. Например, стандартный Serial и его метод проверки входящих данных:

void loop() {
    if (Serial.available()) {
        // данные доступны
    }
}

Таким образом, в рамках суперцикла вся программа может состоять из набора таких тикеров в loop - они не будут мешать друг другу.

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

Выбор подхода (шуточный) #

Видео #

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

4 1 голос
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх