Understanding FASTLed EVERY_N_SECONDS()

I'm trying to understand the EVERY_N_SECONDS() method.

I am using the FASTLed NOISE functions to invoke random moving color patterns.
I want to use a specific palette for a set period of time (60 seconds), followed by a second palette for a set period of time (5 seconds) and then start over.

Expectations are as follows:

  • Lava for 60 seconds
  • Forest for 5 seconds
  • Lava for 60 seconds
  • Forest for 5 seconds....

Current behavior:

  • Lava for 60 seconds
  • Forest for 5 seconds
  • Lava 55
  • Forest 10
  • Lava 50
  • Forest 15
  • Lava 45
  • Forest 20
  • Lava 40
  • Forest 25
  • Lava 35
  • Forest 30
  • Lava 30...

code is as follows:

#include <FastLED.h>

#define LED_PIN 11
#define CLK_PIN 13
#define LED_TYPE APA102
#define COLOR_ORDER BGR
#define NUM_LEDS    60
#define BRIGHTNESS  50
#define FRAMES_PER_SECOND 10

CRGB leds[NUM_LEDS];

CRGBPalette16 currentPalette(CRGB::Black);
CRGBPalette16 targetPalette(LavaColors_p);

void setup() {
  Serial.println("resetting");
  delay(3000);
  LEDS.addLeds<LED_TYPE, LED_PIN, CLK_PIN, COLOR_ORDER>(leds, NUM_LEDS);
  LEDS.setBrightness( BRIGHTNESS );
}

void loop() {

  fillnoise8();

  EVERY_N_MILLIS(10) nblendPaletteTowardPalette(currentPalette, targetPalette, 48);

  EVERY_N_SECONDS(60) targetPalette=CRGBPalette16(ForestColors_p);

  EVERY_N_SECONDS(65) targetPalette = CRGBPalette16(LavaColors_p);

  LEDS.show();

}


void fillnoise8() {

#define scale 30

  for (int i = 0; i < NUM_LEDS; i++) {
    uint8_t index = inoise8(i * scale, millis() / 10 + i * scale);
    leds[i] = ColorFromPalette(currentPalette, index, 255, LINEARBLEND);
  }

} // fillnoise8()

I'm sure I'm missing something.
Help me understand.

thanks,
Mike

Hi Mike, yes you are missing the obvious! You want those colours to be used "for 60s" but the macro performs an action once every 60s.

Your code is like two very slow metronomes. One ticks every 60s, the other every 65s, completely independently of each other.

What is the correct method for accomplishing what I am trying to do?

There is no single correct method.

The EVERY_N_SECONDS macro is useful for simple "metronome" timing of changes, but your requirement is slightly more complex.

I would use millis(), and some variables to remember which was the last palette to be set, and when, and use an if or switch statement to decide what to do based on those variables. This technique is called a "state machine" which sounds advanced and scary, but is actually really easy and obvious to grasp, especially once you have seen it once.

unsigned long lastPalletChange;
byte lastPallet;

...

  if (lastPallet == 0) {
    if (millis() - lastPalletChange >= 60000) {
      targetPalette = CRGBPalette16(LavaColors_p);
      lastPalletChange = millis();
      lastPallet = 1;
    }
  }
  else {
    if (millis() - lastPalletChange >= 5000) {
      targetPalette = CRGBPalette16(ForestColors_p);
      lastPalletChange = millis();
      lastPallet = 0;
    }
  }

Untested!

@PaulRB - Thank you for your responses. Your feedback put me on the right path.

I found a couple of really helpful Videos from 'Programming Electronics Academy':
Doing multiple timed things with Arduino: Unleash the millis()!

I was able to come up with the following code:

#include <FastLED.h>

// How many leds in your strip?
#define NUM_LEDS 60

// For led chips like Neopixels, which have a data line, ground, and power, you just
// need to define DATA_PIN.  For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806, define both DATA_PIN and CLOCK_PIN
#define LED_PIN 11
#define CLK_PIN 13
#define LED_TYPE APA102
#define COLOR_ORDER BGR
#define NUM_LEDS    60
#define BRIGHTNESS  10
#define FRAMES_PER_SECOND 10

// Define the array of leds
CRGB leds[NUM_LEDS];

CRGBPalette16 currentPalette(CRGB::Black); //initialize to BLACK for fade on affect
CRGBPalette16 targetPalette(LavaColors_p); //initialize target palette to LAVA colors
//CRGBPalette16 targetPalette(HeatColors_p);
//CRGBPalette16 targetPalette(ForestColors_p);
String palette = "Lava"; //used for debuging

const unsigned long lavaDur = 60000; //show lava for 60 seconds
const unsigned long forestDur = 5000; //show forest for 5 seconds

unsigned long currentTime = 0; //initilize current timer
unsigned long previousTime = 0; //initialize previous timer
unsigned int x = 0; //used for debugging

void setup() {
/* used for debugging */
  Serial.begin(9600);
  Serial.println("resetting");
  
  delay(3000);
  LEDS.addLeds<LED_TYPE, LED_PIN, CLK_PIN, COLOR_ORDER>(leds, NUM_LEDS);      // APA102, WS8201
  LEDS.setBrightness( BRIGHTNESS );
}

void loop() {
  currentTime = millis(); //start timer
  
/* debugging, curious what is going on under the hood */
  EVERY_N_SECONDS(1){
    x++;
    Serial.print(x);
    Serial.print(": ");      
    Serial.print(" palette: ");
    Serial.println(palette);
  }

/* After 60 seconds, set color palette to Forest */
  if(currentTime - previousTime >= lavaDur){
    targetPalette=CRGBPalette16(ForestColors_p);
    palette = "Forest";
  }

/* After 5 seconds, set color palette to Lava */
  if(currentTime - previousTime >= lavaDur + forestDur){
    targetPalette=CRGBPalette16(LavaColors_p);
    palette = "Lava";
    previousTime = currentTime;
  }

/* Fill LEDS with noise colors */
  fillnoise8();

/* fade colors from current palette to target palette */
  EVERY_N_MILLIS(10) {
    nblendPaletteTowardPalette(currentPalette, targetPalette, 48);          // Blend towards the target palette over 48 iterations.
  }

/* show LEDs */
  LEDS.show();                                                              // Display the LED's at every loop cycle.
}


void fillnoise8() {

#define scale 30                                                          // Don't change this programmatically or everything shakes.

  for (int i = 0; i < NUM_LEDS; i++) {                                      // Just ONE loop to fill up the LED array as all of the pixels change.
    uint8_t index = inoise8(i * scale, millis() / 10 + i * scale);           // Get a value from the noise function. I'm using both x and y axis.
    leds[i] = ColorFromPalette(currentPalette, index, 255, LINEARBLEND);    // With that value, look up the 8 bit colour palette value and assign it to the current LED.
  }

} // fillnoise8()

Thank you again.
Mike

OK I guess, but would advise against using String, especially on Uno and similar, and on any platform for storing statuses or for debugging.

PaulRB:
OK I guess, but would advise against using String, especially on UNO and similar, and on any platform for storing statuses or for debugging.

Or just arrange to reboot frequently! :sunglasses:

Is that because of the space limitations imposed by the device and the space requirements for the String data type?

I'm not used to working with limited space constraints. I can see where a bit level approach would be better in this case. i'm not used to working within that framework. I am still figuring things out.

The 'extra' code was removed for final implementation.

Is that because of the space limitations imposed by the device and the space requirements for the String data type?

Not quite. It is because the memory for old strings is not cleaned up.

In applications running on larger machines there is a "garbage collection" that frees up memory once used by strings but no longer needed because they have been changed or are no longer in scope. Embedded system do not have this feature because it would cause random delays while the garbage is being collected which is what you don't want on an embedded system. The lack of this causes, over time, a stack / heap collision and so the whole program falls over or can do strange things.

@Grumpy_Mike
That makes perfect sense. Maybe that should have been obvious but its been a bit since I have written production level code and I've never had to function within those types of constraints.

Thanks You!