Demonstration code for several things at the same time

The more time and money invested in a mistake, the harder it is to walk away.

Never time to do it right, always time to do it over.

1 Like

Yeah... a friend of mine has the unenviable job of cleaning up sloppy quick-fixes in other people's programming.

And this is why I'm on kind of a tear, criticizing the over-use of delay() to time things. It really annoys me how just about every book, class, website, and video that purports to be for beginners to Arduino, use delay() for everything.

At last I got round to looking at this thread, although I've seen Robin2 link to it numerous times.

The esoteric discussion on latency and slippage aside- most if not all of which went splat on the wall behind me- it's an excellent piece of work and a good look at the whole bwod thing. Well done and karma to you Robin2.

I'm going to use this approach in some stuff I intend to do today- probably get really advanced and have different durations on the LEDs 8), and include some other stuff I'm fiddling with.

[whisper]It's such a pity that this thread went off the rails a number of times but that's life I guess.[/whisper]

Well, i just stumbled across this example after starting this thread:
http://forum.arduino.cc/index.php?topic=248643.0

I had already written a higgledy piggledy code that more or less did what I want it to do, but some things are just seemingly messed up by timing during other events using delay()

I had already taken each part of the code into individual functions as in your example before reading it and was looking into how to use millis in various functions at the same time,

this was very useful having stepped in the previously mentioned poo and looking for a way to clean my foot,

Thanks,

1 Like

And if you need finer grained timing, there is also micros(). Although it counts in microseconds, you should know that the granularity is 4us.

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

GoForSmoke:
If you haven't seen, this is Nock's tutorial:
http://www.gammon.com.au/forum/?id=11411

In case you don't want to / can't remember a five digit number...

casemod:
Since this is a sticky it's worth to post this method here as well:

Added 02 Oct 2014 --- @casemod seems to have moved his posts out of this Thread (as was suggested in the next Post) hence this and some of the following posts are in a bit of a vacuum.

Most of the posts in this Thread are about the cumulative error associated with

previousmillis = currentmillis;

and how it is better to use

previousmillis += time;

or (in your second method)

since += time

Please don't confuse newcomers by recommending the deprecated approach in this Thread.

It would probably also be very useful for newcomers if you would modify your Post and add an explanation about how &since works. Most of them will find that concept very confusing without a clear explanation.

...R

casemod:
I changed that bit and added a brief description

I posted the code with the intent of helping those trying to "multitask" or do things at the same time, not exactly the issue you mentioned, however if it is not suitable let me know so I can remove it or make further corrections

Thanks.

You don't seem to have changed the line with since = currentmillis.

And I can't see your explanation of the way &since works

Your concept might be better placed in its own Thread titled "A non-blocking alternative to delay()". If you move your code you could keep a link to the other Thread here.

...R

casemod:

Robin2:
You don't seem to have changed the line with since = currentmillis.

I changed this line

if (currentmillis - previousmillis >= time) {

previousmillis = currentmillis




with 


if (currentmillis - previousmillis >= time) {
    previousmillis += time;

If you're using the blinking led as a status light then the first way will let accumulated "late errors" actually show.
What code is "right" is a matter of what your output is for. As an indicator, self-correction is not the best goal.

GoForSmoke:
If you're using the blinking led as a status light then the first way will let accumulated "late errors" actually show.
What code is "right" is a matter of what your output is for. As an indicator, self-correction is not the best goal.

Please don't reopen this discussion. It has all been extensively covered in earlier posts and the demo in the first Post in the Thread has taken all the discussion into account. My only concern is that @Casemod's contribution might confuse newcomers. He has said he will move it to another Thread.

...R

Nick's tutorial. :stuck_out_tongue:

My examples there actually do have the creep which is mentioned earlier. Putting aside coding issues, this creep is based on the fact that you reset the timer when the event is noticed, not a fixed interval from when it should have been noticed.

In the case of flashing an LED, this isn't an issue.

Let me give you an example in real-life terms. Say you get a credit card statement in the mail, and you have 30 days to pay. The 30 days is from when the statement was sent, not from when you receive it (which might be a few days later). The creep is in the delivery time (or maybe in the time it takes you to notice the mail).

To have the statements come every 30 days, the sender simply adds 30 days to when they sent the last one. To have the creep, they would add 30 days to when you received the mail.

1 Like

Sorry about that Mr. Gammon, Sir! One quoted mistype that's never going to die, is it? IIRC, I did fix the original.

In the bills case it is desired to keep the billing cycle regular. Apps should fit needs. Been there, wrote the packages.

I want a status light to tell me more about the running of the software, not less. I've got one now that doesn't so much creep as leap whenever the GSM element runs. It's for someone else and free work so I'm not going to fix that library but wow does it need unblocking!

I thank you again for your great tutorials that put words to what I never put words to. My right-brain thinking ways don't do that and I end up with poor explanations for thoughts that are more like working pictures. What can I say? Literally! It's been great to be able to link people to your full, coherent explanations instead of my poor attempts.

Robin2
I am sure people new to programming will find your work very beneficial.
One comment, if a newcomer reads through the whole thread I think they may a bit confused with all the discussions.
IMHO it would be best to chop everything after your example which incorporates the the discussed creep code modification.
Thanks for your time on this.
Thanks to the software guys for there explanations also.

LarryD:
One comment, if a newcomer reads through the whole thread I think they may a bit confused with all the discussions.

I have been wondering about this as well, but I am concerned that I may be too close to the subject to take an objective view.

I have added a note for newcomers at the top of the first Post.

...R

I just wish that you had explained the central problem of blocking code explicitly.
That is why I still link to Nick's blog. He does a good job of doing that.

GoForSmoke:
I just wish that you had explained the central problem of blocking code explicitly.
That is why I still link to Nick's blog. He does a good job of doing that.

As I say in the opening post (the only one that really matters for a newcomer) I just set out to give an extended example of the BWoD concept. I was not trying to monopolize the subject.

I like Nick's stuff - I have bookmarked several items.

But I also think it is a good idea to be able to direct people to material within the Arduino website.

It is always difficult to know how to pitch a teaching item. Some people like to read the theory. Others just want to get stuck in.

...R

I agree with your goals very much.

But to some of this I liken it to teaching the 2 + 2 = 4 and 3 + 3 = 6 without teaching what 2 and 3 are. I want beginners to be able to say 2 + 3 = 5 without having to memorize it, look it up or get a library to do it for them.

If you don't have your fundamentals down then you make fundamental mistakes. Bits, bytes and logic are the ABC's, 123's and +-x/ operators of computing. Arrays are the spelling words and structure is grammar. Everyone with a solid grounding in those will be able to make easier sense of the rest. The how and why is more important than the what and that gets demonstrated every day here which is why I try to hand out fundamentals where I find a lack.

GoForSmoke:
But to some of this I liken it to teaching the 2 + 2 = 4 and 3 + 3 = 6 without teaching what 2 and 3 are.

I see that.

I guess I started from the assumption that the reason WHY would have been explained in whatever Reply told the OP not to use delay() and to look at this as an example of how to get by without it.

It seems to me that the newcomers have no difficulty understanding that delay() gets in the the way of concurrency - but they don't immediately grasp how to use millis() to replace it.

If you can suggest 2 or 3 lines (not more!) for insertion into my original post I will certainly consider it.

...R

Why is the usage of break an argument against switch()? That is the same as saying the use of else is an argument against if... It are code constructs to make an algorithm easier to maintain and give the compiler means to optimize the code.

For a switch statement there are at least 2 ways to compile to machine code while an if then else ladder (expressed functionally equivalent) cannot. One reason is that for a switch statement the compiler knows immediately that there is one element in an "is equal" compare, which is every time the same (e.g. optimize in a register). For an if then else ladder every comparison is a new one for the parse tree, which might be optimized later. In switch statements every case must be disjunct, semantics forces that. In an if then else ladder you can do the same comparison multiple times.

A switch statements allows a fall through which can be used for simple OR or for reducing double code. An if then else ladder has no (straightforward) equivalent for that.

For me the only drawback of the switch statement is that it does only support simple integer types and not complex or aggregate like strings and structs. BUt that is by design so I can (have to) live with that.