Demonstration code for several things at the same time

Months ago I saw this thread but since the principle is the same as what Nick Gammon covers very will in his blog (that explains blocking code and other basic concepts in a slow-pitch way), I just linked people to that.

If you haven't seen, this is Nock's (edit, errr Nick's) tutorial:

Note to self: start using the new keyboard that doesn't have half the letters rubbed off.

Not long ago I got back and learned one very good lesson from Robin2 (karma to you, Robin) of not updating start time to millis() in a time check that may be late but rather adding the interval to the start which is ALWAYS right.

So this time I actually looked the code over and I see something that I have done very often before, especially when under the gun, but that does not make it good practice at all. In fact, even as beginners we are reminded to not do this thing.
And what is that?
Copy&paste then modify to suit code multiplication. Because it works and what works is good, right? Wrong.

Do I want to add a block of code for every led I add? No. That's why there's arrays, etc.

I had a pretty good day today, healthwise, so I dug out the breadboard, parts boxes and magnifier to do something.

Here is Blink Multiple Leds Without Delay in 2 versions.
1st is the short version that I count as 24 lines of actual code (not counting braces).
2nd is a longer teaching version with loads of comments and serial report options. 1st is the short version that I count as 24 lines of actual code (not counting braces).

Mess with it. Ask questions. I had all that addressed then the forum timed me out and poof, all that is gone!
Add leds. If you want more than 8 then change ledState to unsigned int or long.

Short version:

//  Blink Multiple leds With Uneven Delays, Short Version

const byte    LEDS = 4;
const byte    ledPin[ LEDS ] = // I use Autoformat. It won't let this be 1 line.
{ 
  13, 3, 5, 7 
};

byte          ledState = 0; // this will handle 8 led states as bits
byte          led = 0; // this example processes 1 led per pass through loop()

unsigned int  millisNow; // fill with the lower 16 bits of millis()
unsigned int  startBlink[ LEDS ]; // these all default to zero 
unsigned int  waitBlink[ LEDS ][ 2 ]; // one for OFF time, one for ON time

void setup()
{
  for ( led = 0; led < LEDS; led++ )
  {
    waitBlink[ led ][ 0 ] = 250U * ((unsigned int) led + 1U ); // OFF millis 
    waitBlink[ led ][ 1 ] = 10U * ((unsigned int) led + 1U ); // ON millis

    pinMode( ledPin[ led ], OUTPUT ); // default output is LOW
    digitalWrite( ledPin[ led ], bitRead( ledState, led ));
  }  

  led = 0;
}

void loop()
{
  millisNow = (unsigned int)( millis() & 0xFFFF ); // lower 16 bits roll over just fine

  if ( millisNow - startBlink[ led ] >= waitBlink[ led ][ bitRead( ledState, led ) ] )  
  {
    startBlink[ led ] += waitBlink[ led ][ bitRead( ledState, led ) ]; // update start time

    bitWrite( ledState, led, ( bitRead( ledState, led ) == 0 )); // change led state bit
    digitalWrite( ledPin[ led ], bitRead( ledState, led ) ); // change the led pin  
  }

  led++;
  if ( led >= LEDS )
  {
    led = 0;
  }
}

Long 'learning' version with serial reporting options.

//  Blink Multiple leds With Uneven Delays, Serial reporting optional

/*
The hardest part of this was getting the debug/report prints to not screw the timing
by more than 1 milli. I even left in some commented-out lines just for comparison.
Serial is not free, it blocks execution!
*/

// make SERPRINT 1 to enable serial preorti
#define  SERPRINT  1

// #if SERPRINT ---- if I leave this in, the sketch won't compile
byte runBeforeStop = 199; // to limit serial print lines set this to < 200
//#endif

const byte    LEDS = 4;
const byte    ledPin[ LEDS ] = // I use Autoformat. It won't let this be 1 line.
{ 
  13, 3, 5, 7 
};

byte          ledState = 0; // this will handle 8 led states as bits
//            to track more than 8 leds, use larger unsigned integers than byte
byte          led = 0; // this example processes 1 led per pass through loop()

unsigned int  millisNow; // fill with the lower 16 bits of millis()
unsigned int  startBlink[ LEDS ]; // these all default to zero 
unsigned int  waitBlink[ LEDS ][ 2 ]; // one for OFF time, one for ON time
//               using the low 16 bits of millis allows 65.535 second blink 
//               however if you need longer, change these to unsigned long
//          you can initialize this array as const or use PROGMEM to store it
//          I chose to use a formula. With a different formula I could get rid
//          of the whole array and use one or more variables to run the formula.

// This is code. Make your own way all you *can*! More you do, better you get.
// And note, this mess just begs to be turned into a Class Objects example.

void setup()
{
#if SERPRINT
  Serial.flush();
  Serial.begin( 9600 );
  Serial.println( "\nMulti-led blink test\n" ); 
#endif

  for ( led = 0; led < LEDS; led++ )
  {
#if SERPRINT
    Serial.print( " Led " ); 
    Serial.print( led, DEC ); 
    Serial.print( " >> " ); 
#endif

    // setting the blink delays by formula here 
    // YES for this the array is not needed, the code below could use the formula
    // however this example can also use a pre-compiled array
    // and if User I/O or other dynamic change method is added, the array may serve that
    // But Please, change it all to suit your ideas as they occur!

    waitBlink[ led ][ 0 ] = 250U * ((unsigned int) led + 1U ); // OFF millis 
    waitBlink[ led ][ 1 ] = 10U * ((unsigned int) led + 1U ); // ON millis

#if SERPRINT
    Serial.print( " OFF millis = " ); 
    Serial.print( waitBlink[ led ][ 0 ] ); 
    Serial.print( " ON millis = " ); 
    Serial.println( waitBlink[ led ][ 1 ] ); 
#endif

    pinMode( ledPin[ led ], OUTPUT ); // default output is LOW
    digitalWrite( ledPin[ led ], bitRead( ledState, led ));
  }  
#if SERPRINT
  Serial.println( ); 
  
  Serial.println( "Led <led> t <millisNow> x <pin state> n <next change>" );
#endif

  led = 0;
}

void loop()
{

  millisNow = (unsigned int)( millis() & 0xFFFF ); // lower 16 bits roll over just fine

  if ( millisNow - startBlink[ led ] >= waitBlink[ led ][ bitRead( ledState, led ) ] )  
  {
#if SERPRINT
    Serial.print( led, DEC ); 
    Serial.print( " t " ); 
    Serial.print( millisNow ); 
/*
    Serial.print( " states " ); 
    Serial.print( ledState, BIN );
*/
#endif

    startBlink[ led ] += waitBlink[ led ][ bitRead( ledState, led ) ]; // update start time
    // Thank You Robin2, this really does take care of late updates 

    bitWrite( ledState, led, ( bitRead( ledState, led ) == 0 )); // change led state bit
    digitalWrite( ledPin[ led ], bitRead( ledState, led ) ); // change the led pin  

#if SERPRINT
/*  put these in and see what it does occasionally to timing. then add more serial 
    Serial.print( " change " ); 
    Serial.print( ledState, BIN );
    Serial.print( " start " ); 
    Serial.print( startBlink[ led ] );
    Serial.print( " wait " ); 
    Serial.print( waitBlink[ led ][ bitRead( ledState, led ) ] );
*/
    Serial.print( " x " ); 
    Serial.print( bitRead( ledState, led ) );
    Serial.print( " n " ); 
    Serial.print( startBlink[ led ] + waitBlink[ led ][ bitRead( ledState, led ) ] );
    Serial.println();
    
    if ( runBeforeStop < 200 )
    {
      if ( !runBeforeStop-- )
      {
        while( 1 );
      }
    }
#endif

  }

  led++;
  if ( led >= LEDS )
  {
    led = 0;
  }
}
1 Like