Fading Led Up and Down with predetermined intervals - Non-blocking

Hi There,

There’s lots of tutorials and examples on how to fade a led up and down without using delay() and they are great because I’ve got lots else to do in the loop without having to wait for delay(). Here is my challenge and I haven’t really found a way to do it successfully (yet):

1st - wait for a certain period time before Led fades up (called “event state = up” in my program below) w/o blocking
2nd - fade led up at a pre-determine interval without blocking
3rd - hold Led at full on state for predetermined invert without blocking
4th - fade led down at (at same rate as fade up) without blocking
5th - start over - i.e wait for next event trigger

In other words: I would like to set constants for when led fades up and how long it is on before it fades down.

I think that I’ve been over thinking this too long and I’m stuck. I think that this could be done much simpler than what I’ve put together. Below is combination of other’s sketch and some of my own:

//predetermined LED sequence - mark biasotti 11/18

const byte pwmLED = 3;
int long const event_trigger_ledup = 6000;
int long const event_trigger_ledconstant = 2000;

// How smooth to fade?
byte fadeIncrement = 10;

// How fast to increment?
int fadeInterval = 5;

// define directions for LED fade
#define UP 0
#define DOWN 1

//define states for event state
#define fadeupled 0
#define ledonconstant 1
#define fadedownled 2

// constants for min and max PWM
const int minPWM = 0;
const int maxPWM = 255;



// State Variable for Fade Direction
byte fadeDirection;
byte event_state;

// Global Fade Value
// but be bigger than byte and signed, for rollover
int fadeValue = 0;



// millis() timing Variable, just for fading
unsigned long previousFadeMillis;
unsigned long event_trigger_prevmillis_ledup = 0;
unsigned long event_trigger_prevmillis_ledon = 0;



void setup() {
  // put pwmLED into known state (off)
  analogWrite(pwmLED, fadeValue);
  Serial.begin(250000);
  Serial.println("Starting!");
}

void doTheFade(unsigned long thisMillis) {
  // is it time to update yet?
  // if not, nothing happens
  if (thisMillis - previousFadeMillis >= fadeInterval) {
    // yup, it's time!
    if (fadeDirection == UP && event_state == fadeupled) {
      fadeValue = fadeValue + fadeIncrement;
      if (fadeValue >= maxPWM) {
        // At max, limit and change direction
        fadeValue = maxPWM;
        fadeDirection = DOWN;
        event_state = ledonconstant;
      }
    }
    //  time to fade led down
    if (fadeDirection == DOWN && event_state == fadedownled) {
      fadeValue = fadeValue - fadeIncrement;
      if (fadeValue <= minPWM) {
        // At max, limit and change direction
        fadeValue = minPWM;
        fadeDirection = UP;
      }
    }
    // Only need to update when it changes
    analogWrite(pwmLED, fadeValue);
    // reset millis for the next iteration (fade timer only)
    previousFadeMillis = thisMillis;
  }
}

void loop() {
  // get the current time, for this time around loop
  // all millis() timer checks will use this time stamp
  unsigned long event_trigger_currentmillis_ledup = millis();
  if (event_trigger_currentmillis_ledup - event_trigger_prevmillis_ledup >= event_trigger_ledup) {  // triggers led into event to fade up
    event_state = fadeupled;
    event_trigger_prevmillis_ledup = event_trigger_currentmillis_ledup;
  }
  if (event_state == ledonconstant) {
    unsigned long event_trigger_currentmillis_ledon = millis();                 
    if (event_trigger_currentmillis_ledon - event_trigger_prevmillis_ledon >= event_trigger_ledconstant) {  // when on_state is reached  then timer starts and when timer triggered Fade led down is triggered
      event_state = fadedownled;
      event_trigger_prevmillis_ledon = event_trigger_currentmillis_ledon;
    }
  }

  unsigned long currentMillis = millis();
  doTheFade(currentMillis);                 // aways calling this in every loop
  Serial.print("  event_state    =   ");
  Serial.print(event_state);
  Serial.print("      fade direction =   ");
    Serial.print(fadeDirection);
    Serial.print("     led value    =   ");
    Serial.println(fadeValue);

}

Next learn Finite State Machines from simple up. State machines let you have steps/modes called cases in your code.

This tutorial is about reading serial w/o blocking. The 2nd example is state machine, it reads and reacts to text according to text already read by using a state machine to vary what the sketch does.

http://gammon.com.au/serial

Thank you GoforSmoke,

I’ve been studying that post and the other two that have recently appeared and getting quite a bit of attention. I’ve learned a lot but still struggling because all of posts, including the BwoD assume that that the multiple events (functions) are timed independently and are not dependent on a “state” or action from one of the other functions. This is where I’m struggling with timing.

I’m going to comment in the most recent post by UKHeliBob, because he is closest to to what I want to do:

1st - Wait a certain period time (off period) before fading up led
2nd - fade up led at pre-determined interval
3rd - hold led at full bright (on period) for a pre-determined interval
4th - fade led down at pre-determined interval (same interval as fadeup)
5th - Wait the certain off period and start all over again.

All of this without block. What gets tricky is that eventually I’ll want to randomly change the off period and the on period without the three timings (off, on, fade) stepping on each other.

I’ve got it mostly working but seeing timing issues (which I think are cumulative errors)

Here is my code so far. Also having an issue with the led fading down but I also think that the serial monitor interferes with the millis timing.

//predetermined LED sequence - mark biasotti 11/18

const byte pwmLED = 3;

// How smooth to fade?
byte fadeIncrement = 5;
int fadeInterval = 10;                   // How fast to increment?
long const timeledturnson = 4000 ;
long const timeledtostayon = 2000 ;


// define directions for LED fade
#define OFF 0
#define UP 1
#define HOLD 2
#define DOWN 3


// constants for min and max PWM
const int minPWM = 0;
const int maxPWM = 255;

// State Variable for Fade Direction
byte fadeDirection = UP;

// Global Fade Value
// but be bigger than byte and signed, for rollover
int fadeValue = 0;



// millis() timing Variable, just for fading
unsigned long currentMillis;
unsigned long previousledfadeup = 0;
unsigned long previousFadeMillis = 0;
unsigned long previousledstayon = 0;


void setup() {
  // put pwmLED into known state (off)
  analogWrite(pwmLED, 0);
Serial.begin(250000);
Serial.println("Starting!");
  fadeDirection = OFF;

}

void loop() {
  // get the current time, for this time around loop
  // all millis() timer checks will use this time stamp
  currentMillis = millis();
  ledfadeup();
  doTheFade();
  ledonhold();
}

void ledfadeup() {
  if (currentMillis - previousledfadeup >= timeledturnson) { //time lapses till fade direction is flagged to up
    fadeDirection = UP;
   Serial.println("turn led on");
    previousledfadeup = currentMillis;
  }
}

void ledonhold() {
  if (fadeDirection == HOLD) {
    if (currentMillis - previousledstayon >= timeledtostayon) {  // time lapses till fade direction is flagged to down
      fadeDirection = DOWN;
      previousFadeMillis = currentMillis;
Serial.println("turn led off");
      previousledstayon = currentMillis;
    }
  }
}

void doTheFade() {
  if (fadeDirection == UP) {
    if (currentMillis - previousFadeMillis >= fadeInterval) {
      fadeValue = fadeValue + fadeIncrement;
      if (fadeValue > 255) {
        fadeValue = maxPWM;
      }
      analogWrite(pwmLED, fadeValue);
Serial.println(fadeValue);
      if (fadeValue >= maxPWM) {
        fadeDirection = HOLD;
        previousledstayon = currentMillis;
      }
      previousFadeMillis = currentMillis;
    }
  }
  if (fadeDirection == DOWN) {
    if (currentMillis - previousFadeMillis >= fadeInterval) {
      fadeValue = fadeValue - fadeIncrement;
      if (fadeValue < miniPWM) {
        fadeValue = minPWM;
      }
      analogWrite(pwmLED, fadeValue);
Serial.println(fadeValue);
      if (fadeValue = minPWM) {
       previousledfadeup = currentMillis;
      }
      previousFadeMillis = currentMillis;
    }
  }
}

I’ve been studying that post and the other two that have recently appeared and getting quite a bit of attention. I’ve learned a lot but still struggling because all of posts, including the BwoD assume that that the multiple events (functions) are timed independently and are not dependent on a “state” or action from one of the other functions. This is where I’m struggling with timing.

The example tasks are to show unrelated tasks running without interfering with each other. In a loop() to loop() to loop() manner, the led tasks run in parallel all the time.

Now you have a sequential over time list, a process:

1st - Wait a certain period time (off period) before fading up led
2nd - fade up led at pre-determined interval
3rd - hold led at full bright (on period) for a pre-determined interval
4th - fade led down at pre-determined interval (same interval as fadeup)
5th - Wait the certain off period and start all over again.

In my undelay example sketch I show how to get rid of delays in old code. It’s just a technique.

// add-a-sketch_un-delay 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Apr 30/18 by GFS. Compiled on Arduino IDE 1.6.9.
// This sketch shows a general method to get rid of delays in code.
// You could upgrade code with delays to work with add-a-sketch.

#include <avr/io.h>
#include "Arduino.h"

int r,s,t; // WinXP compile jam fix
//byte a,b,c;

const byte ledPin = 13;
unsigned long delayStart, delayWait;

void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Un-Delay Example, free by GoForSmoke\n" ));
  Serial.println( F( "This sketch shows how to get rid of delays in code.\n" ));

  pinMode( ledPin, OUTPUT );
};


/* The section of the original sketch with delays:
 * 
 * digitalWrite( ledPin, HIGH );   --  0
 * delay( 500 );
 * digitalWrite( ledPin, LOW );    --  1
 * delay( 250 );
 * digitalWrite( ledPin, HIGH );   --  2
 * delay( 250 );
 * digitalWrite( ledPin, LOW );    --  3
 * delay( 250 );
 * digitalWrite( ledPin, HIGH );   --  4
 * delay( 1000 );
 * digitalWrite( ledPin, LOW );    --  5
 * delay( 1000 );
 */

byte blinkStep; // state tracking for BlinkPattern() below

void BlinkPattern()
{
  //  // start of one-shot timer
  if ( delayWait > 0 ) // one-shot timer only runs when set
  {
    if ( millis() - delayStart < delayWait )
    {
      return; // instead of blocking, the undelayed function returns
    }
    else
    {
      delayWait = 0; // time's up! turn off the timer and run the blinkStep case
    }
  }
  // end of one-shot timer

  // here each case has a timed wait but cases could change Step on pin or serial events.
  switch( blinkStep )  // runs the case numbered in blinkStep
  {
    case 0 :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "Case 0 doing something unspecified here at " ));
    Serial.println( delayStart = millis()); // able to set a var to a value I pass to function
    delayWait = 500; // for the next half second, this function will return on entry.
    blinkStep = 1;   // when the switch-case runs again it will be case 1 that runs
    break; // exit switch-case

    case 1 :
    digitalWrite( ledPin, LOW );
    Serial.println( F( "Case 1 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    blinkStep = 2;
    break;

    case 2 :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "Case 2 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    blinkStep = 3;
    break;

    case 3 :
    digitalWrite( ledPin, LOW );
    Serial.println( F( "Case 3 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    blinkStep = 4;
    break;

    case 4 :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "Case 4 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 1000;
    blinkStep = 5;
    break;

    case 5 :
    digitalWrite( ledPin, LOW );
    Serial.print( F( "Case 5 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 1000;
    blinkStep = 0;
    break;
  }
}


void loop()  // runs over and over, see how often
{            
  BlinkPattern();
}

mbiasotti:
I’m going to comment in the most recent post by UKHeliBob, because he is closest to to what I want to do:

um… UKHeilBob has not posted on this thread…

Hello GoforSmoke,

Thanks for the reply and for posting your sketch. What I failed to mention is that the led sequence is not the only function I'm planning for my program. I'll study what you posted here and see if it helps my situation.

thanks

GoforSmoke,

I've had a chance to study your switch case example for non-blocking delay. Unfortunately I cannot get my head around it when it comes to fading a led.

It turns out that my Led Sequence is not entirely sequential since the led wants to fade up and down incrementally when the event timer is called for. If you simply do a led_value++; and then analog.Write(ledPin, led_value); it has to wait for the other cases before getting back around to incrementing the led brightness.

So as far as triggering the led to fade up and then to hold for a certain amount of time at full brightness then trigger to fade down - yes they are all sequential, but the led-fed increment (timing) needs to happen independently of previous stated sequence.

Also, remember I have that other independent function that I still need to right into my sketch using the mills approach. I could use your case approach for triggering the led to fade up, hold fully on, and fade down triggers, but for the fading and the other function, perhaps I should use mills approach in conjunction in the loop.

Thanks

mbiasotti:
GoforSmoke,

I've had a chance to study your switch case example for non-blocking delay. Unfortunately I cannot get my head around it when it comes to fading a led.

It turns out that my Led Sequence is not entirely sequential since the led wants to fade up and down incrementally when the event timer is called for. If you simply do a led_value++; and then analog.Write(ledPin, led_value); it has to wait for the other cases before getting back around to incrementing the led brightness.

It doesn't have to. You could change the case code so that state only changes when the fade is done. Until then, that same case runs over and over. You can make it only fade one step every 1 ms and the led will go dark to bright in 1/4 second, only 255 steps. So perhaps fade 1 step up or down every 4 ms.

So as far as triggering the led to fade up and then to hold for a certain amount of time at full brightness then trigger to fade down - yes they are all sequential, but the led-fed increment (timing) needs to happen independently of previous stated sequence.

Also, remember I have that other independent function that I still need to right into my sketch using the mills approach. I could use your case approach for triggering the led to fade up, hold fully on, and fade down triggers, but for the fading and the other function, perhaps I should use mills approach in conjunction in the loop.

Thanks

You have a process. The state variable is "process state". The cases are what is done at each state.
Any case code may change the state to any other but does not have to change the state at all.

That sketch is part of a set all made to work and play well with other non-blocking code.
They are made to show adding functionality as a task developed in another sketch.
I have a loopcounter example to add the function to other non-blocking void loop() and show average loop speed.
Adding loopcounter lets you see change in speed afterwards, see if some code is dragging.

// add-a-sketch_loop_counter 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Apr 29/2018 by GFS. Compiled on Arduino IDE 1.6.9.
// This sketch counts times that loop has run each second and prints it.
// It uses the void LoopCounter() function that does not block other code.

#define microsInOneSecond 1000000UL

void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Loop Counter, free by GoForSmoke\n" ));
  Serial.println( F( "This sketch counts times that loop has run each second and prints it." ));
}

void LoopCounter() // tells the average response speed of void loop()
{ // inside a function, static variables keep their value from run to run
  static unsigned long count, countStartMicros; // only this function sees these

  count++; // adds 1 to count after any use in an expression, here it just adds 1.
  if ( micros() - countStartMicros >= microsInOneSecond ) // 1 second
  {
    countStartMicros += microsInOneSecond; // for a regular second
    Serial.println( count ); // 32-bit binary into decimal text = many micros
    count = 0; // don't forget to reset the counter 
  }
}

void loop()  // runs over and over, see how often with LoopCounter()
{
  LoopCounter(); // the function runs as a task, the optimizer will inline the code.
}

Hello Goforsmoke,

I’ve taken a look at your case approach and applied it to the led sequence that I would like to do. following is how I’ve adapted it. My understanding of how switch case works is that when the BlinkPattern() function calls a particular case in the sequence it executes what is in the case and then goes to the next. If the time delay is not due for that case, the next time it comes around, it does not execute what is in the case - right? So, below is what I’ve tried and it doesn’t work yet. I’m getting hung up on case # 2 which is to hold the led at full brightness. I’m not sure how to do this since I need to somehow go to the next case which is to fade the led down and I realize that using “else” isn’t really going to work here? (just using the onboard LED for now).

// add-a-sketch_un-delay 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Apr 30/18 by GFS. Compiled on Arduino IDE 1.6.9.
// This sketch shows a general method to get rid of delays in code.
// You could upgrade code with delays to work with add-a-sketch.

#include <avr/io.h>
#include "Arduino.h"

int r, s, t; // WinXP compile jam fix
//byte a,b,c;

const byte ledPin = 13;
unsigned long delayStart, delayWait;

const byte minPWM = 0;
const byte maxPWM = 255;

byte fade_value = 0;

byte event_state;
byte fade_direction;

#define OFF  0
#define UP  1
#define ON  2
#define DOWN  3
#define HOLD 4

void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Un-Delay Example, free by GoForSmoke\n" ));
  Serial.println( F( "This sketch shows how to get rid of delays in code.\n" ));

  pinMode( ledPin, OUTPUT );
};

byte blinkStep; // state tracking for BlinkPattern() below

void BlinkPattern()
{
  //  // start of one-shot timer
  if ( delayWait > 0 ) // one-shot timer only runs when set
  {
    if ( millis() - delayStart < delayWait )
    {
      return; // instead of blocking, the undelayed function returns
    }
    else
    {
      delayWait = 0; // time's up! turn off the timer and run the blinkStep case
    }
  }
  // end of one-shot timer

  // here each case has a timed wait but cases could change Step on pin or serial events.
  switch ( blinkStep ) // runs the case numbered in blinkStep
  {
    case 0 :                            // this case starts the sequence
      event_state = ON;
      Serial.print( F( "starting sequence - turning led on " ));
      Serial.println( delayStart = millis()); // able to set a var to a value I pass to function
      delayWait = 4000; //                // this millis controls how long between led is off then on
      blinkStep = 1;   // when the switch-case runs again it will be case 1 that runs
      break; // exit switch-case

    case 1 :                            // this case handles the fading of the LED
      if (event_state == ON && fade_direction == UP) {
        fade_value++;
        if (fade_value >= maxPWM) {
          event_state = HOLD;           // changes event state to hold led full bright
        }
      }
      if (event_state == OFF && fade_direction == DOWN) {
        fade_value--;
        if (fade_value <= minPWM) {
          event_state = ON;               // toggles back to beginning of sequence and waits
        }
      }
      analogWrite(ledPin, fade_value);

      Serial.print( F( "fading the led up or down and value is =   " ));
      Serial.print(fade_value);
      Serial.print("milliseconds =  ");
      Serial.println( delayStart = millis());
      delayWait = 50;                      // this millis constrols speed of fade
      blinkStep = 2;
      break;

    case 2 :                           // this cases handles how long the led is on
      if (event_state == HOLD)  {
        analogWrite(ledPin, fade_value);
      }
      else {
        event_state = OFF;
      }
      Serial.println(" led is full bright for this amount of time =   ");
      Serial.println( delayStart = millis());
      delayWait = 1000;                  // keeps the Led on for 1 second
      blinkStep = 3;
      break;

    case 3 :
      // digitalWrite( ledPin, LOW );   // reseved for future functionality
      // Serial.println( F( "Case 3 doing something unspecified here at " ));
      // Serial.println( delayStart = millis());
      delayWait = 1;
      blinkStep = 4;
      break;

    case 4 :
      // digitalWrite( ledPin, HIGH );   // reserved for future functionality
      // Serial.println( F( "Case 4 doing something unspecified here at " ));
      // Serial.println( delayStart = millis());
      delayWait = 1;
      blinkStep = 5;
      break;
  }
}


void loop()  // runs over and over, see how often
{
  BlinkPattern();
}

perhaps I need to toggle case # 2 something like this:

// add-a-sketch_un-delay 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Apr 30/18 by GFS. Compiled on Arduino IDE 1.6.9.
// This sketch shows a general method to get rid of delays in code.
// You could upgrade code with delays to work with add-a-sketch.

#include <avr/io.h>
#include "Arduino.h"

int r, s, t; // WinXP compile jam fix
//byte a,b,c;

const byte ledPin = 13;
unsigned long delayStart, delayWait;

const byte minPWM = 0;
const byte maxPWM = 255;

byte fade_value = 0;

byte event_state;
byte fade_direction;

#define OFF  0
#define UP  1
#define ON  2
#define DOWN  3
#define HOLD 4

void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Un-Delay Example, free by GoForSmoke\n" ));
  Serial.println( F( "This sketch shows how to get rid of delays in code.\n" ));
  fade_direction = UP;              // starting out with fade up led when turned on
  event_state = ON;                 // starting out with led turning on immediately
  pinMode( ledPin, OUTPUT );
};

byte blinkStep; // state tracking for BlinkPattern() below

void BlinkPattern()
{
  //  // start of one-shot timer
  if ( delayWait > 0 ) // one-shot timer only runs when set
  {
    if ( millis() - delayStart < delayWait )
    {
      return; // instead of blocking, the undelayed function returns
    }
    else
    {
      delayWait = 0; // time's up! turn off the timer and run the blinkStep case
    }
  }
  // end of one-shot timer

  // here each case has a timed wait but cases could change Step on pin or serial events.
  switch ( blinkStep ) // runs the case numbered in blinkStep
  {
    case 0 :                            // this case starts the sequence
      event_state = ON;
      Serial.print( F( "starting sequence - turning led on " ));
      Serial.println( delayStart = millis()); // able to set a var to a value I pass to function
      delayWait = 4000; //                // this millis controls how long between led is off then on
      blinkStep = 1;   // when the switch-case runs again it will be case 1 that runs
      break; // exit switch-case

    case 1 :                            // this case handles the fading of the LED
      if (event_state == ON ) {
        fade_value++;
        if (fade_value >= maxPWM) {
          fade_value = maxPWM;
          fade_direction == DOWN;
          event_state = HOLD;          // changes event state to hold led full bright
        }
      }
      if (event_state == OFF) {
        fade_value--;
        if (fade_value <= minPWM) {
          fade_value = minPWM;
          event_state = ON;               // toggles back to beginning of sequence and waits
          fade_direction = UP;
        }
      }
      analogWrite(ledPin, fade_value);    // writes the fade value to the led

      Serial.print( F( "fading the led up or down and value is =   " ));
      Serial.print(fade_value);
      Serial.print("milliseconds =  ");
      Serial.println( delayStart = millis());
      delayWait = 50;                      // this millis controls speed of fade
      blinkStep = 2;
      break;

    case 2 :                           // this cases handles how long the led is on
      if (event_state == HOLD)  {
        analogWrite(ledPin, maxPWM);
        event_state = OFF;               //  toggles for next time thru case 2 doesn't execute
      }
      delayWait = 1000;                  // keeps the Led on for 1 second
      Serial.print(" led is full bright for this amount of time =   ");
      Serial.println(delayWait);
      Serial.print("amount of time passed =  ");
      Serial.println( delayStart = millis());
      blinkStep = 3;
      break;

    case 3 :
      // digitalWrite( ledPin, LOW );   // reserved for future functionality
      // Serial.println( F( "Case 3 doing something unspecified here at " ));
      // Serial.println( delayStart = millis());
      delayWait = 1;
      blinkStep = 4;
      break;

    case 4 :
      // digitalWrite( ledPin, HIGH );   // reserved for future functionality
      // Serial.println( F( "Case 4 doing something unspecified here at " ));
      // Serial.println( delayStart = millis());
      delayWait = 1;
      blinkStep = 5;
      break;
  }
}


void loop()  // runs over and over, see how often
{
  BlinkPattern();
}

Umm, no.

The C structure is switch-case like how there are if-else and for-loop structures.

I even commented the code to say that blinkStep decides which case runs. It is the process state, the blink step.

  switch ( blinkStep ) // runs the case numbered in blinkStep
  {
    case 0 :                            // this case starts the sequence
      event_state = ON;
      Serial.print( F( "starting sequence - turning led on " ));
      Serial.println( delayStart = millis()); // able to set a var to a value I pass to function
      delayWait = 4000; //                // this millis controls how long between led is off then on
      blinkStep = 1;   // when the switch-case runs again it will be case 1 that runs
      break; // exit switch-case

    case 1 :                            // this case handles the fading of the LED

The only way for the code in a case to run is if blinkStep has the case number in it.
Switch-case is a choice structure faster than a chain of if-else-if-else-if-else…

When what I wrote runs with a loop counter, void loop() shows to run over 50K/second. Brightness change > 50 times per millisecond is an awful quick ‘fade’.

Timing with Arduino is about time marks where you save millis or micros to mark an event like the start of a fade half-cycle.

Unsigned time difference = end time - start time works up to the limit of the time variables used, millis() can do 49.71… days.

If I want to fade 0 to 255 over 1 second, I’ll use microseconds for scale – 3906 micros each of 256 steps.

case F :
if ( fadeValue < 255 )
{
fadeValue = (( micros() - start time ) / 3906 ); // in about a second it reaches 255
analogWrite( pin, fadeValue );
}
else
{
// set the wait timer and start time and change the switch (state) to the next case
}
break;

Since you have a list of states, I’m guessing that a state machine would be a good start:

enum FaderStates
{
  IDLE,                     // Waiting for a trigger?
  PAUSE_BEFORE_FADE_UP,    // 1st - Wait a certain period time (off period) before fading up led
  FADE_UP,                 // 2nd - fade up led at pre - determined interval
  HOLD_AT_FULL_BRIGHTNESS, // 3rd - hold led at full bright (on period) for a pre - determined interval
  FADE_DOWN,               // 4th - fade led down at pre - determined interval (same interval as fadeup)
  HOLD_AT_OFF              // 5th - Wait the certain off period and start all over again.
};

Then I would put all of the data into a ‘struct’:

struct Fade
{
  byte LEDPin;
  byte maxBrightness;
  unsigned long startDelayInterval;
  unsigned long fadeUpInterval;
  unsigned long maxBrightnessHoldInterval;
  unsigned long fadeDownInterval;
  unsigned long minBrightnessHoldInterval;
};

Then you can create an array of struct Fade (if you have more than on LED doing fades) and in loop() call a function to move the fade forward one step.

const byte RedLEDPin = 5;
const byte GreenLEDPin = 6;
const byte BlueLEDPin = 6;

const byte MaxBrightness = 250; // Tone it down a bit

enum FaderStates
{
  IDLE,                     // Waiting for a trigger?
  PAUSE_BEFORE_FADE_UP,    // 1st - Wait a certain period time (off period) before fading up led
  FADE_UP,                 // 2nd - fade up led at pre - determined interval
  HOLD_AT_FULL_BRIGHTNESS, // 3rd - hold led at full bright (on period) for a pre - determined interval
  FADE_DOWN,               // 4th - fade led down at pre - determined interval (same interval as fadeup)
  HOLD_AT_OFF              // 5th - Wait the certain off period and start all over again.
};

struct Fade
{
  enum FaderStates state;
  unsigned long currentStateStartTime;

  const byte LEDPin;
  const byte maxBrightness;
  const unsigned long startDelayInterval;
  const unsigned long fadeUpInterval;
  const unsigned long maxBrightnessHoldInterval;
  const unsigned long fadeDownInterval;
  const unsigned long minBrightnessHoldInterval;
};

struct Fade Fades[] =
{
  {IDLE, 0UL, RedLEDPin, MaxBrightness, 1000UL, 1000UL, 1000UL, 1000UL, 1000UL},
  {IDLE, 0UL, GreenLEDPin, MaxBrightness, 1100UL, 900UL, 900UL, 1100UL, 1100UL},
  {IDLE, 0UL, BlueLEDPin, MaxBrightness, 1200UL, 1000UL, 1200UL, 1000UL, 900UL},
};
const byte FadeCount = sizeof Fades / sizeof Fades[0];

void DoFade(struct Fade &fade)
{
  unsigned long currentTime = millis();
  unsigned long elapsed;

  switch (fade.state)
  {
    case   IDLE:  // Waiting for a trigger?
      // No trigger to wait for?  Just go on to the next state
      fade.currentStateStartTime = currentTime;
      fade.state = PAUSE_BEFORE_FADE_UP;
      break;

    case   PAUSE_BEFORE_FADE_UP:    // 1st - Wait a certain period time (off period) before fading up led
      if (currentTime - fade.currentStateStartTime >= fade.startDelayInterval)
      {
        fade.currentStateStartTime = currentTime;
        fade.state = FADE_UP;
      }
      break;

    case   FADE_UP:                 // 2nd - fade up led at pre - determined interval
      elapsed = currentTime - fade.currentStateStartTime;
      if (elapsed <= fade.fadeUpInterval)
      {
        byte brightness = (fade.maxBrightness * fade.fadeUpInterval) / elapsed;
        analogWrite(fade.LEDPin, brightness);
      }
      else
      {
        analogWrite(fade.LEDPin, 255);

        // Go on to next state
        fade.currentStateStartTime = currentTime;
        fade.state = HOLD_AT_FULL_BRIGHTNESS;
      }
      break;

    case   HOLD_AT_FULL_BRIGHTNESS: // 3rd - hold led at full bright (on period) for a pre - determined interval
      if (currentTime - fade.currentStateStartTime >= fade.maxBrightnessHoldInterval)
      {
        fade.currentStateStartTime = currentTime;
        fade.state = FADE_DOWN;
      }
      break;

    case   FADE_DOWN:               // 4th - fade led down at pre - determined interval (same interval as fadeup)
      elapsed = currentTime - fade.currentStateStartTime;
      if (elapsed <= fade.fadeUpInterval)
      {
        byte brightness = (fade.maxBrightness * fade.fadeUpInterval) / elapsed;
        analogWrite(fade.LEDPin, fade.maxBrightness - brightness);
      }
      else
      {
        analogWrite(fade.LEDPin, 0);

        // Go on to next state
        fade.currentStateStartTime = currentTime;
        fade.state = HOLD_AT_OFF;
      }
      break;

    case   HOLD_AT_OFF:
      if (currentTime - fade.currentStateStartTime >= fade.minBrightnessHoldInterval)
      {
        fade.currentStateStartTime = currentTime;
        fade.state = IDLE;
      }
      break;
  }
}

void setup()
{
  for (int i = 0; i < FadeCount; i++)
  {
    pinMode(Fades[i].LEDPin, OUTPUT);
  }
}

void loop()
{
  for (int i = 0; i < FadeCount; i++)
  {
    DoFade(Fades[i]);
  }
}

I love John’s code above. But it doesn’t respond to external events. The OP said he wanted events although I can’t see anywhere he told us what the events might be.

The following is a direct copy from above except for three things:

  1. Each LED waits in the idle state for an event.
  2. I added 2 buttons to allow you to test events that come in at random times. One red, one green. If the LED is already fading then the button is ignored, like a de-bounce.
  3. I added some more rules: when either the red or green LEDs reach the end of their cycle, the blue LED fades up for one cycle regardless of if it is already in the middle of a cycle.
  4. I shortened the pause-before for all LEDs so it would be more responsive.
const byte RedButtonPin = 2;
const byte GreenButtonPin = 3;
const byte RedLEDPin = 5;
const byte GreenLEDPin = 6;
const byte BlueLEDPin = 6;

const byte MaxBrightness = 250; // Tone it down a bit

enum FaderStates
{
  IDLE,                     // Waiting for a trigger
  PAUSE_BEFORE_FADE_UP,    // 1st - Wait a certain period time (off period) before fading up led
  FADE_UP,                 // 2nd - fade up led at pre - determined interval
  HOLD_AT_FULL_BRIGHTNESS, // 3rd - hold led at full bright (on period) for a pre - determined interval
  FADE_DOWN,               // 4th - fade led down at pre - determined interval (same interval as fadeup)
  HOLD_AT_OFF              // 5th - Wait the certain off period and start all over again.
};

struct Fade
{
  enum FaderStates state;
  unsigned long currentStateStartTime;

  const byte LEDPin;
  const byte maxBrightness;
  const unsigned long startDelayInterval;
  const unsigned long fadeUpInterval;
  const unsigned long maxBrightnessHoldInterval;
  const unsigned long fadeDownInterval;
  const unsigned long minBrightnessHoldInterval;
};

struct Fade Fades[] =
{
  {IDLE, 0UL, RedLEDPin, MaxBrightness, 10UL, 1000UL, 1000UL, 1000UL, 1000UL},
  {IDLE, 0UL, GreenLEDPin, MaxBrightness, 10UL, 900UL, 900UL, 1100UL, 1100UL},
  {IDLE, 0UL, BlueLEDPin, MaxBrightness, 10UL, 1000UL, 1200UL, 1000UL, 900UL},
};
const byte FadeCount = sizeof Fades / sizeof Fades[0];
const byte RefFade = 0;
const byte GreenFade = 1;
const byte BlueFade = 2;


void DoFade(struct Fade &fade)
{
  unsigned long currentTime = millis();
  unsigned long elapsed;

  switch (fade.state)
  {
    case   IDLE:  // Waiting for a trigger
      break;

    case   PAUSE_BEFORE_FADE_UP:    // 1st - Wait a certain period time (off period) before fading up led
      if (currentTime - fade.currentStateStartTime >= fade.startDelayInterval)
      {
        fade.currentStateStartTime = currentTime;
        fade.state = FADE_UP;
      }
      break;

    case   FADE_UP:                 // 2nd - fade up led at pre - determined interval
      elapsed = currentTime - fade.currentStateStartTime;
      if (elapsed <= fade.fadeUpInterval)
      {
        byte brightness = (fade.maxBrightness * fade.fadeUpInterval) / elapsed;
        analogWrite(fade.LEDPin, brightness);
      }
      else
      {
        analogWrite(fade.LEDPin, 255);

        // Go on to next state
        fade.currentStateStartTime = currentTime;
        fade.state = HOLD_AT_FULL_BRIGHTNESS;
      }
      break;

    case   HOLD_AT_FULL_BRIGHTNESS: // 3rd - hold led at full bright (on period) for a pre - determined interval
      if (currentTime - fade.currentStateStartTime >= fade.maxBrightnessHoldInterval)
      {
        fade.currentStateStartTime = currentTime;
        fade.state = FADE_DOWN;
      }
      break;

    case   FADE_DOWN:               // 4th - fade led down at pre - determined interval (same interval as fadeup)
      elapsed = currentTime - fade.currentStateStartTime;
      if (elapsed <= fade.fadeUpInterval)
      {
        byte brightness = (fade.maxBrightness * fade.fadeUpInterval) / elapsed;
        analogWrite(fade.LEDPin, fade.maxBrightness - brightness);
      }
      else
      {
        analogWrite(fade.LEDPin, 0);

        // Go on to next state
        fade.currentStateStartTime = currentTime;
        fade.state = HOLD_AT_OFF;
      }
      break;

    case   HOLD_AT_OFF:
      if (currentTime - fade.currentStateStartTime >= fade.minBrightnessHoldInterval)
      {
        fade.currentStateStartTime = currentTime;
        fade.state = IDLE;
      }
      break;
  }
}

void setup()
{
  for (int i = 0; i < FadeCount; i++)
  {
    pinMode(Fades[i].LEDPin, OUTPUT);
  }
  pinMode(RedButtonPin, INPUT_PULLUP);
  pinMode(GreenButtonPin, INPUT_PULLUP);
}

void loop()
{
  //read buttons
  if(digitalRead(RedButtonPin)==LOW)
  {
    if(Fades[RedFade].state==IDLE) Fades[RedFade].state = PAUSE_BEFORE_FADE_UP;
  }
  if(digitalRead(GreenButtonPin)==LOW)
  {
    if(Fades[GreenFade].state==IDLE) Fades[GreenFade].state = PAUSE_BEFORE_FADE_UP;
  }

  //other transitions: follow every red or green with a blue
if(Fades[RedFade].state==HOLD_AT_OFF || Fades[GreenFade].state==HOLD_AT_OFF) 
  {
  Fades[BlueFade].state = PAUSE_BEFORE_FADE_UP;
  }

  //now actually light the LEDs...
  for (int i = 0; i < FadeCount; i++)
  {
    DoFade(Fades[i]);
  }
}
  if(digitalRead(GreenButtonPin)==LOW)
  {
    if(Fades[GreenFade].state==IDLE) Fades[GreenFade].state = PAUSE_BEFORE_FADE_UP;
  }

That won't work properly because the currentStateStartTime isn't set. You can fix it with:

  if (Fades[GreenFade].state == IDLE && digitalRead(GreenButtonPin) == LOW)
  {
   Fades[GreenFade].currentStateStartTime = millis();
   Fades[GreenFade].state = PAUSE_BEFORE_FADE_UP;
  }

A lot to catch up on here guys... and I will, I'm just tied up at the moment. Thank you for contributing to the discussion.

3 weeks ago, I thought that fading up and down a LED would be a no-brainer sketch to write but as it turns out, this is difficult. I'm a newbie but I've written other sophisticated LED sketches for a few other projects, and they were non-blocking but they just cycled thru from color to color using the FastLed library.

Where I'm going with this sketch is that eventually I want to randomize the on and hold times as well as turn on and off a motor.

It may be presumptuous of me but newbie's or casual Arduino programmers would love to see something like the following where you have a delay function with two arguments, the function that it is currently part of (scope) and the delay time:

delaywoB(function name, time);

It would make programming so much easier.

But what would that delaywoB() do? If it doesn't block then it is not a delay.

There are other libraries like TimeAlarms and Interval that will execute a function after a desired time.

If you want to add buttons then add a task to handle the buttons. I have examples for button, buttons and button matrix that can import into the undelay sketch. Each button has a status byte that tasks in the sketch can read and act upon. The buttons are handled in parallel with the state machine. It’s a lot easier that way than the one big knot of code method especially when it comes to non-blocking code.

You can put a state machine inside of a state machine. I’ve done that to analyze text on PCs before.

Here is multiple buttons example:

// add-a-sketch_buttons 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, May 6/2018 by GFS. Compiled on Arduino IDE 1.6.9.
/*  Button Debounce Example

  --- for this example connect a button between pin 7 and GND
  --- or stick a jumper in pin 7 and while holding the board steady
  --- tap the free end onto the grounded USB port box to press.

  Yes I'm using a 16 bit micros timer to time fractions of millis as micros.
  The button reader only reads 1 button per call so as to not block void loop().
  Each button has a history byte that holds the last 8 reads with 256 possible
  states but only 4 of them being significant.
  0 is the button held down
  255 is the button left up
  127 is the buton transitioning from up to down, button just released.
  128 is the button transititioning from down to up, button just pressed.
  everything else is to be ignored as bounce.

  For multiple buttons on 1:1 pins the between-reads time is reduced and each
  pin is read in turn. This demo makes pin 0 be ON-RESET/OFF and pin 1 adjusts
  blink time.
*/

// multi_buttons vars
const byte buttons = 2;
byte buttonIndex, lastIndex; // only process 1 button every waitButtonTime micros
byte buttonPin[ buttons ] = { 6, 7 };
byte buttonHistory[ buttons ];
word markButtonTime;        // 16-bit micros timers
const word waitButtonTime = 250; // micros, 20 to 500 / more to fewer buttons.
// type word as micros can time across 65.535 millis before rollover, can be a few late

// added sketch task, on-off blinker vars
byte ledState, ledPin = 13; // use byte for small values, int cost 2 bytes
word startBlink, waitBlink; // 16 bit millis is good to time 65.535 seconds


void multiButtonsTask()
{ // read twice per milli, bits 0 to 6 all same sets the state
  if ( word( micros()) - markButtonTime >= waitButtonTime ) // read occaisoinally
  {
    buttonHistory[ buttonIndex ] <<= 1; // if you don't know <<= look it up in the Arduino Reference
    // keep a browser open to that page when you use the IDE.
    buttonHistory[ buttonIndex ] += digitalRead( buttonPin[ buttonIndex ] ); // read history streams through buttonHistory
    markButtonTime = micros(); // gets the low 16 bits of micros(), time to 60 ms + margin
  }

  // ++buttonIndex pre-increments buttonIndex before comparing to buttons
  if ( ++buttonIndex >= buttons )   buttonIndex = 0;
}

void OnOffBlinker() // only blinks if there's a wait time, can be switched on/off
{
  if ( waitBlink > 0 ) // this is the on/off switch
  {
    // word( millis()) gets the low 16 bits of the 32-bit millis() return.
    if ( word( millis()) - startBlink >= waitBlink ) // difference in time by subtracting start from end
    {
      ledState = !ledState;  // ! is NOT: not_0/true becomes 0/false else 0 becomes 1.
      digitalWrite( ledPin, ledState ); // the led changes state.
      startBlink += waitBlink; // next blink starts when it should, where diff > wait.
    }
  }
  else if ( ledState > 0 ) // waitBlink == 0 turns blinking off
  {
    digitalWrite( ledPin, ledState = LOW ); //  make sure the led is OFF
  } // yes, you can set a variable during calculation in C, the write here is LOW.
}


void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Multi-Buttons Example, free by GoForSmoke\n" ));

  pinMode( ledPin, OUTPUT );

  for ( byte i = 0; i < buttons; i++ )
  {
    pinMode( buttonPin[ i ], INPUT_PULLUP );
    buttonHistory[ i ] = 255;
  }
};


void loop()
{
  multiButtonsTask();
  /*
    0 is the button held down
    255 is the button left up
    127 is the buton changing from up to down, button just released.
    128 is the button changing from down to up, button just pressed.
    everything else is to be ignored as bounce.
  */

  if ( buttonIndex != lastIndex )
  {
    lastIndex = buttonIndex;

    switch ( buttonHistory[ buttonIndex ] ) // buttonHistory does not change as fast as this runs
    {
      case 128 : // pin is HIGH in bit 7, LOW for 7 reads, up to down detected
        buttonHistory[ buttonIndex ] = 0; // change detected, make it into no change now
        Serial.print( F( "button " ));
        Serial.print( buttonIndex );
        Serial.print( F( "  press detected     " ));
        Serial.println( millis());
        if ( buttonIndex == 0 )
        {
          if ( waitBlink == 0 ) // toggle action tied to button press
          {
            waitBlink = 500; // makes the blinking start
            startBlink = millis(); // gets the low 16 bits
          }
          else // press button 2 changes the blink rate
          {
            waitBlink = 0; // makes the blinking stop
          }
        }
        else
        {
          if (( waitBlink -= 100 ) < 100 ) // setting a var in an expression is okay C
          {
            waitBlink = 1000;
          }
        }
        break;
      case 127 : // pin is LOW in bit 7, HIGH for 7 reads, down to up detected
        buttonHistory[ buttonIndex ] = 255; // change detected, make it into no change now
        Serial.print( F( "button " ));
        Serial.print( buttonIndex );
        Serial.print( F( "  release detected   " ));
        Serial.println( millis());
        break;
    }
  }

  OnOffBlinker();
}

MorganS:
But what would that delaywoB() do? If it doesn't block then it is not a delay.

I think the OP is asking for a multi-tasking operating system with non-preemptive scheduling. This would allow each task to use a function to turn control over to some other process.

Perhaps one of these would do the trick:

https://playground.arduino.cc/Code/TaskScheduler

The main problem on a basic Arduino UNO is having enough stack space for each of the processes.

Just because there is a cycle-wasting function named delay() does make every use of the word delay to mean the same thing.

But I use the wait where "delay running this until later" would do.

unsigned long startTime, waitTime;

Cooperative tasking that keeps each task's run time short does work until too many tasks are added. I use a loop counter to keep track of that and suggest splitting tasks off to multiple controllers to do more.

@johnwasser - yeah, I like a recent article that came out from Make Magazine title: "Arduino or Raspberry Pi: which board to choose?"

"Yes, there is! Think about what you want your project to do. If you can describe it with less than two ‘and’s, get an Arduino. If you need more than two ‘and’s, get a Raspberry Pi.

Examples:
“I want to monitor my plants and have them Tweet me when they need water.” That can best be done by an Arduino.

“I want to monitor my plants and have them Tweet me when they need water and check the National Weather Service, and if the forecast is for fair weather, turn on the irrigation system and if the forecast is for rain, do nothing.” That would best be handled by a Raspberry Pi."