My idle loop doesn't stop when i need it to

Hi,
I am in need of assistance with some code for my custom lightsaber lights. Specifically I am trying to code an idle "breathing" animation where the brightness slowly fades up and down until the switch state is detected as off. What I want to happen is for the idle loop to stop running wherever it is and play the off animation as soon as the switch is off. What currently happens is when I turn the switch off, it will only play the off animation AFTER the idle loop has finished. I am quite inexperienced with coding in general(I taught myself all this in like 2 weeks) so any help would be greatly appreciated.

Here's the code(I am using Tinkercad's virtual circuit design btw):

#include <Adafruit_NeoPixel.h>


#define LED_PIN 2 //Output pin
#define LED_COUNT 60

int delayval = 25;// Delay in ticks(1000ticks = 1 sec)
int colorRed = 0;
int colorGreen = 0;
int colorBlue = 0;
int switch_pin = 9;
int switch_state;
int l = 0;

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// NEO_GRB means how it will send the color signals, green, red, and finally blue. You may need to change it.

void setup() {
  strip.begin(); // Start Strip
  strip.show(); // Clears out the colors from the previous program
  
  pinMode(LED_BUILTIN, OUTPUT); // Activates built-in LED to confirm code is working
  
}

void loop() {
  onOff();//detects whether switch is on or off and runs on and off animations
  pulse();//pulse/breathing animation.
  digitalWrite(LED_BUILTIN, HIGH);//test to make sure the board is running correctly
}

void onOff() {
  
  switch_state = digitalRead(switch_pin); //tells whether switch is ON or OFF
  
  if (switch_state == HIGH) {
    
  	for (l=0; l < LED_COUNT; l++) {//if switch is on lights activate in an up pattern
      colorRed = 255;
      
    
      strip.setPixelColor(l, strip.Color(colorRed, 0, 0));// Set color RGB
    
      strip.show();// This makes the color actually appear.
    
      
    }
    }
    if (switch_state == LOW) {//if switch is off lights activate in a down pattern
    
    	for (l=LED_COUNT; l >= 0; l--) {
      		colorRed = 0;
      
      		strip.setPixelColor(l, strip.Color(colorRed, 0, 0));
      
      		strip.show();
      
      		
    	}
  	}
  
}

void pulse() {
  if (switch_state != LOW) {
    
     for (colorRed=255; colorRed > 5; colorRed -=1) {// brings red value from 255 down gradually to 5
      
      for (int i=0; i < strip.numPixels(); i++) {
          strip.setPixelColor(i, colorRed, colorGreen, colorBlue);
        
      }
        strip.show();
        delay(delayval);
    
     }
      for (colorRed=5; colorRed < 255; colorRed +=1) {//brings red value from 5 up gradually to 255
      
       for (int i=0; i < strip.numPixels(); i++) {
            strip.setPixelColor(i, colorRed, colorGreen, colorBlue);
        
        }
        strip.show();
        delay(delayval);
     }
  }
}
  
  

You need to learn how to time events without using delay(). This tutorial explains how that works:
https://www.baldengineer.com/blink-without-delay-explained.html

I am in 100 percent agreement with @jremington who points out the path to really being able to make these small machines do what we want when we want it.

On the other hand, at the level of coding you've managed to teach yourself you may enjoy seeing and figuring out how a crude but effective hack might be what you need to accomplish your current goal and get out to play with your toy.

Start here and read up and down the thread:

Again, not the crutch you wanna walk around needing for the rest of your life, just a demo of how things can be done when you must or don't know better, yet.

HTH

a7

As it turns out, the delay is useless anyways so that isn't the problem.

The delay is useless anyways so that isn't the problem.

This

int delayval = 25;// Delay in ticks(1000ticks = 1 sec)

looks like it would control the speed of those loops. What happens when you set it to zero?

At zero, the speed is controlled entirely by how fast the real code in the loop runs; the only significant time is in the show() calls…

Either way, you are back to the advice which is to learn how to code so that each step anything takes is very small, and things go forward by logic that repeatedly takes such steps, which gives it the opportunity to manage many such little stepping things, one of which might be to watch buttons for inputs that woukd change which steps and whether even to step.

The general idea is called a "finite state machine", google

Arduino finite state machine

and poke around a bit. Life changing.

a7

I built your project and did find the delay() to change the fade up and down rate, and that it was amenable to interruption with the myDelay() hack.

Your onOff() function runs so fast no one will see it trying to do anything. To do what it looks like you are aiming for will def require some more thinking on your part - the sweep off, it it comes in the middle of the fade, should start from the current brightness level, otherwise as you can see the sweep first sets all the pixels to bright red.

Here's a link, play with it yourself.

As it goes with hacks, if you try using myDelay() in onOff() you will be the victim of switch bouncing, Fixing it will make the hole mess uglier still.

The code, 98.2 percent yours.
# include <Adafruit_NeoPixel.h>

# define LED_PIN 2 //Output pin
# define LED_COUNT 25

int delayval = 30; // 25;// Delay in ticks(1000ticks = 1 sec)
int colorRed = 0;
int colorGreen = 0;
int colorBlue = 0;
int switch_pin = 9;
int switch_state;
int l = 0;

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// NEO_GRB means how it will send the color signals, green, red, and finally blue. You may need to change it.

//...
int buttonPin = 7;
bool myDelay(unsigned long milliseconds)
{
//... uncomment to use real delay
// delay(milliseconds);
//  return false;

  unsigned long startTime = millis();

  while (millis() - startTime <= milliseconds) {
    if (digitalRead(buttonPin) == LOW)
      return true;
  }

  return false;
}

void setup() {
  Serial.begin(115200);
  Serial.println("\nNews, Weather and Sports!\n");

  strip.begin(); // Start Strip
  strip.show(); // Clears out the colors from the previous program
  
  pinMode(LED_BUILTIN, OUTPUT); // Activates built-in LED to confirm code is working

  pinMode(switch_pin, INPUT_PULLUP);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  static long counter;
  Serial.print("loop... ");
  Serial.println(counter);
  counter++;

  Serial.println("      gonna onOff");  
  onOff();//detects whether switch is on or off and runs on and off animations

  Serial.println("             gonna pulse"); 
  pulse();//pulse/breathing animation.

  digitalWrite(LED_BUILTIN, HIGH);//test to make sure the board is running correctly
}

void onOff() {
  switch_state = digitalRead(switch_pin); //tells whether switch is ON or OFF
  
  if (switch_state == HIGH) {
    
  	for (l=0; l < LED_COUNT; l++) {//if switch is on lights activate in an up pattern
      colorRed = 255;
      
      strip.setPixelColor(l, strip.Color(colorRed, 0, 0));// Set color RGB

      strip.show();// This makes the color actually appear.
    }
  }
  
  if (switch_state == LOW) {//if switch is off lights activate in a down pattern
  
    for (l=LED_COUNT; l >= 0; l--) {
        colorRed = 0;
    
        strip.setPixelColor(l, strip.Color(colorRed, 0, 0));
   
        strip.show();
    }
  }
}

void pulse() {
  if (switch_state != LOW) {
    
     for (colorRed=255; colorRed > 5; colorRed -=1) {// brings red value from 255 down gradually to 5
      
      for (int i=0; i < strip.numPixels(); i++) {
          strip.setPixelColor(i, colorRed, colorGreen, colorBlue);
        
      }
        strip.show();
        if (myDelay(delayval)) return;
    
     }
      for (colorRed=5; colorRed < 255; colorRed +=1) {//brings red value from 5 up gradually to 255
      
       for (int i=0; i < strip.numPixels(); i++) {
            strip.setPixelColor(i, colorRed, colorGreen, colorBlue);
        
        }
        strip.show();
        if (myDelay(delayval)) return;
     }
  }
}

I would start over once you have learned more about this kind of coding challenge.

I added the button to interrupt, I see how you might want the switch to work but can't invest any time getting that to be the one switch that stops and starts the fade up/down. It's your trip, just decide whether to hack this into existence or take a time out and attack it again with better methods.

a7

Your nested for-loops

needs a long time to finish.
If you want immidiate reaction you have to change to non-blocking coding.
The basic concept is that all functions like your pulse()-function are called repeatedly at high speed. And with each call do one single proceeding step.
This tutorial has some pictures to illustrate it.

While I agree 100 percent with the sentiment of this remark, it simply is not true.

And further while it would be best to learn how to do this the "right" way, it is entirely possible to do with simple hacks, even if it makes some people heart attack.

I'm not clear what the OP @amaz0ns_al3xa is going for, but here's that same hacky simulation with a few more adjustments.

The switch turns on and off the fading. The fading starts with all the pixels coming on one by one to bright red. The fading ends when the switch is returned to the off position, at which point the pixels are extinguished one by one.


There is one remaining not-so-subtle detail in that I used wokwi's magic bounceless switches. Fixing it to work IRL is left as an exercise I hope no one wastes time on, although I reserve the right to do. :expressionless:

a7

Holy crap you did it! I couldn't figure out how to get the off animation to happen instantly. Imma study your code for the next hour lol.

The debouncing may show up: I had it working but occasionally it woukd ramp bright red before ramping off, and changing to the magic switches fixed it. It was a quick test of my seat of the pants sense of why it was behaving inconsistently.

There are software solutions, but you can do it in hardware.

With the switch wired between the input pin and ground, a 0.1 uF ceramic capacitor of the most garden variety will fix you up. Wire it in parallel to the switch contacts. I can explain why, sort of.

I meant to try it IRL before saying, also a friend in the Umbrella Academy has suggested a not-too- ugly software fix I want to try.

a7

im sorry, umbrella academy? also i already have most of the hardware: switches, arduino nano, solder kit, breadboard, wires and LEDs so im gonna try to test it myself soon

Sry, my crew at the beach.

a7

The trick is making the delay conditional. myDelay checks your switch while delaying:

//...
bool myDelay(unsigned long milliseconds)
{
//... uncomment to use real delay
// delay(milliseconds);
//  return false;

  unsigned long startTime = millis();

  while (millis() - startTime <= milliseconds)
    if (digitalRead(switch_pin) == LOW)
      return true;

  return false;
}

and then your code that uses it aborts early with "return" if it gets the "true":

     for (colorRed=255; colorRed > 5; colorRed -=1) {// brings red value from 255 down gradually to 5
      
      for (int i=0; i < strip.numPixels(); i++) {
          strip.setPixelColor(i, colorRed, colorGreen, colorBlue);
        
      }
        strip.show();
        if (myDelay(delayval)) return;
    
     }

Specialized conditional delays like this do work, but as you attempt more complex programs, the interactions between the for() loops and their specialized delays becomes more fragile and hard to manage or debug. For managing more complex programs, writing the components so that they can cooperate with any number of other components without monopolizing the processor ends up being much simpler. For instance, if you were trying to program two independent simultaneous strips, a conditional delay for one strip won't be able to share processor time nicely with a conditional delay in the other strip.

Folks who commonly program Arduinos to do more than one thing at a time, start from the cooperative perspective and see your problem as two separate components: "checking the inputs" and "processing the appropriate animation".

If you wrote your strip-pulsing routine to share time nicely and do only a millisecond's worth of animation at a time, (per @StefanL38's "NON-blocking" advice), they could take turns with each other:

void loop(){
    readInputSwitches();
    animateStripOneForATurn();  
    //animateStripTwoForATurn();  // other things
    // ...
}

Customized conditional delay routines can work for simple problems, such as doing a thing or not doing that thing. But if your program might ever develop into doing more than one thing at a time, it is good to plan for cooperation from the beginning.

What @DaveX said ^ 3.

I might say I am in 100 percent agreement. :wink:

a7