0

I have a project with some NeoPixels and other LEDs and an Arduino UNO. Once switched on, the defaultLEDAnimation with colors and blinking, etc. starts.

I want to trigger some other animations, so I connected two buttons to the Arduino. Whenever you press button1, the Arduino should stop everything it is doing and run animation1. The same goes for button2. if you press it, the Arduino should run animation2. All animations are quite complex and run for several seconds and even minutes.

Now for the challenge: How do I use the two buttons to immediately stop every animation happening and display the appropriate animation?

The tried using interrupts and connected the two buttons to pin 2 and 3. However, when I press the buttons, the animation starts after several seconds delay. Is there any way to restart the loop function immediately after the interrupt has been made? Or is there another way to trigger my animations with those two buttons?

The code looks something like this:

#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);
    }
}

The function doAnimation1 is just an example. It will be much more complex.

5
  • 2
    Make the animation functions non-blocking, so that you can react immediately to the buttons. You don't even need interrupts for this. Please show us an example for the animation functions
    – chrisl
    Commented Aug 16, 2019 at 19:06
  • You have to show us the animation routines, that's where the magic has to take place. Restarting the loop() is not the way something like that is implemented. You probably have another (blocking) loop in the animations and that's where you have to define an exit clause which has to by triggered by the corresponding button press.
    – Sim Son
    Commented Aug 16, 2019 at 19:24
  • The clean solution, as explained in Duncan C's answer, is to make the animation code non blocking. If this is too inconvenient, you may try to longjmp() directly from the ISR to the main loop. Commented Aug 16, 2019 at 20:55
  • @EdgarBonet: how exactly do you make the animation code non blocking? Commented Aug 17, 2019 at 11:34
  • See my answer. I outline in some detail how to make the animation code non-blocking.
    – Duncan C
    Commented Aug 20, 2019 at 13:34

2 Answers 2

1

Basically you have to make the animation loops return if the condition is met, for example:

void doAnimation1() {
    for(int i=0; i<10; i++){
        if (digitalRead (button1) == HIGH)  // Time to stop?
           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)  // Time to stop?
           return;
        neoPixelStrip.setPixelColor(neoPixelArray[i], neoPixelStrip.Color(0, 0, 0, 0));
        neoPixelStrip.show();
        delay(100);
    }
}

If you don't want to wait for the 100 ms delay (or the 1000 ms delay) then you could replace that with code that waits for 100 ms to elapse, but also checks the button.

Interrupts are not the way to do it. Interrupts let you briefly stop doing something, do something else, then go back to what you were doing before. They don't "interrupt" your entire workflow.

For example, you might interrupt your movie watching to answer the phone, you don't "interrupt" your movie watching to go out to dinner with a friend. That is just a case of doing a different thing.

3
  • I thought I can avoid asking for the buttonState in every animation step. That makes things very complex. Commented Aug 17, 2019 at 11:33
  • Well, most of that code of yours could be put into a function. For example, number of loops, starting colour, ending colour, delay inbetween. Then that function is the one that decides whether to stop or not.
    – Nick Gammon
    Commented Aug 17, 2019 at 12:20
  • I implemented it exactly like that. Thanks. Commented Aug 17, 2019 at 12:41
1

"How to interrupt the loop function and restart it?"

You don't. That's not how you write Arduino code. You need to have your loop function call a series of non-blocking functions that check to see if it's time to do something, do a very small bit of work if it is time, or just return if it's not time yet.

What I've done is to create a class ArduinoObject that has a setup function, a start function, a stop function, a loop function, and a boolean property running. My main program has a global variable that holds an array of ArduinoObjects.

Every time through the loop, I check all the ArduinoObjects in the array, and call their loop functions.

The first thing each object does is check its running flag. If running==false, it exits immediately.

Each object uses unsigned long values to track the next millis() time count when it should do something.

Let's say the object does an animation, so it has an unsigned long nextAnimationStepTime. In the loop() function, if nextAnimationStepTime<millis(), it doesn't do anything. If instead nextAnimationStepTime>=millis(), the object advances the animation to the next step, and sets nextAnimationStepTime = millis() + animationStepDuration. That way, next time the object is called, it will wait until it's time to advance the animation yet another step.

Each object is written to only do a tiny bit of work on each pass through the loop, and return as quickly as possible.

With this design, I can add an arbitrary number of objects/tasks to my loop, and all of them get attention on every pass through the loop. Most objects simply decide they're not running, or it's not time to do anything, and return.

You can then add control logic that starts and stops individual objects based on things like button presses.

With this approach you'd have your button 1 press stop animation 2 if it was running and run animation 1. Conversely, pressing button 2 would stop animation 1 if

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.