Creating a 'sparkle' effect for a LED skirt - on/off button help needed

If you have a multimeter... use the alligator clips on the MM leads. Place the MM in "continuity." If the the MM beeps without pressing the pushbutton, the button is NC.

I don't have one... this is my first foray into circuits :frowning: (there are $10 options on Amazon :thinking: )

Does the code from post #20 work on the present project? (without overheating or dim lighting)

// Continuity tester
// Connect probes to pin 2 and GND
// Connect speaker to pin 3 and GND

#define PROBE_PIN   2
#define SPEAKER_PIN  3
#define LED_BUILTIN 13

void setup() {
  Serial.begin(115200);
  pinMode(PROBE_PIN, INPUT_PULLUP); // probe
  pinMode(SPEAKER_PIN, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  bool probe = digitalRead(PROBE_PIN);
  if (!probe) { // probe has continuity
    delay(150);
    digitalWrite(LED_BUILTIN, HIGH);
    Serial.println("BEEP");
    tone(3, 1660, 50); // pin, Hz, duration
  } else {
    digitalWrite(LED_BUILTIN, LOW);
  }
}
diagram.json file for wokwi
{
  "version": 1,
  "author": "Anonymous maker",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-nano", "id": "nano", "top": -4.8, "left": -0.5, "attrs": {} },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": -95.9,
      "left": 111.9,
      "rotate": 270,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-buzzer",
      "id": "bz1",
      "top": -112.8,
      "left": 49.8,
      "attrs": { "volume": "0.1" }
    },
    {
      "type": "wokwi-text",
      "id": "legendservo1",
      "top": -86.4,
      "left": 172.8,
      "attrs": { "text": "Probes\n- one probe/wire on ground\n- one probe/wire on pin 2" }
    },
    {
      "type": "wokwi-text",
      "id": "legendservo2",
      "top": 28.8,
      "left": 124.8,
      "attrs": { "text": "<------\"L\" LED" }
    },
    {
      "type": "wokwi-text",
      "id": "legendservo3",
      "top": -86.4,
      "left": -9.6,
      "attrs": { "text": "Beeper" }
    }
  ],
  "connections": [
    [ "nano:GND.2", "btn1:2.l", "black", [ "v-9.6", "h28.6" ] ],
    [ "nano:2", "btn1:1.l", "green", [ "v-19.2", "h19.2" ] ],
    [ "bz1:2", "nano:3", "green", [ "h-0.4", "v9.6", "h19.2" ] ],
    [ "nano:GND.2", "bz1:1", "black", [ "v-9.6", "h-48" ] ]
  ],
  "dependencies": {}
}

So, as far as I can tell, the Flora Microcontroller would not accept the uF2 output file from the MakeCode interface (the code in post #20) above, which is why I started this Arduino adventure. The code from post #20 works perfectly fine on a separate project I have - which is running on an Adafruit Playground Express with only 20 LED lights.

Assuming there is no reason to suspect any broken device... can you make the smallest sketch to test your circuit board, button and ONE neopixel?

#include <FastLED.h>
#define BUTTONPIN 3
#define DATA_PIN  2
#define NUM_LEDS  1
CRGB led[NUM_LEDS];

void setup() {
  randomSeed(analogRead(A0));
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(led, NUM_LEDS);
  FastLED.clear();
  FastLED.setBrightness(255);
  FastLED.show();

  pinMode(BUTTONPIN, INPUT_PULLUP);
  while (digitalRead(BUTTONPIN)); // hold until button pressed
}

void loop() {
  rgbcym();
  // flicker();
}

void flicker() {
  int i = random (63, 255);
  led[0] = CRGB(i, i, i);
  FastLED.show();
  delay(random(63, 255));
}

void rgbcym() {
  for (int i = 0; i < 3; i++) {
    led[0] = CRGB(255 * (i == 0 ? 0 : 1), 255 * (i == 1 ? 0 : 1), 255 * (i == 2 ? 0 : 1)); // CYM
    // led[0] = CRGB(255 * (i == 0 ? 0 : 1), 255 * (i == 1 ? 0 : 1), 255 * (i == 2 ? 0 : 1)); // RGB
    FastLED.show();
    delay(333);
  }
}

diagram.json for wokwi
{
  "version": 1,
  "author": "Anonymous maker",
  "editor": "wokwi",
  "parts": [
    {
      "type": "wokwi-arduino-nano",
      "id": "nano",
      "top": -41.2,
      "left": 1.3,
      "rotate": 270,
      "attrs": {}
    },
    {
      "type": "wokwi-neopixel",
      "id": "rgb1",
      "top": -121.1,
      "left": 76,
      "rotate": 180,
      "attrs": {}
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": -61,
      "left": -19.2,
      "attrs": { "color": "green" }
    }
  ],
  "connections": [
    [ "nano:5V", "rgb1:VDD", "red", [ "h-9.6", "v-9.6" ] ],
    [ "nano:2", "rgb1:DIN", "white", [ "h9.6", "v-19.2" ] ],
    [ "nano:GND.2", "rgb1:VSS", "black", [ "v-67.2", "h18.4" ] ],
    [ "nano:GND.2", "btn1:1.r", "black", [ "h0" ] ],
    [ "btn1:2.r", "nano:3", "green", [ "h0" ] ]
  ],
  "dependencies": {}
}

Hmmm this is very interesting! And first of all, thank you so much for the help! If I go offline for a bit, it's because I have 12 hour workdays and can only tackle this on my breaks between calls / reviewing materials.

What I did:

  1. Plugged in my Flora with the full 100 LED strand and buttons attached (wired as in the photo)
  2. Ran your code (with updated LED pin / button numbers)
  3. Result: All the LEDs 'froze' as the blue color (at least not dim angry red :partying_face: ). I clicked the button and then the first LED started changing the colors. I could not get it to turn 'off'
  4. I then turned off the microcontroller and turned it back on. Once on, all lights were 'out'. When I clicked the button, the first LED started changing colors. But if I clicked the button again, the LED would not turn off.

Good news is - my button is working and it's not causing weird issues to the LEDs. Now to figure out how to get it to turn the LED off and to work for my full code.

Two questions, comparing the code you shared above with your original push button code:

  1. I see that while (digitalRead(BUTTONPIN)); is under void setup() in the new code above, but not in the original push button code you shared. Does this need to be added?
  2. My original code starts void setup() with delay( 3000 ) and then adds your Serial.begin(9600); before the pinMode() code. Could this be causing an issue?

I experimented with adding the while(DigitalRead(ButtonPin)) to my full code and removing the Serial.begin(9600). Same issue. When I turn the microcontroller off and back on, all lights start out off. I then click the button - nothing happens. I tried a few more times, and the lights turn an angry dim red color....(but the Board does not feel hot, like has happened to me before when overloading it)

I got it to (mostly) work!!

I went back to Google and specifically looked for FastLED push pin functions, since the rest of the code is written for FastLED. I found a few examples that use the button function to 'switch through' animations. I played around with the code, and finally got it to: 1) When the microcontroller is plugged in, all LEDs are off; 2) When the button is clicked, LEDs turn on with animation (no angry red flow); 3) When the button is clicked twice (yes, not once), the LEDs will turn off again.

I will keep playing around with the code, I need to understand the FastLED functions better to see if there's a way to shorten turning off the lights to one click vs two. Although, in a way, this is functionally better for something I will be wearing (I will need to purposefully turn it off vs. accidentally hitting the button)

Anyways, here's the full code:

#include "FastLED.h"


#define NUM_LEDS      100
#define LED_TYPE   WS2811
#define COLOR_ORDER   GRB
#define DATA_PIN        6
//#define CLK_PIN       4
#define VOLTS          3.3
#define MAX_MA       2500
#define buttonPin       2
// byte buttonPin = 10;
//unsigned long timer, timeout = 50;
//bool currentButtonState, lastButtonRead;
volatile byte buttonState = 0; 
CRGBArray<NUM_LEDS> leds;

// Overall twinkle speed.
// 0 (VERY slow) to 8 (VERY fast).  
// 4, 5, and 6 are recommended, default is 4.
#define TWINKLE_SPEED 4

// Overall twinkle density.
// 0 (NONE lit) to 8 (ALL lit at once).  
// Default is 5.
#define TWINKLE_DENSITY 6

// How often to change color palettes.
#define SECONDS_PER_PALETTE  60
// Also: toward the bottom of the file is an array 
// called "ActivePaletteList" which controls which color
// palettes are used; you can add or remove color palettes
// from there freely.

// Background color for 'unlit' pixels
// Can be set to CRGB::Black if desired.
//CRGB gBackgroundColor = CRGB::DeepSkyBlue; 
CRGB gBackgroundColor = CRGB::Black; 
// Example of dim incandescent fairy light background color
 //  CRGB gBackgroundColor = CRGB(CRGB::FairyLight).nscale8_video(16);

// If AUTO_SELECT_BACKGROUND_COLOR is set to 1,
// then for any palette where the first two entries 
// are the same, a dimmed version of that color will
// automatically be used as the background color.
#define AUTO_SELECT_BACKGROUND_COLOR 1

// If COOL_LIKE_INCANDESCENT is set to 1, colors will 
// fade out slighted 'reddened', similar to how
// incandescent bulbs change color as they get dim down.
#define COOL_LIKE_INCANDESCENT 0


CRGBPalette16 gCurrentPalette;
CRGBPalette16 gTargetPalette;

void setup() {
  delay( 3000 ); //safety startup delay
  FastLED.setMaxPowerInVoltsAndMilliamps( VOLTS, MAX_MA);
  FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS)
    .setCorrection(TypicalLEDStrip);
  chooseNextColorPalette(gTargetPalette);
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), increaseState, CHANGE);
  //FastLED.clear();
}


void loop()
{
  //readButton(); // put the pushbutton-read in a function
  if(buttonState > 1){
    buttonState = 0;
  }

  if (buttonState == 0){  //turn all leds off 
    for(int i = 0; i < 101; i++){
    leds[i] = CRGB::Black;;
  }
  FastLED.show();
    FastLED.clear(); //when buttons state is zero turn off all leds 
  }
  else /*(buttonState == 1)*/{  //run code to turn all leds simply on 
     EVERY_N_SECONDS( SECONDS_PER_PALETTE ) { 
    chooseNextColorPalette( gTargetPalette ); 
  }
  
  EVERY_N_MILLISECONDS( 10 ) {
    nblendPaletteTowardPalette( gCurrentPalette, gTargetPalette, 12);
  }

  drawTwinkles( leds);
  
  FastLED.show();
  }
}

void increaseState(){
  if(digitalRead(2) == LOW){
    buttonState++;
  }
}

//  This function loops over each pixel, calculates the 
//  adjusted 'clock' that this pixel should use, and calls 
//  "CalculateOneTwinkle" on each pixel.  It then displays
//  either the twinkle color of the background color, 
//  whichever is brighter.
void drawTwinkles( CRGBSet& L)
{
  // "PRNG16" is the pseudorandom number generator
  // It MUST be reset to the same starting value each time
  // this function is called, so that the sequence of 'random'
  // numbers that it generates is (paradoxically) stable.
  uint16_t PRNG16 = 11337;
  
  uint32_t clock32 = millis();

  // Set up the background color, "bg".
  // if AUTO_SELECT_BACKGROUND_COLOR == 1, and the first two colors of
  // the current palette are identical, then a deeply faded version of
  // that color is used for the background color
  CRGB bg;
  if( (AUTO_SELECT_BACKGROUND_COLOR == 1) &&
      (gCurrentPalette[0] == gCurrentPalette[1] )) {
    bg = gCurrentPalette[0];
    uint8_t bglight = bg.getAverageLight();
    if( bglight > 64) {
      bg.nscale8_video( 16); // very bright, so scale to 1/16th
    } else if( bglight > 16) {
      bg.nscale8_video( 64); // not that bright, so scale to 1/4th
    } else {
      bg.nscale8_video( 86); // dim, scale to 1/3rd.
    }
  } else {
    bg = gBackgroundColor; // just use the explicitly defined background color
  }

  uint8_t backgroundBrightness = bg.getAverageLight();
  
  for( CRGB& pixel: L) {
    PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number
    uint16_t myclockoffset16= PRNG16; // use that number as clock offset
    PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number
    // use that number as clock speed adjustment factor (in 8ths, from 8/8ths to 23/8ths)
    uint8_t myspeedmultiplierQ5_3 =  ((((PRNG16 & 0xFF)>>4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08;
    uint32_t myclock30 = (uint32_t)((clock32 * myspeedmultiplierQ5_3) >> 3) + myclockoffset16;
    uint8_t  myunique8 = PRNG16 >> 8; // get 'salt' value for this pixel

    // We now have the adjusted 'clock' for this pixel, now we call
    // the function that computes what color the pixel should be based
    // on the "brightness = f( time )" idea.
    CRGB c = computeOneTwinkle( myclock30, myunique8);

    uint8_t cbright = c.getAverageLight();
    int16_t deltabright = cbright - backgroundBrightness;
    if( deltabright >= 32 || (!bg)) {
      // If the new pixel is significantly brighter than the background color, 
      // use the new color.
      pixel = c;
    } else if( deltabright > 0 ) {
      // If the new pixel is just slightly brighter than the background color,
      // mix a blend of the new color and the background color
      pixel = blend( bg, c, deltabright * 8);
    } else { 
      // if the new pixel is not at all brighter than the background color,
      // just use the background color.
      pixel = bg;
    }
  }
}


//  This function takes a time in pseudo-milliseconds,
//  figures out brightness = f( time ), and also hue = f( time )
//  The 'low digits' of the millisecond time are used as 
//  input to the brightness wave function.  
//  The 'high digits' are used to select a color, so that the color
//  does not change over the course of the fade-in, fade-out
//  of one cycle of the brightness wave function.
//  The 'high digits' are also used to determine whether this pixel
//  should light at all during this cycle, based on the TWINKLE_DENSITY.
CRGB computeOneTwinkle( uint32_t ms, uint8_t salt)
{
  uint16_t ticks = ms >> (8-TWINKLE_SPEED);
  uint8_t fastcycle8 = ticks;
  uint16_t slowcycle16 = (ticks >> 8) + salt;
  slowcycle16 += sin8( slowcycle16);
  slowcycle16 =  (slowcycle16 * 2053) + 1384;
  uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8);
  
  uint8_t bright = 0;
  if( ((slowcycle8 & 0x0E)/2) < TWINKLE_DENSITY) {
    bright = attackDecayWave8( fastcycle8);
  }

  uint8_t hue = slowcycle8 - salt;
  CRGB c;
  if( bright > 0) {
    c = ColorFromPalette( gCurrentPalette, hue, bright, NOBLEND);
    //if( COOL_LIKE_INCANDESCENT == 1 ) {
    //  coolLikeIncandescent( c, fastcycle8);
    //}
  } else {
    c = CRGB::Black;
  }
  return c;
}


// This function is like 'triwave8', which produces a 
// symmetrical up-and-down triangle sawtooth waveform, except that this
// function produces a triangle wave with a faster attack and a slower decay:
//
//     / \ 
//    /     \ 
//   /         \ 
//  /             \ 
//

uint8_t attackDecayWave8( uint8_t i)
{
  if( i < 86) {
    return i * 3;
  } else {
    i -= 86;
    return 255 - (i + (i/2));
  }
}

// This function takes a pixel, and if its in the 'fading down'
// part of the cycle, it adjusts the color a little bit like the 
// way that incandescent bulbs fade toward 'red' as they dim.
//void coolLikeIncandescent( CRGB& c, uint8_t phase)
//{
//  if( phase < 128) return;

 // uint8_t cooling = (phase - 128) >> 4;
 // c.g = qsub8( c.g, cooling);
 // c.b = qsub8( c.b, cooling * 2);
//}

// A mostly blue palette with white accents.
// "CRGB::Gray" is used as white to keep the brightness more uniform.
const TProgmemRGBPalette16 BlueWhite_p FL_PROGMEM =
{  CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, 
   CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, 
   CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, 
   CRGB::Blue, CRGB::Gray, CRGB::Gray, CRGB::Gray };

// Cloudy Blue
   const TProgmemRGBPalette16 BlueCloud_p FL_PROGMEM =
{  CRGB::DodgerBlue, CRGB::DodgerBlue, CRGB::DodgerBlue, CRGB::DodgerBlue, 
   CRGB::DodgerBlue, CRGB::DodgerBlue, CRGB::DodgerBlue, CRGB::DodgerBlue,
   CRGB::DodgerBlue, CRGB::DodgerBlue, CRGB::DodgerBlue, CRGB::DodgerBlue,
   CRGB::DodgerBlue, CRGB::Gray, CRGB::Gray, CRGB::Gray };

// A palette of soft snowflakes with the occasional bright one
const TProgmemRGBPalette16 Snow_p FL_PROGMEM =
{  0x304048, 0x304048, 0x304048, 0x304048,
   0x304048, 0x304048, 0x304048, 0x304048,
   0x304048, 0x304048, 0x304048, 0x304048,
   0x304048, 0x304048, 0x304048, 0xE0F0FF };

// A cold, icy pale blue palette
#define Ice_Blue1 0x0C1040
#define Ice_Blue2 0x182080
#define Ice_Blue3 0x5080C0
const TProgmemRGBPalette16 Ice_p FL_PROGMEM =
{
  Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
  Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
  Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
  Ice_Blue2, Ice_Blue2, Ice_Blue2, Ice_Blue3
};


// Add or remove palette names from this list to control which color
// palettes are used, and in what order.
const TProgmemRGBPalette16* ActivePaletteList[] = {
  //&BlueWhite_p,
  &Snow_p,
  &Ice_p,
  &BlueCloud_p
};


// Advance to the next color palette in the list (above).
void chooseNextColorPalette( CRGBPalette16& pal)
{
  const uint8_t numberOfPalettes = sizeof(ActivePaletteList) / sizeof(ActivePaletteList[0]);
  static uint8_t whichPalette = -1; 
  whichPalette = addmod8( whichPalette, 1, numberOfPalettes);

  pal = *(ActivePaletteList[whichPalette]);
}

From post #11. This is how I (learned to) read a button. It uses state changes (button pressed changes to button released) and non-blocking debouce timing (a button press causes hundreds or thousands of +/- readings to register... a good reading waits for this to subside).

This is my "go" button routine that I use when i want to mount a system and apply power without it taking off. This line waits for the button to be pressed, and does not care about debounce ringing. This routine/line is not for "button-reading" only for holding the floodgate.

A delay(3000) is a timed way of holding the floodgates (I used a button). This is not the cause of the lights behaving badly.

I put this serial communication initialization in most of my code to allow me to place debug statements in code sections I need to examine. If everything is working, I remove it when I am done to allow more memory for the program.

That is the intent of the button routine (while not pressed, do not start the animation).

The button was only made to allow the animation to start, not to control it.

That is very good.

You do not need to use FastLED. I used it to test your first LED because it is easy for me to set up.

Use Adafruite_NeoPixel.h if you prefer how it is coded.

Excellent.

A "feature" for the win!