Как прервать функцию цикла и перезапустить ее?

У меня есть проект с несколькими NeoPixels и другими светодиодами и Arduino UNO. После включения запускается defaultLEDAnimation с цветами, мерцанием и т. д.

Я хочу запустить некоторые другие анимации, поэтому я подключил две кнопки к Arduino. Всякий раз, когда вы нажимаете button1, Arduino должен останавливать все, что он делает, и запускать animation1. То же самое касается button2. если вы нажмете ее, Arduino должен запустить animation2. Все анимации достаточно сложные и выполняются несколько секунд и даже минут.

Теперь задача: как использовать две кнопки, чтобы немедленно остановить каждую анимацию и отобразить соответствующую анимацию?

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

Код выглядит примерно так:

#define button1 2
#define button2 3

volatile bool shouldPerformButton1Action = false;
volatile bool shouldPerformButton2Action = false;

void setup() {
    attachInterrupt(digitalPinToInterrupt(button1), button1Pressed, RISING);
    attachInterrupt(digitalPinToInterrupt(button2), button2Pressed, RISING);
}

void loop() {
    if (shouldPerformButton1Action) {
        doAnimation1();
        shouldPerformButton1Action = false;
    } else if (shouldPerformButton2Action) {
        doAnimation2();
        shouldPerformButton2Action = false;
    } else {
        runDefaultAnimation();
    }
}

void button1Pressed() {
    shouldPerformButton1Action = true;
}

void button2Pressed() {
    shouldPerformbutton2Action = true;
}


void doAnimation1() {
    for(int i=0; i<10; i++){
        neoPixelStrip.setPixelColor(neoPixelArray[i], neoPixelStrip.Color(255, 0, 0, 255));
        neoPixelStrip.show();
        delay(100);
    }
    delay(1000);
    for(int i=0; i<10; i++){
        neoPixelStrip.setPixelColor(neoPixelArray[i], neoPixelStrip.Color(0, 0, 0, 0));
        neoPixelStrip.show();
        delay(100);
    }
}

Функция doAnimation1 является лишь примером. Это будет намного сложнее.

, 👍0

Обсуждение

Сделайте функции анимации неблокирующими, чтобы можно было сразу реагировать на кнопки. Для этого даже не нужны прерывания. Пожалуйста, покажите нам пример для функций анимации, @chrisl

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

Чистое решение, как объяснено в ответе Дункана С, состоит в том, чтобы сделать код анимации _неблокирующим_. Если это слишком неудобно, вы можете попробовать [longjmp() напрямую из ISR в основной цикл](https://arduinoprosto.ru/q/23129)., @Edgar Bonet

@EdgarBonet: как именно сделать код анимации неблокирующим?, @WalterBeiter

Смотрите мой ответ. Я подробно описываю, как сделать код анимации неблокирующим., @Duncan C


2 ответа


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

1

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

void doAnimation1() {
    for(int i=0; i<10; i++){
        if (digitalRead (button1) == HIGH)  // Время остановиться?
           return;
        neoPixelStrip.setPixelColor(neoPixelArray[i], neoPixelStrip.Color(255, 0, 0, 255));
        neoPixelStrip.show();
        delay(100);
    }
    delay(1000);
    for(int i=0; i<10; i++){
        if (digitalRead (button1) == HIGH)  // Время остановиться?
           return;
        neoPixelStrip.setPixelColor(neoPixelArray[i], neoPixelStrip.Color(0, 0, 0, 0));
        neoPixelStrip.show();
        delay(100);
    }
}

Если вы не хотите ждать задержки в 100 мс (или задержки в 1000 мс), вы можете заменить ее кодом, который ожидает истечения 100 мс, но также проверяет кнопку.

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

Например, вы можете прервать просмотр фильма, чтобы ответить на звонок. Вы не прерываете просмотр фильма, чтобы пойти поужинать с другом. Это всего лишь случай, когда нужно сделать что-то другое.

,

Я думал, что смогу не запрашивать состояние кнопки на каждом этапе анимации. Это делает вещи очень сложными., @WalterBeiter

Что ж, большая часть вашего кода может быть помещена в функцию. Например, количество петель, начальный цвет, конечный цвет, задержка между ними. Тогда эта функция решает, останавливаться или нет., @Nick Gammon

Я реализовал именно так. Спасибо., @WalterBeiter


1

"Как прервать функцию цикла и перезапустить ее?"

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

Что я сделал, так это создал класс ArduinoObject, который имеет функцию настройки, функцию запуска, функцию остановки, функцию цикла и логическое свойство running. В моей основной программе есть глобальная переменная, содержащая массив ArduinoObject.

Каждый раз в цикле я проверяю все ArduinoObject в массиве и вызываю их циклические функции.

Первое, что делает каждый объект, это проверяет свой флаг running. Если running==false, он немедленно завершает работу.

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

Допустим, объект выполняет анимацию, поэтому у него есть длинное беззнаковое значение nextAnimationStepTime. В функции loop(), если nextAnimationStepTime<millis(), она ничего не делает. Если вместо этого nextAnimationStepTime>=millis(), объект переводит анимацию на следующий шаг и устанавливает nextAnimationStepTime = millis() + animationStepDuration. Таким образом, при следующем вызове объекта он будет ждать, пока не придет время продвинуть анимацию еще на один шаг.

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

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

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

При таком подходе вы должны нажать кнопку 1, чтобы остановить анимацию 2, если она запущена, и запустить анимацию 1. И наоборот, нажатие кнопки 2 остановит анимацию 1, если

,