Neopixel in a loop

Hello, I am doing some work with neopixels and could uses some help. the issues I am having is keeping a fade effect running and then having a button interrupt it to change to the next state. The states are separated by breaks and having issues looing with in the break and still being able to control it with a button.
here is my sketch (sorry if its messy relatively new to this)

 #include <stdio.h>
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

#define BUTTON_PIN  4    
#define PIXEL_PIN    6    
#define PIXEL_COUNT 144

Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

bool oldState = HIGH;
int showType = 0;

int waitTime = 1;
int condition=0;

void setup() {
     
    
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  strip.begin();
  strip.show(); 
}

void loop() {
  bool newState = digitalRead(BUTTON_PIN);

  if (newState == LOW && oldState == HIGH) {
    delay(20);
    
    newState = digitalRead(BUTTON_PIN);
    if (newState == LOW) {
      showType++;
      if (showType > 2)
        showType=0;
      startShow(showType);
    }
  }

  oldState = newState;
}
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

void fadeInAndOut(uint8_t red, uint8_t green, uint8_t blue, uint8_t wait) {

  for(uint8_t b=0; b <255; b++) {
     for(uint8_t i=0; i < strip.numPixels(); i++) {
        strip.setPixelColor(i, red*b/255, green*b/255, blue*b/255);
     }
     strip.show();
     delay(wait);
  }

  for(uint8_t b=255; b > 0; b--) {
     for(uint8_t i=0; i < strip.numPixels(); i++) {
        strip.setPixelColor(i, red*b/255, green*b/255, blue*b/255);
     }
     strip.show();
     delay(wait);
  }
}
void startShow(int i) {
  switch(i){
    case 0: colorWipe(strip.Color(0, 0, 0), 0);
            break;
    case 1: 
          colorWipe(strip.Color(0, 0, 150),0);  
             break;
    case 2: while (condition < 20) { 
              fadeInAndOut(0, 0, 255, waitTime); // Blue
            condition ++;

            if (condition >15) {
            break;     
     }
    }
   }
  }

If anyone could point me in the right direction or offer advice that would be appreciated!

First off, if you encase your code like this, it come out looking much nicer and easier to read:

```c++
//Your code here
```

One problem I see is that your fade routine is blocking. You have a delay in the fadeinandout function… while the function is delaying, there could be a button press that you are not catching. You should probably catch that button press with an interrupt and set a flag. When the loop runs, check the flag to see if you should switch modes. Something like:

bool switchMode = false;

loop {
  if (switchMode) {
    //Do the mode change.
    switchMode = false;
  }
}

void catchInterrupt {
  switchMode = true;
}

I am also working on a big project (see DotStar Ball on Github) writing animation sequenced for DotStar LED strips (same as NeoPixels functionally but uses a 2-wire SPI interface for faster refreshes.) It’s not quite ready to post as a project for public consumption but it’s already in GitHub. You might want to have a look at some of my code. Specifically, how I have “modes” and the “animations” run based on parameters stored in the Mode. I have a “fade” animations which is mode 11 (look at animations.cpp). The loop in the .ino file shows how to use non-blocking code to update the strip:

loop {
     //Has enough time elapsed between last run? If so, update to the next step in the animation.
     if ( ((millis() - mo.LastRunMillis) > (mo.NextMillis + mo.Delay)) ) {

        bool adv = false;
        //Reset the mode if repeat is enabled.
        if (mo.Complete) { adv = mo.AutoReset(); }

        if (!mo.Complete) {
            Log.warn("Mode Tick @ %u. Step %u. RepeatCount %i", millis(), mo.Step, mo.RepeatCount);
            
            //Process the animation.
            if (mo.Step <= mo.Steps) { a.ProcessMode(&mo); }
            
            //Advance the mode to the next step.
            mo.StepAdvance();
            
            Log.trace("End of Loop. Free memory: %u", System.freeMemory());
            
            //loopDebug = {true, true, true};
            //for(auto &v: loopDebug) { v = true; }
        }
    }
}
1 Like

I’d agree with @ninjatill’s suggestions, but add one point.
When altering global variables inside an ISR, you should mark these variables as volatile

volatile bool switchMode = false;

so that the optimizer doesn’t “optimize” away your actual flag check from loop().

Additionally some simple button debounce may be required to prevent one button press causing multiple triggers.

so if iam understanding right, your addition should be put in to the fadeinandout function. where exactly? or is it a new segment?

Try this code as an example. See how I removed all delays from the fadeInAndOut function and now I use a time comparison in the main loop to see if we should increment/decrement the fade. I use a “step” variable to track where we are in the fade routine (fade up or fade down).

Edit: This won’t work as is. You’ll need to tweak a bit based on @ScruffR’s comments below.

#include <stdio.h>
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

#define BUTTON_PIN  4    
#define PIXEL_PIN    6    
#define PIXEL_COUNT 144

Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

//bool oldState = HIGH; //Don't need since we are using a rising-edge interrupt.
int showType = 0;

int waitTime = 1;
//int condition = 0;  //What was this for again? Don't need it now I think

//Added variables.
int step = 0;  //Used to track fade position.
unsigned long lastMillis = 0;  //Use for a time comparison to see if we should adjust the fade.
volatile bool buttonPushed = false;  //Use to set a flag within the interrupt.
bool runNow = false;  //Used to trigger the show immediately.

void setup() {
    pinMode(BUTTON_PIN, INPUT_PULLUP);
    attachInterrupt(BUTTON_PIN, catchInterrupt, RISING);
    strip.begin();
    strip.show(); 
}

void loop() {
    //Process button pushed events.
    if (buttonPushed) {
        advanceShow();
    }
    
    //Process the next step in the light animation
    if ( (millis() - lastMillis > waitTime || runNow)) {
        runNow = false;
        switch(showType){
            case 0: 
                colorWipe(strip.Color(0, 0, 0), 0);
                break;
            case 1: 
                colorWipe(strip.Color(0, 0, 150),0);  
                break;
            case 2: 
                fadeInAndOut(0, 0, 255, step); // Blue
        }
        lastMillis = millis();
        step++;
        strip.show();
    }
}

void catchInterrupt() {
    delay(20);  //Debounce
    
    bool newState = digitalRead(BUTTON_PIN); //Get pin state after debounce.
    if (newState) { //If button pushed, set advance flag.
        buttonPushed = true;
    }
}

void advanceShow() {
    showType++;  //Advance show.
    if (showType > 2) {  //Keep show within bounds.
        showType=0;
        startShow(showType);
    }
    buttonPushed = false;  //Reset the button pushed variable so we can catch it again later.
    runNow = true;  //Run the show change immediately (Don't use wait delay.)
}

void colorWipe(uint32_t c, uint8_t wait) {
    for(uint16_t i=0; i<strip.numPixels(); i++) {
        strip.setPixelColor(i, c);
        strip.show();
        delay(wait);
    }
}

void fadeInAndOut(uint8_t red, uint8_t green, uint8_t blue, uint8_t step) { //By adding step, this function becomes "state-less" so to speak.
    int subStep = step%255; //Get the position in the fade operation.
    int direction = step/255; //Should we fade up or down?
    
    if (direction == 0) { //Fade Up
        for(uint8_t i=0; i < strip.numPixels(); i++) {
            strip.setPixelColor(i, red*(subStep/255), green*(subStep/255), blue*(subStep/255));
        }
    }
    if (direction ==1) { //Fade Down
        for(uint8_t i=0; i < strip.numPixels(); i++) {
            strip.setPixelColor(i, red*((255-subStep)/255), green*((255-subStep)/255), blue*((255-subStep)/255));
        }
    }
}
1 Like

@ninjatill, delay() is an absolute No-Go in an interrupt. It will most likely cause some SOS panic (you can also not use millis() inside an ISR the common way as they are “frozen” inside).
The debounce should either happen outside the ISR or you need to allow for multiple visits to the ISR but only register relevant ones.

Also the RISING interrupt and INPUT_PULLUP need some more consideration with respect to button bounce, since the combiniation (without bounce) would suggest that you only want to react on button release, but due to bounce you probably will get triggers on button press already and probably at release again.

Also is the delay(wait) inside colorWipe() intended? It could (should?) be removed by adopting a non-blocking approach too.

As a note on integer divisions and precision it would usually be better to first do the multiplications and only at the end perform the division - hence the parenthesis around the division/255 should be dropped in favour of relative precision.

3 Likes

I didn’t fully vet the code. I left some of the OP code untouched. Your comments are valid.

1 Like

ok thanks guys for the help, maybe a little lost right now but hopefully I can figure it out

I hear your pain, switch de-bouncing’s a tricky beast.

If you want to reliably use edge-triggered interrupts then chuck a bit of capacitance across your switches. If you’re using the internal ~=40K pullups, and the switch is direct to ground, then the simplest thing to do will be to put a ~4.7 nF capacitor in parallel with the switch should remove any switch bounce less than 1 millisecond or so. If you’re throwing a big switch that’s bouncing closer to the hundreds of Hz, then a larger cap will have the same effect (say 47 nF for ~10 mSec). You won’t get any lag on a ‘press’ event, since that’s basically instantly dumping the cap, but you’ll get a few milliseconds lag on a ‘release’ event.

In case you haven’t played with them before, here’s the docs link to digital input interrupts on the photon:
https://docs.particle.io/reference/firmware/photon/#attachinterrupt-

2 Likes