Go Down

Topic: Help!-BlinkWithoutDelay plus Multi-switch Debounce plus WS2812 'NeoPixel'  (Read 1 time) previous topic - next topic

Zer0

Hi, so I have made projects with addressable LED strips that would cycle through modes with repeated pushes of a momentary switch pushbutton. Now I want to attach various LED animations one each to separate pushbutton switches. I was able to do so successfully when I used the conventional delay-based LED strip animation functions, but of course those delays mean that unless the function is pretty short and simple, the delays will interfere with responsiveness to the button pushes.
I learned from https://forum.arduino.cc/index.php?topic=191028.0. that the way to proceed is with BlinkWithoutDelay style programming. But when I attempt to fire off my WithoutDelay strip functions in my program, an odd thing happens: the light output doesn't loop through the function, instead each function's output only advances if I repeatedly press the same button. So for example, when I press the button connected to the 'blink the whole strip on and off' function, the lights toggle on or off with each push. Same for the others. If I press the button over and over rapidly I can see the intended light animation sequence advancing very  gradually. It seems like it should be something simple, but I'm just not seeing it so far. Help?!

Forgive me, the code is a bit long, even though I condensed it.
I am using a Nano ATmega328, IDE "Adafruit Arduino 1.0.5"
The circuit is simple: some momentary switches between ground and pullup input pins on the board; plus normal connections to the LED strip.

Code following...

Thanks!!!

larryd

No technical PMs.
If you are asked a question, please respond with an answer.
If you are asked for more information, please supply it.
If you need clarification, ask for help.

Zer0

Code: [Select]
//broken. animation advances only with many repeated button presses.

/*This is adapted by me, ZerO, from
multi switches debounce code: https://gist.github.com/smching/7df5cce818a1aab3cbc3.js  
and code by user drumminhands: https://github.com/drumminhands/drumminhands_neopixel_button_hat
 that he made from Adafruit Strandtest https://github.com/adafruit/Adafruit_NeoPixel
    and Blink without Delay  http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay

Previous version used conventional NeoPixel strandtest animations
and worked fine, but could not handle delays in the fancier
LED effects functions so I'm converting it to use BlinkWithoutDelay effects
*/

//unsigned long currentMillis = 0;  

long previousMillis = 0;        // will store last time LED was updated
int neoPixelToChange = 0; //track which neoPixel to change
int neoPixel_j = 0; //stores values for program cycles
int defaultBrightness = 64;
int ledState = HIGH;

#define DEBOUNCE 5  // how many ms to debounce, 5+ ms is usually plenty
byte buttons[] = {6,8, 9,11,12}; //define the buttons that we'll use.
#define NUMBUTTONS sizeof(buttons) //determine how big the array is

//track if a button is just pressed, just released, or 'currently pressed'
byte pressed[NUMBUTTONS], justpressed[NUMBUTTONS], justreleased[NUMBUTTONS];
byte previous_keystate[NUMBUTTONS], current_keystate[NUMBUTTONS];
#include <Adafruit_NeoPixel.h> //NeoPixel library
#define PIN 5    //pin for WS2812 LED strip data
#define numPixelsInStrip 7  //number of pixels in strip
Adafruit_NeoPixel strip = Adafruit_NeoPixel(numPixelsInStrip, PIN, NEO_GRB + NEO_KHZ800); //Used by NeoPixel Library
    
void setup() {
  strip.begin();
  strip.show();   // initialize all pixels to 'off'  

    strip.setBrightness(32); // initialize brightness

  byte i;
  Serial.begin(9600); //set up serial port
  Serial.print("Button checker with ");
  Serial.print(NUMBUTTONS, DEC);
  Serial.println(" buttons");
  for (i=0; i< NUMBUTTONS; i++) {     // Make input & enable pull-up resistors on switch pins
    pinMode(buttons[i], INPUT);
    digitalWrite(buttons[i], HIGH);
  }
}

void loop() {
  byte thisSwitch=thisSwitch_justPressed(); //For Mulitiple switches debouncing function
  switch(thisSwitch)
  {  
    
    case 0:
      allColor(strip.Color(10,10,10)); //off
      break;
    case 1:
      blinkColor(strip.Color(255,0,0),100); // red
      break;        
    case 2:
      colorWipeCycle(strip.Color(0,255,0),500); // green
      break;
    case 3:
      softBlink(strip.Color(0,0,255),defaultBrightness, 50); // blue
      break;
    case 4:
      strip.setBrightness(defaultBrightness); // initialize brightness
      rainbow(20);
      break;
  
  }
}


//Drumminhands code for NeoPixel sequences without delay: Thanks https://github.com/drumminhands!

// Fill all the dots with one color
void allColor(uint32_t c) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, c);
      strip.show();
  }
} // note for a random color, use:
  // allColor((unsigned long)random(0x01000000)); // random color
  
void blinkColor(uint32_t c, uint8_t wait) {
  unsigned long currentMillis = millis();
 
  if(currentMillis - previousMillis > wait) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;  

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW){
      ledState = HIGH;
      allColor(c);
    } else {
      ledState = LOW;
      allColor(strip.Color(0,0,0)); // off
    }
  }
}  
  
// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  
  unsigned long currentMillis = millis();
  
  //only do this if some of the pixels still need to be lit
  if (neoPixelToChange <= strip.numPixels()){
    
    if(currentMillis - previousMillis > wait) {
      
      // save the last time you changed a NeoPixel
      previousMillis = currentMillis;  
    
      //change a pixel
      strip.setPixelColor(neoPixelToChange, c);
      strip.show();
      neoPixelToChange++;
    }
  }
}

// Fill the dots one after the other with a color
void colorWipeCycle(uint32_t c, uint8_t wait) {
  
  unsigned long currentMillis = millis();
  
  //only do this if some of the pixels still need to be lit
  if (neoPixelToChange <= strip.numPixels()){
    
    if(currentMillis - previousMillis > wait) {
      
      // save the last time you changed a NeoPixel
      previousMillis = currentMillis;  
    
      //change a pixel
      strip.setPixelColor(neoPixelToChange, c);
      strip.show();
      neoPixelToChange++;
      
    }
    
  } else {
    // reset the cycle
    previousMillis = 0;  
    neoPixelToChange = 0;
    allColor(strip.Color(0,0,0));
  }
}

void rainbow(uint8_t wait) {
    
  unsigned long currentMillis = millis();

  if(currentMillis - previousMillis > wait) {
    
    // save the last time you changed a NeoPixel
    previousMillis = currentMillis;

    //change the colors of the pixels
    uint16_t i;

    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+neoPixel_j) & 255));
    }
    strip.show();
    neoPixel_j = (neoPixel_j + 1) % 255; //increment j until all colors are used, then start over
  }
}

void softBlink(uint32_t c, uint8_t brightness, uint8_t wait) {
  
  unsigned long currentMillis = millis();

  if(currentMillis - previousMillis > wait) {
  
    //set the color of all pixels
    allColor(c); // is there a way to set this only once per program ????????????????????
    
    // save the last time you changed a NeoPixel
    previousMillis = currentMillis;
    
    uint16_t i;
    int b = (neoPixel_j * brightness) / brightness;
    strip.setBrightness(b);
    strip.show();
    // sometime figure out how to get this to fade down too instead of just restarting //////////////////////
    neoPixel_j = (neoPixel_j + 1) % brightness;
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}


//Multi switch debounce code:

void check_switches()
{
  static byte previousstate[NUMBUTTONS];
  static byte currentstate[NUMBUTTONS];
  static long lasttime;
  byte index;
  if (millis() < lasttime) {
    // we wrapped around, lets just try again
    lasttime = millis();
  }
  if ((lasttime + DEBOUNCE) > millis()) {
    // not enough time has passed to debounce
    return;
  }
  // ok we have waited DEBOUNCE milliseconds, lets reset the timer
  lasttime = millis();
  for (index = 0; index < NUMBUTTONS; index++) {
    justpressed[index] = 0;       //when we start, we clear out the "just" indicators
    justreleased[index] = 0;
    currentstate[index] = digitalRead(buttons[index]);   //read the button
    if (currentstate[index] == previousstate[index]) {
      if ((pressed[index] == LOW) && (currentstate[index] == LOW)) {
        // just pressed
        justpressed[index] = 1;
      }
      else if ((pressed[index] == HIGH) && (currentstate[index] == HIGH)) {
        justreleased[index] = 1; // just released
      }
      pressed[index] = !currentstate[index];  //remember, digital HIGH means NOT pressed
    }
    previousstate[index] = currentstate[index]; //keep a running tally of the buttons
  }
}
 
byte thisSwitch_justPressed() {
  byte thisSwitch = 255;
  check_switches();  //check the switches &amp; get the current state
  for (byte i = 0; i < NUMBUTTONS; i++) {
    current_keystate[i]=justpressed[i];
    if (current_keystate[i] != previous_keystate[i]) {
      if (current_keystate[i]) thisSwitch=i;
    }
    previous_keystate[i]=current_keystate[i];
  }  
  return thisSwitch;
}

Zer0

Sorry about that, that code I posted was unnecessarily long. Here is a shorter version, with only two simple animation effects:

Code: [Select]
//broken. animation advances only with many repeated button presses.

/*This is adapted by me, ZerO, from
multi switches debounce code: https://gist.github.com/smching/7df5cce818a1aab3cbc3.js 
and code by user drumminhands: https://github.com/drumminhands/drumminhands_neopixel_button_hat
 that he made from Adafruit Strandtest https://github.com/adafruit/Adafruit_NeoPixel
    and Blink without Delay  http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay

Previous version used conventional NeoPixel strandtest animations
and worked fine, but could not handle delays in the fancier
LED effects functions so I'm converting it to use BlinkWithoutDelay effects
*/

//unsigned long currentMillis = 0; 

long previousMillis = 0;        // will store last time LED was updated
int neoPixelToChange = 0; //track which neoPixel to change
int neoPixel_j = 0; //stores values for program cycles
int defaultBrightness = 64;
int ledState = HIGH;

#define DEBOUNCE 5  // how many ms to debounce, 5+ ms is usually plenty
byte buttons[] = {6,8}; //define the buttons that we'll use.
#define NUMBUTTONS sizeof(buttons) //determine how big the array is

//track if a button is just pressed, just released, or 'currently pressed'
byte pressed[NUMBUTTONS], justpressed[NUMBUTTONS], justreleased[NUMBUTTONS];
byte previous_keystate[NUMBUTTONS], current_keystate[NUMBUTTONS];
#include <Adafruit_NeoPixel.h> //NeoPixel library
#define PIN 5    //pin for WS2812 LED strip data
#define numPixelsInStrip 7  //number of pixels in strip
Adafruit_NeoPixel strip = Adafruit_NeoPixel(numPixelsInStrip, PIN, NEO_GRB + NEO_KHZ800); //Used by NeoPixel Library
   
void setup() {
  strip.begin();
  strip.show();   // initialize all pixels to 'off'   

    strip.setBrightness(32); // initialize brightness

  byte i;
  Serial.begin(9600); //set up serial port
  Serial.print("Button checker with ");
  Serial.print(NUMBUTTONS, DEC);
  Serial.println(" buttons");
  for (i=0; i< NUMBUTTONS; i++) {     // Make input & enable pull-up resistors on switch pins
    pinMode(buttons[i], INPUT);
    digitalWrite(buttons[i], HIGH);
  }
}

void loop() {
  byte thisSwitch=thisSwitch_justPressed(); //For Mulitiple switches debouncing function
  switch(thisSwitch)
  { 
   
    case 0:
      allColor(strip.Color(10,10,10)); //off
      break;
    case 1:
      blinkColor(strip.Color(255,0,0),100); // red
      break;       
   
  }
}
//Drumminhands code for NeoPixel sequences without delay: Thanks https://github.com/drumminhands!
// Fill all the dots with one color
void allColor(uint32_t c) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, c);
      strip.show();
  }
}

void blinkColor(uint32_t c, uint8_t wait) {
  unsigned long currentMillis = millis();
 
  if(currentMillis - previousMillis > wait) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;   

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW){
      ledState = HIGH;
      allColor(c);
    } else {
      ledState = LOW;
      allColor(strip.Color(0,0,0)); // off
    }
  }


//Multi switch debounce code:
void check_switches()
{
  static byte previousstate[NUMBUTTONS];
  static byte currentstate[NUMBUTTONS];
  static long lasttime;
  byte index;
  if (millis() < lasttime) {
    // we wrapped around, lets just try again
    lasttime = millis();
  }
  if ((lasttime + DEBOUNCE) > millis()) {
    // not enough time has passed to debounce
    return;
  }
  // ok we have waited DEBOUNCE milliseconds, lets reset the timer
  lasttime = millis();
  for (index = 0; index < NUMBUTTONS; index++) {
    justpressed[index] = 0;       //when we start, we clear out the "just" indicators
    justreleased[index] = 0;
    currentstate[index] = digitalRead(buttons[index]);   //read the button
    if (currentstate[index] == previousstate[index]) {
      if ((pressed[index] == LOW) && (currentstate[index] == LOW)) {
        // just pressed
        justpressed[index] = 1;
      }
      else if ((pressed[index] == HIGH) && (currentstate[index] == HIGH)) {
        justreleased[index] = 1; // just released
      }
      pressed[index] = !currentstate[index];  //remember, digital HIGH means NOT pressed
    }
    previousstate[index] = currentstate[index]; //keep a running tally of the buttons
  }
}
 
byte thisSwitch_justPressed() {
  byte thisSwitch = 255;
  check_switches();  //check the switches &amp; get the current state
  for (byte i = 0; i < NUMBUTTONS; i++) {
    current_keystate[i]=justpressed[i];
    if (current_keystate[i] != previous_keystate[i]) {
      if (current_keystate[i]) thisSwitch=i;
    }
    previous_keystate[i]=current_keystate[i];
  } 
  return thisSwitch;
}

PaulRB

So you now have a button for each pattern? Then you don't need to debounce. You can greatly simplify your code.

Zer0

Wot? O dear gawd I see what you mean. The switch bouncing on an off before settling does not change the outcome. I had blinders on.
Value of forums proven. Thank you.
But wait!
I am thinking that I might like to next update the program so that I can cycle through modes on each of the buttons, in which case I am back to needing debounce.

And regardless, I want to know why it doesn't work. So I can sleep.

Zer0

I believe I have answered my own question.
In every loop, the program is running that last chunk of code in my posted example, which starts with
Code: [Select]
byte thisSwitch_justPressed() {
  byte thisSwitch = 255;

It then checks the switches to see if any have been pressed and released, and if they have, updates the value of thisSwitch to reflect which number switch has been pressed and uses that number to select the switch case to use in void loop. 255 is a default value that reflects no case--so nothing happens.
I made up a simplified version that emulates this effect. Here is the relevant snippet, with 'do something' in place of the LED strip function that would be running in each case:
Code: [Select]

void loop() {
  byte index=checkSwitches();
  switch(index) {  
    
    case 1:  /*do something*/; break;
    case 2: /*do something else*/; break;
  }
}
byte checkSwitches() {
  byte index=255;
  for (byte i = 0; i < NUMBUTTONS; i++) {
    if (digitalRead(buttons[i]) == LOW) {
      index = i;
    }
  }
return index;
}

Because the blink without delay strip animations do not delay this check from happening over and over, the switches are constantly polled, and thisSwitch reverts to 255 on every loop that doesn't detect a button press, meaning no case is selected except in a loop that also detects a button press.
So as soon as a case is selected to run, it is shut off again in the very next loop. Each time the button is pressed the blinkwithoutdelay animations get to update their pixel setting variables so the animation progresses, manually. In my simplified example here, I can hold down a button to make the strip function progress. In the debounced version, the code would only register a button press if the button was also released, so the button had to be pressed repeatedly to make the strip lights change.
Phew!

Still, going back to the original project, I like the elegance of being able to enter an arbitrary, non-consecutive string of switch pins only once at the beginning of my program. That part is still unsolved and something I would like to get back to at some point. For now, I can make my project work with if/else statements, e.g.:
Code: [Select]
void loop() {

  if (digitalRead(6) == LOW) {
    lightPattern=1;
    }
    else { if(digitalRead(8)==LOW) {
    lightPattern=2; }
    else { if(digitalRead(9)==LOW) {
    lightPattern=3; }
    else { if(digitalRead(11)==LOW) {
    lightPattern=4; }
    else { if(digitalRead(12)==LOW) {
    lightPattern=5; }
    }}}}


Just feels a little clunky still. Suggestions welcomed.  :)

Grumpy_Mike

I posted all the AdaFruit neopixel examples in a blink without delay in the section a few weeks ago. I am on my iPad so I don't have access to it at the moment to post again. Search for it.

Go Up