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.
2 Answers 2
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.
-
I thought I can avoid asking for the buttonState in every animation step. That makes things very complex.WalterBeiter– WalterBeiter08/17/2019 11:33:43Commented 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.08/17/2019 12:20:08Commented Aug 17, 2019 at 12:20
-
I implemented it exactly like that. Thanks.WalterBeiter– WalterBeiter08/17/2019 12:41:02Commented Aug 17, 2019 at 12:41
"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 ArduinoObject
s.
Every time through the loop, I check all the ArduinoObject
s 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
longjmp()
directly from the ISR to the main loop.