Can't get button working

Thank you in advance for any help you can give. I am pulling my hair out at this point.

I have watched and read countless tutorials on how to use a button to switch modes on an LED strip, but no matter what code or board configuration I use, the button does nothing. Sometimes I can see the button is registering because the LED strip will very slightly fade or the small LED on the Arduino will blink, but the LED strip doesn't change modes. I have read about the delay function and that millis is better, but I have seen code work with a button and delay and would prefer it because I can't get millis to work with my code either.

Here is the main code I am trying to get working (but I have tried other working posted ones with no success, so I'm thinking it's my board setup). I am using the FastLED library and the OneButton library. All of these modes have been tested independently of a button and work as intended.

//bring up libraries 
  #include <FastLED.h>
  #include <OneButton.h>

// set number of strips, leds per strip, total leds, and brightness
  #define NUM_STRIPS 3
  #define NUM_LEDS_A    33
  #define NUM_LEDS_B    15
  #define NUM_LEDS_C    12
  #define NUM_LEDS NUM_LEDS_A+NUM_LEDS_B+NUM_LEDS_C
  #define BRIGHTNESS  50

// set parameters for patterns later
  int fadeAmount = 1;
  int brightness = 0;
  
  uint8_t colorIndex[NUM_LEDS];
  
  // create integer for pattern counter, keeps track of which pattern showing
  uint8_t patternCounter = 0;
  
  DEFINE_GRADIENT_PALETTE( greentwinkle_gp ) 
{
   0, 0, 0, 0,
   64, 21, 249, 7,
   128, 80, 255, 67,
   197, 21, 249, 7,
   255, 0, 0, 0
};
//Activate the palette
  CRGBPalette16 greentwinkle = greentwinkle_gp;

// set button to data pin 12
  #define BTN_PIN     12
 
// create an array of all the leds
  CRGB leds[NUM_LEDS];
  
// push button connected to pin 12 and GND; 2nd false = add 10k resistor to ground, true = internal arduino resistor
  OneButton btn = OneButton(BTN_PIN, true, false); 

void setup() {
  
// tell FastLED there's 33 NEOPIXEL leds on pin 6, starting at index 0 in the led array
  FastLED.addLeds<NEOPIXEL, 6>(leds, 0, NUM_LEDS_A);

// tell FastLED there's 15 NEOPIXEL leds on pin 7, starting at index 33 in the led array
  FastLED.addLeds<NEOPIXEL, 7>(leds, NUM_LEDS_A, NUM_LEDS_B);

// tell FastLED there's 12 NEOPIXEL leds on pin 8, starting at index 48 in the led array
  FastLED.addLeds<NEOPIXEL, 8>(leds, NUM_LEDS_A+NUM_LEDS_B, NUM_LEDS_C);

// set brightness level
  FastLED.setBrightness(BRIGHTNESS);

// call next pattern when button is clicked
  btn.attachClick(nextPattern);

}

void loop()
{ 
  switch (patternCounter) {
    case 0:
      SteadyOn();   // all leds steady green
      break;
    case 1:
      ThreeSteadyOn();  // segments of 3 leds steady green
      break;
    case 2: 
      Chase3();     // chase 3 pixels at a time
      break;
    case 3:
      Chase3Fast(); // chase 3 pixels faster
      break;
    case 4:
      Glow();       // glow green
      break;
    case 5:
      Twinkle();    // twinkle green
      break;
    case 6:
      Blackout();   // all leds off
      break;
  }

   
  FastLED.show();
  btn.tick();       // checks the button each loop, MUST HAVE 
 
}

void nextPattern() {
  patternCounter = (patternCounter + 1) %7;     // Advance the pattern; Change number after % to number of patterns
}

void SteadyOn() {
  for(int i = 0; i < NUM_LEDS; i++ )
   {
   leds[i].setRGB(0,255,0);  // Set Color here
  }
}

void ThreeSteadyOn() {
      leds[44] = CRGB::Green; 
      leds[43] = CRGB::Green; 
      leds[42] = CRGB::Green; 
      leds[38] = CRGB::Green; 
      leds[37] = CRGB::Green; 
      leds[36] = CRGB::Green;  
      leds[32] = CRGB::Green; 
      leds[31] = CRGB::Green; 
      leds[30] = CRGB::Green; 
      leds[26] = CRGB::Green; 
      leds[25] = CRGB::Green; 
      leds[24] = CRGB::Green; 
      leds[20] = CRGB::Green; 
      leds[19] = CRGB::Green; 
      leds[18] = CRGB::Green; 
      leds[14] = CRGB::Green; 
      leds[13] = CRGB::Green; 
      leds[12] = CRGB::Green; 
      leds[8] = CRGB::Green; 
      leds[7] = CRGB::Green; 
      leds[6] = CRGB::Green; 
      leds[2] = CRGB::Green; 
      leds[1] = CRGB::Green; 
      leds[0] = CRGB::Green; 
}

void Chase3() {
    for(int i = 32; i > -1; i--) { 
      leds[i] = CRGB::Green; 
      leds[i-1] = CRGB::Green;
      leds[i-2] = CRGB::Green;
      FastLED.delay(250); 
      leds[i] = CRGB::Black; 
      leds[i-1] = CRGB::Black;
      leds[i-2] = CRGB::Black;
    }
    for(int j = 33; j <48; j++) { 
      leds[j] = CRGB::Green; 
      leds[j+1] = CRGB::Green;
      leds[j+2] = CRGB::Green;
      FastLED.delay(250); 
      leds[j] = CRGB::Black; 
      leds[j+1] = CRGB::Black;
      leds[j+2] = CRGB::Black;
    }
    for(int k = 48; k <60; k++) { 
      leds[k] = CRGB::Green; 
      leds[k+1] = CRGB::Green;
      leds[k+2] = CRGB::Green;
      FastLED.delay(250); 
      leds[k] = CRGB::Black; 
      leds[k+1] = CRGB::Black;
      leds[k+2] = CRGB::Black;
    }

}

void Chase3Fast() {
  for(int i = 32; i > -1; i--) { 
  leds[i] = CRGB::Green; 
  leds[i-1] = CRGB::Green;
  leds[i-2] = CRGB::Green;
  FastLED.delay(100); 
  leds[i] = CRGB::Black; 
  leds[i-1] = CRGB::Black;
  leds[i-2] = CRGB::Black;
}
for(int j = 33; j <48; j++) { 
  leds[j] = CRGB::Green; 
  leds[j+1] = CRGB::Green;
  leds[j+2] = CRGB::Green;
  FastLED.delay(100); 
  leds[j] = CRGB::Black; 
  leds[j+1] = CRGB::Black;
  leds[j+2] = CRGB::Black;
}
}

void Glow() {
  for(int i = 0; i < NUM_LEDS; i++ )
   {
   leds[i].setRGB(0,255,0);  // Set Color HERE!!!
   leds[i].fadeLightBy(brightness);
  }
  brightness = brightness + fadeAmount;
  // reverse the direction of the fading at the ends of the fade: 
  if(brightness == 0 || brightness == 255)
  {
    fadeAmount = -fadeAmount ; 
  }    
  delay(9);  // This delay sets speed of the fade. I usually do from 5-75 but you can always go higher.
}

void Twinkle() {
  //Fill the colorIndex array with random numbers
  for (int i = 0; i < NUM_LEDS; i++) {
    colorIndex[i] = random8();
  }
  //color each pixel from the palette using the index from colorIndex
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = ColorFromPalette(greentwinkle, colorIndex[i]);
  }
  
  EVERY_N_MILLISECONDS(2){
    //switch on an led at random, choosing a random color from the palette
    //leds[random8(0, NUM_LEDS - 1)] = ColorFromPalette(greentwinkle, random8(), 255, LINEARBLEND);
    for (int i = 0; i < NUM_LEDS; i++){
      colorIndex[i]++;
    //}
  }

    //fade all LEDS down by 1 in brightness each time this is called
    fadeToBlackBy(leds, NUM_LEDS, 10); 
}}

void Blackout() {
  fill_solid( leds, NUM_LEDS, CRGB(0,0,0));
}

Here is my board setup. I am using an Arduino Uno and Neopixel LED strips:

If you light up 33 NeoPixels at full white, you will be drawing 1.8 Amps and if you try to power this from the USB port, it will shut down.

To operate NeoPixels, you need a regulated 5 V supply. Some sort of USB "phone charging" adapter with at least 2.1 Amp capability would be a good start for that first strip, but it sounds like you may need twice that for the three strips. So you power the strips directly with 5 V, never through the Arduino. You must of course connect the ground of the 5 V supply back to the Arduino along (bundled together) with the three data lines. You can also connect the 5 V to the "5V" pin on the Arduino UNO except when you connect it to the PC via USB.

Next, you show the pushbutton connected to the 3.3 V pin. Why? The button should connect between the pin and ground using pinMode of INPUT_PULLUP so you generally do not need a pullup resistor. Obviously this means it reads LOW when pressed.

These are basics before we look at the code. :grin:

Thank you, Paul. I've rewired the button to be between ground and data now with no power input and adjusted my code for that change. Unfortunately, it still doesn't work.

In the meantime, I'm trying to start over using the Adafruit library instead of FastLED to see if I can get all the modes that I want since the button now works with that code.

FYI

The FastLED messes up the millis() and the OneButton library depends on millis(). I thought that I could make it work at least a little, but the Wokwi simulation seems to be broken at the moment.

The patterns are very slow, because of the long delays.

You could add messages to the Serial Monitor, so you know what the sketch is doing.

1 Like

I can't imagine why you need a library for one button!

You should know - or be able to determine one way or the other - how long a strip update takes. You should be able to use this as a basis for delays, since you are forced to use that routine.

Cause I'm completely new at this :laughing:

I am working with a new code that does not rely on a library for the button. Now I am trying to figure out how to make the functions outside the void loop continue looping until I press the button. Looks like do/while or interrupts might solve this? Which do you recommend?

Thank you so much! That is very helpful

Thank you, I now have the switch connected to ground and data pin 2 with no external resistor and using this code:

#define BUTTON_PIN 2
pinMode(BUTTON_PIN, INPUT_PULLUP);

Probably neither.

Interrupts are not needed with buttons. They are only needed for use with circuits where very precise timing or very short-lived events need to be captured. Plus, they might not even work in your case because as already mentioned, FastLED disables them when updating your strip.

Do/while loops that wait for buttons to be pressed or released would become what's called "blocking code" which would prevent your sketch from updating the strip animations for example.

All you need is digitalRead(), if(), maybe a short delay() of 20~50ms for debouncing, and a variable to hold the previous result of digitalRead(), so you know when the button state has changed.

That's incorrect. You can say

#define BUTTON_PIN 2

or

const byte BUTTON_PIN = 2;

Thank you, that was a typo. I do have it as you have written

That sounds inside out. I think you mean the functions inside the loop()

That is not what interrupts are for!

As a beginner, it is incredibly unlikely that interrupts will be useful to you.

A common "newbie" misunderstanding is that an interrupt is a mechanism for altering the flow of a program - to execute an alternate function. Nothing could be further from the truth! :astonished:

An interrupt is a mechanism for performing an action which can be executed in "no time at all" with an urgency that it must be performed immediately or else data - information - will be lost or some harm will occur. It then returns to the main task without disturbing that task in any way though the main task may well check at the appropriate point for a "flag" set by the interrupt.

Now these criteria are in a microprocessor time scale - microseconds. This must not be confused with a human time scale of tens or hundreds of milliseconds or indeed, a couple of seconds. A switch operation is in this latter category and even a mechanical operation perhaps several milliseconds; the period of a 6000 RPM shaft rotation is ten milliseconds. Sending messages to a video terminal is clearly in no way urgent,

Unless it is a very complex procedure, you would expect the loop() to cycle many times per millisecond. If it does not, there is most likely an error in code planning; while the delay() function is provided for testing purposes, its action goes strictly against effective programming methods. The loop() will be successively testing a number of contingencies as to whether each requires action, only one of which may be whether a particular timing criteria has expired. Unless an action must be executed in the order of mere microseconds, it will be handled in the loop().

So what sort of actions do require such immediate attention? Well, generally those which result from the computer hardware itself, such as high speed transfer of data in UARTs(, USARTs) or disk controllers.

An alternate use of interrupts, for context switching in RTOSs, is rarely relevant to this category of microprocessors as it is more efficient to write cooperative code as described above.


I am not entirely sure how the code you first posted works - or indeed, **if** it works at all. I am particularly confused about how it addresses the three separate strips or how your various patterns are supposed to work with those three strips.

As best i can see, the basic problem is that your main loop() calls one of a number of sequences which are supposed to execute - if they actually involve animation - a complete animation cycle before returning to that main loop(). This is puzzling as I do not know what "FastLED.delay()" is supposed to do and it seems that only in the main loop() is FastLED.show called which would actually update the LED string(s). Since there is no statefulness of the various pattern sequences, I do not know how it is supposed to animate; it appears that the same pattern is called at each successive step (through loop()).

To have the ability to switch through patterns, you need to monitor your button (and that is indeed, a topic in itself, because you are presumably looking for each individual separate press of the button and not the fact that it continues to be pressed) with each step of an animation rather than waiting for the animation to complete, in order to switch to a different pattern. This can be done either by putting the button test inside each loop iteration within those different patterns, or else making the pattern animations execute only one step at a time before returning to the main loop() where the button is tested, keeping track of where they are in the pattern by a "state" counter variable.

Another consideration is that whenever you switch from one pattern to another you will need to initialise the whole display to the starting condition of the new pattern.

1 Like

I'm not sure that's the real problem here. Your chase3() function has a 250ms delay inside a loop. That function will take like 10 seconds to return. Any button presses in that interval will be missed.

I added a function chaseNonBlocking() which does the same thing, but returns almost instantly. The button seems to work now.

2 Likes

This works awesome, thank you! I will incorporate this into the code

Thank you so much @stavs ! Everything is working perfectly now, and your chase function looks much better than mine did to boot. The twinkle is a little wonky now, so I changed it to pulse for now and will keep working on it.
Here is the final code for those interested:

//bring up libraries 
  #include <FastLED.h>
  #include <OneButton.h>

// set number of strips, leds per strip, total leds, and brightness
  #define NUM_STRIPS 3
  #define NUM_LEDS_A    33
  #define NUM_LEDS_B    15
  #define NUM_LEDS_C    12
  #define NUM_LEDS ((NUM_LEDS_A)+(NUM_LEDS_B)+(NUM_LEDS_C))
  #define BRIGHTNESS  100

// set parameters for patterns later
  int fadeAmount = 1;
  int brightness = 0;
  
  uint8_t colorIndex[NUM_LEDS];
  
  // create integer for pattern counter, keeps track of which pattern showing
  uint8_t patternCounter = 0;
  
  DEFINE_GRADIENT_PALETTE( greentwinkle_gp ) 
{
   255, 0, 0, 0,
   64, 21, 249, 7,
   128, 80, 255, 67,
   197, 21, 249, 7,
   255, 0, 0, 0
};
//Activate the palette
  CRGBPalette16 greentwinkle = greentwinkle_gp;

// set button to data pin 10
  #define BTN_PIN     2
 
// create an array of all the leds
  CRGB leds[NUM_LEDS];
  
// push button connected to pin 12 and GND with internal pull-up, so LOW is "on"
  OneButton btn = OneButton(BTN_PIN, true, true); 

void setup() {
  
// tell FastLED there's 33 NEOPIXEL leds on pin 6, starting at index 0 in the led array
  FastLED.addLeds<NEOPIXEL, 6>(leds, 0, NUM_LEDS_A);

// tell FastLED there's 15 NEOPIXEL leds on pin 7, starting at index 33 in the led array
  FastLED.addLeds<NEOPIXEL, 7>(leds, NUM_LEDS_A, NUM_LEDS_B);

// tell FastLED there's 12 NEOPIXEL leds on pin 8, starting at index 48 in the led array
  FastLED.addLeds<NEOPIXEL, 8>(leds, NUM_LEDS_A+NUM_LEDS_B, NUM_LEDS_C);

// set brightness level
  FastLED.setBrightness(BRIGHTNESS);

// call next pattern when button is clicked
  btn.attachClick(nextPattern);

}

void loop()
{ 
  switch (patternCounter) {
    case 0:
      SteadyOn();   // all leds steady green
      break;
    case 1:
      ThreeSteadyOn();  // segments of 3 leds steady green
      break;
    case 2: 
      // Chase3();     // chase 3 pixels at a time
      ChaseNonBlocking(250);     // chase 3 pixels at a time, every 250ms
      break;
    case 3:
      // Chase3Fast(); // chase 3 pixels faster
      ChaseNonBlocking(100);     // chase 3 pixels at a time, every 100ms
      break;
    case 4:
      Glow();       // glow green
      break;
    case 5:
      Pulse();    // twinkle green
      break;
    case 6:
      Blackout();   // all leds off
      break;
  }

   
  FastLED.show();
  btn.tick();       // checks the button each loop, MUST HAVE 
 
}

void nextPattern() {
  patternCounter = (patternCounter + 1) %7;     // Advance the pattern; Change number after % to number of patterns
}

void SteadyOn() {
  for(int i = 0; i < NUM_LEDS; i++ )
   {
   leds[i].setRGB(0,255,0);  // Set Color here
  }
}

void ThreeSteadyOn() {
      fill_solid( leds, NUM_LEDS, CRGB(0,0,0));
      leds[44] = CRGB::Green; 
      leds[43] = CRGB::Green; 
      leds[42] = CRGB::Green; 
      leds[38] = CRGB::Green; 
      leds[37] = CRGB::Green; 
      leds[36] = CRGB::Green;  
      leds[32] = CRGB::Green; 
      leds[31] = CRGB::Green; 
      leds[30] = CRGB::Green; 
      leds[26] = CRGB::Green; 
      leds[25] = CRGB::Green; 
      leds[24] = CRGB::Green; 
      leds[20] = CRGB::Green; 
      leds[19] = CRGB::Green; 
      leds[18] = CRGB::Green; 
      leds[14] = CRGB::Green; 
      leds[13] = CRGB::Green; 
      leds[12] = CRGB::Green; 
      leds[8] = CRGB::Green; 
      leds[7] = CRGB::Green; 
      leds[6] = CRGB::Green; 
      leds[2] = CRGB::Green; 
      leds[1] = CRGB::Green; 
      leds[0] = CRGB::Green; 
}

void ChaseNonBlocking(uint16_t delay) {
   static uint16_t base_position = 0;
   static uint32_t last_change = 0;
   if (millis() - last_change < delay)
      return;
   last_change = millis();
   base_position = (base_position + 1) % NUM_LEDS;

   uint16_t this_pos = base_position;
   for (uint16_t ledno = 0; ledno < NUM_LEDS; ledno++) {
      if (this_pos % 6 < 3)
         leds[ledno] = CRGB::Green;
      else
         leds[ledno] = CRGB::Black;
      this_pos++;
   }
}

void Glow() {
  for(int i = 0; i < NUM_LEDS; i++ )
   {
   leds[i].setRGB(0,255,0);  // Set Color HERE!!!
   leds[i].fadeLightBy(brightness);
  }
  brightness = brightness + fadeAmount;
  // reverse the direction of the fading at the ends of the fade: 
  if(brightness == 0 || brightness == 255)
  {
    fadeAmount = -fadeAmount ; 
  }    
  delay(9);  // This delay sets speed of the fade. I usually do from 5-75 but you can always go higher.
}

void Pulse() {
  //Fill the colorIndex array with random numbers
for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = ColorFromPalette(greentwinkle, colorIndex[i]);
  }
  
  EVERY_N_MILLISECONDS(10){
    for (int i = 0; i < NUM_LEDS; i++) {
      colorIndex[i]++;
    }
  }
  FastLED.show();
}

void Blackout() {
  fill_solid( leds, NUM_LEDS, CRGB(0,0,0));
}

My implementation of your sketch in Wokwi: https://wokwi.com/arduino/projects/299243987871465994.

@stavs made a different circuit, but the result should be the same.

@laurie_ann, you are allowed to have your own style of the text layout, however, you should not indent the includes and the defines and the global variables.
A sketch can send messages to the Serial Monitor.
Can you try my Wokwi, click the button and see the messages.
I have added a freeMemory() function.
[UPDATE] I have fixed the freeMemory() function, you only use half of the SRAM (1216 bytes still free). That is okay.

2 Likes

That is really handy with the serial messages. Thank you!