Neopixels and button control

so i have a project going on and decided to use single neopixels as "uplighting"

i figured these would need case statements to be controlled with a button, so pressing a momentary button would change the function of the leds.

i looked into the button cycler example given in the arduino library but honestly it makes some sense but not entirely, it has functions i dont need in there and a function i want in there isnt there as its rather specific.

essentially heres what i want to happen.

when powered on, leds are off.

button presses

1 - on - white
2 - on - red
3 - on - green
4 - on - blue
5 - on - fade through the 4 colours
6 - off

i've posted the code below and i guess what im asking for is help from someone to talk me through this step by step (so i can actually learn) i dont really want the changes to the cycler done for me because i wont learn anywhere near as much as the help asked for.

heres the code

// This is a demonstration on how to use an input device to trigger changes on your neo pixels.
// You should wire a momentary push button to connect from ground to a digital IO pin.  When you
// press the button it will change to a new pixel animation.  Note that you need to press the
// button once to start the first animation!

#include <Adafruit_NeoPixel.h>

#define BUTTON_PIN   2    // Digital IO pin connected to the button.  This will be
                          // driven with a pull-up resistor so the switch should
                          // pull the pin to ground momentarily.  On a high -> low
                          // transition the button press logic will execute.

#define PIXEL_PIN    6    // Digital IO pin connected to the NeoPixels.

#define PIXEL_COUNT 16

// Parameter 1 = number of pixels in strip,  neopixel stick has 8
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_RGB     Pixels are wired for RGB bitstream
//   NEO_GRB     Pixels are wired for GRB bitstream, correct for neopixel stick
//   NEO_KHZ400  400 KHz bitstream (e.g. FLORA pixels)
//   NEO_KHZ800  800 KHz bitstream (e.g. High Density LED strip), correct for neopixel stick
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

bool oldState = HIGH;
int showType = 0;

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
}

void loop() {
  // Get current button state.
  bool newState = digitalRead(BUTTON_PIN);
  
  // Check if state changed from high to low (button press).
  if (newState == LOW && oldState == HIGH) {
    // Short delay to debounce button.
    delay(20);
    // Check if button is still low after debounce.
    newState = digitalRead(BUTTON_PIN);
    if (newState == LOW) {
      showType++;
      if (showType > 9)
        showType=0;
      startShow(showType);
    }
  }

  // Set the last button state to the old state.
  oldState = newState;
}

void startShow(int i) {
  switch(i){
    case 0: colorWipe(strip.Color(0, 0, 0), 50);    // Black/off
            break;
    case 1: colorWipe(strip.Color(255, 0, 0), 50);  // Red
            break;
    case 2: colorWipe(strip.Color(0, 255, 0), 50);  // Green
            break;
    case 3: colorWipe(strip.Color(0, 0, 255), 50);  // Blue
            break;
    case 4: theaterChase(strip.Color(127, 127, 127), 50); // White
            break;
    case 5: theaterChase(strip.Color(127,   0,   0), 50); // Red
            break;
    case 6: theaterChase(strip.Color(  0,   0, 127), 50); // Blue
            break;
    case 7: rainbow(20);
            break;
    case 8: rainbowCycle(20);
            break;
    case 9: theaterChaseRainbow(50);
            break;
  }
}

// Fill the dots one after the other with a color
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 rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

//Theatre-style crawling lights.
void theaterChase(uint32_t c, uint8_t wait) {
  for (int j=0; j<10; j++) {  //do 10 cycles of chasing
    for (int q=0; q < 3; q++) {
      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, c);    //turn every third pixel on
      }
      strip.show();
     
      delay(wait);
     
      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
}

//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
  for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
    for (int q=0; q < 3; q++) {
        for (int i=0; i < strip.numPixels(); i=i+3) {
          strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on
        }
        strip.show();
       
        delay(wait);
       
        for (int i=0; i < strip.numPixels(); i=i+3) {
          strip.setPixelColor(i+q, 0);        //turn every third pixel off
        }
    }
  }
}

// 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) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else if(WheelPos < 170) {
    WheelPos -= 85;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  }
}

i guess the first step is to change the code to a single neopixel led, i presume that would be changing

#define PIXEL_COUNT 16

to

#define PIXEL_COUNT 1

to redefine it to a single pixel, is that correct?

after which im guessing i need to alter the last state change which would be this;

if (showType > 9)

to this

if (showType > 5)

(then modify case 5 for the desired fade and further alterations to code elsewhere)

Your suggested changes make sense

I would start by getting the button press counter working first and printing the current value to the Serial monitor.

Once that works, add a switch/case based on the counter value to call the appropriate function to do the required effect. None of these functions should be blocking so, no delay()s, long for loops or while loops and ideally none at all

hi, the changes made work, but the code does use delays and these cannot be interupted by the button presses.

this is the stock example code in the neopixel package thats being changed, i didnt write the code itself.

what you've suggested literally went over my head, am i right in thinking your suggesting making a whole new code?

I would get the sketch working well for your modes 1,2,3,4 & 6 to begin with. Replace your colour fading mode with another fixed colour for the moment.

The Adafruit example sketch uses a "colourWipe" effect/function even for the fixed colours. This updates the strip one led at a time, and does it slowly enough to see the new colour "wiping" along the strip. No point having that in your project, with only one led!

So you can replace this:

    case 1: colorWipe(strip.Color(255, 0, 0), 50);  
    break;

with:

    case 1: strip.setPixelColor(0, strip.Color(255, 0, 0));
      strip.show();
      break;

for example.

Yes, I am afraid so

Here is an example of a non blocking sketch that changes the output mode based on the number of button presses

const byte buttonPin = A3;

void setup()
{
  Serial.begin(115200);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop()
{
  enum states
  {
    SLOW,
    FAST
  };
  unsigned long currentTime = millis();
  static unsigned long startTime = currentTime;
  unsigned long statePeriod;
  static unsigned long previousTime = millis();
  static byte previousButtonState = HIGH;
  static byte buttonPressCounter = 0;
  byte currentButtonState = digitalRead(buttonPin);
  static byte count = 0;
  //
  //detect a button press
  //
  if (currentButtonState != previousButtonState && currentButtonState == LOW)
  {
    buttonPressCounter++;
    if (buttonPressCounter > 1)
    {
      buttonPressCounter = 0;
    }
    Serial.println(buttonPressCounter);
  }
  previousButtonState = currentButtonState;
  //
  //act on the value of the number of button presses
  //
  switch (buttonPressCounter)
  {
    case SLOW:
      statePeriod = 2000;
      if (currentTime - startTime >= statePeriod)
      {
        count++;
        Serial.println(count);
        startTime = currentTime;
      }
      break;
    case FAST:
      statePeriod = 200;
      if (currentTime - startTime >= statePeriod)
      {
        count++;
        Serial.println(count);
        startTime = currentTime;
      }
      break;
  };
}

Note how the code for each state has no blocking code in it which allows the sketch to return frequently to the loop() function to read the button

okay, this makes sense, makes it more efficient and to the point of the end goal.

i will admit i do dislike the fact this code calls for delays, the delay on the button press can easily be removed if a pullup/down resistor is used to compensate for debounce but (and i know, thinking a little futher ahead) the delay for the fade sequence is quite fustrating as it blocks any input from the button and that segment only does 5 cycles.

honestly i dont know how i would translate that into what i want to acheive, in my head the neopixel library makes matters more complicated and modifying the original coding to be more towards what i need it to do seems the simpler route to go down.

I understand your feelings but you really do need to change the sketch in order to remove the blocking code

If you look at my example then you will see for each state there is code to determine whether it is time to move on to the next step in the same effect. If so, then the code makes the required changes to the output, notes the time of change ready for the next time check then goes back to loop(), otherwise it goes back to loop() immediately

To make it more like the example that you have then the switch/case could call an amended version of one of your current functions, for example (COMPLTELY UNTESTED)


void colorWipe(uint32_t c, uint8_t wait)
{
  unsigned long currentTime = millis();
  static unsigned long previousTime = currentTime;
  static byte currentPixel = 0;
  if (currentTime - previousTime >= wait) //time for a change ?
  {
    currentPixel++; //next pixel
    if (currentPixel == strip.numPixels)  //all done ?
    {
      currentPixel = 0; //back to the start of the strip
      previousTime = currentTime; //reset the start time
      strip.setPixelColor(currentPixel, c); //set colour of next pixel
      strip.show(); //show it
    }
  }
}

Absolutely not true. First of all the pull up resistor is there to produce a voltage, as a switch can't produce any by itself. Secondly, switch bounce can not be compensated with a resistor in any possible way. It can be compensated by a capacitor, but only for individual switches and not any switch matrix.

No, it won't, sorry!

You can use a capacitor and resistor to debounce if you really want, but it's easy to do it in code.

EDIT: @anon57585045 beat me to it.

then in that case i have absolutely no idea where to start.

You could start with the sketch you posted above and make the changes you proposed and the changes I suggested.

Examine the code for my alternative colorWipe() function. Can you see what it does each time that it is called ? Basically, if it is time for the next change then it makes it, otherwise it just goes back to loop().

Most of the functions that you want in your original post, ie white, red, green, blue are trivial because there is no timing involved, only the fade through 4 colours needs timing

What do you envisage the "fade through 4 colours" function doing ? Which case is it in the code or is it something else ?

This I don't get. The advantage of neopixels is that you can have strips containing many LEDs without the problem of running out of PWM output pins. With ordinary/dumb RGB LEDs, 3x PWM pins are needed per led, so an Uno can only control 2 LEDs, for example.

But you only plan to use a single led, so why use neopixels? Am I missing something?

the project will have 4 leds wired parallel, but each spot light a single neopixel, why those? well i have them lying around and not being used, the shape of the pcb fits nicely into a circular spot shade and the solder pads are ideally placed for this too. convenience of hardware.

so the fade feature would simply and continuosly cycle the rainbow colors until the button is pressed again to turn the whole thing off.

i was going to limit it to the 4 colors but probably easier to just go with the entire spectrum at this point.

i see what your example code does, its just i have no idea how to reconfigure the setup or anything else if its an entirely new code.

i can typically modify code with a little help along the way but starting a whole new sketch with something i dont understand (coding specifically for neopixel) is like jumping into the atlantic, expecting to swim to the nearest country but not being able to swim...makes sense?

So is it one of the functions in the sketches posted here or is it something else ?

// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

that is the closest to it, this section cycles the rainbow fade 5 times then stops on solid red. during this sequence the entire code is locked down due to the delay and the button presses arent ackowledged as a result, i can increase the number of cycles to any amount but no good if you want to turn it off 2000 cycles into a 20k cycle lockout?

no idea what the below actually does as an FYI

// 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) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else if(WheelPos < 170) {
    WheelPos -= 85;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  }
}

If that is really the only function that causes problems by blocking then you could simply take the pragmatic approach and call a function from inside the rainbow function that reads the button input and return from the rainbow function if the button count changes

NOTE : I do not mean that you should use an interrupt

i think i understand what you mean, i have no idea how to do that, never seen code that does that, nor written code for it. the solution does sound like that would work though.

so what would be the best approach to that as a solution? which if im not mistaken is a function within a function?

so i found this

void delay_till_condition (bool (*condition)(void))
{
  while (! condition())
  {}
}
You can pass in an arbitrary condition testing function to it, such as

bool button_pressed()
{
  return digitalRead (BUTTON) == LOW ;
}

void loop()
{
  delay_till_condition (button_pressed) ;
}

here: Cancel the delay - #4 by MarkT

is this what your talking about?