HOME  - -  Many other Arduino "How Tos", etc, at my older site

Arduino: A better Blink.

Making an LED go on/ off/ on/ off... a BETTER WAY!
And why it is better, when you might use it.
(Not just for winking LEDs!)

(filename: aht3flash_LED_with_millis.htm)

Was winking an LED with a little loop using the delay() function the first Arduino program you ever ran? I bet it was. Is there anyone out there whose first program wasn't "Blink"??? (I'd be delighted if you treated those questions as a poll! How to contact this page's editor, Tom Boyd. Please cite "wywtk...aht3flash" if you write.)

But sometimes using the delay() function, particularly for delays in excess of, say, a second, can have unwanted consequences. Because it is a blocking function. Nothing else can happen while the program sits through a delay. (Although I think interrupts are still processed.)

Where we are starting from...

Before I show you my alternative to using he delay() function, I will present a starting point for this guide... "the old way" of making an LED wink continuously.

Here's how you MIGHT do it. From here we will progress to what I would call a better way.

/*Assuming an LED on GPIO 0, and assuming that
  the right statement to turn it OFF is...

            digitalWrite(0,1)

  ... the following code should "work".

  the lines like #define pinLED 0 have been used
  for two reasons...

  a) digitalWrite(pinLED,bLEDOff); makes more
  sense to someone reading the code than
  digitalWrite(0,1) does, and...

  b) if you want to re-use a bit of code on
  different hardware where the LED may be
  on a different pin, or where a different
  level on the pin gives rise to the LED *off*,
  it is easy to adapt the code.
 */

#define pinLED 0 // no ; on a #define line
#define bLEDOn 0
#define bLEDOff 1


void setup() {
pinMode (pinLED,OUTPUT);
}

void loop() {
//Turn LED on for 900ms
digitalWrite(pinLED,bLEDOn);
delay(900);

//Turn LED off for 100ms
digitalWrite(pinLED,bLEDOff);
delay(100);

//... over and over and over...

}

Meet the Marvelous Millis()!

If you put...

x=millis();

... into your code (assuming you've declared a variable "x"!), you will fill x with the number of milliseconds which have passes since the program booted.

If you aren't familiar with the Arduino's Serial Monitor, read the article that the link will take you to.

Here's our earlier program, extended to let you become more familiar with "millis()"...

#define pinLED 0 // no ; on a #define line
#define bLEDOn 0
#define bLEDOff 1

void setup() {
pinMode (pinLED,OUTPUT);

Serial.begin(115200);//The value here, and the value chosen for the
  //"baud", lower right-hand corner, Serial Monitor, must match
delay(500);// Give system a chance to "settle". (Messy kludge)
Serial.println();
Serial.println();
Serial.println("Hello world... now wait a moment. (Less than 5 seconds)");
delay(2000);// Give reader a chance to see the output.
}//end of setup()

void loop() {
//Turn LED on for 900ms
digitalWrite(pinLED,bLEDOn);
delay(2700);

//Turn LED off for 100ms
digitalWrite(pinLED,bLEDOff);
delay(300);

Serial.println(millis());

//... over and over and over...

}//end of loop()

Overwhelmed by Overflow?

When I learned to count... age 5?... for a few years I was fascinated by the idea that I could count upwards "forever". I already knew that "thousands" came after "hundreds", and then it was "tens of thousands", "hundreds of thousands", "millions", etc. It escaped my 5 year old imagination that I would have been stuck after "hundreds of billions".

Computers can't count upwards "forever".

It's more complex than we need to go into fully, but how high you can count, in simple terms, is determined by what DATA TYPE- Sparkfun's tutorial you are using.

The "answer" (result) you get from the millis() function can be a number as high as 4,294,967,295.

What happens if you leave your program running for more than 4,294,967,295 milliseconds, and then call millis()?

At the 4,294,967,295th millisecond, the "millis counter" went back to zero.

Will your program ever run that long? Who knows. Depends on the application. I have some that have run, uninterrupted, that long, I think. I'm even more confident that some of my old programs used variable which couldn't hold such big numbers. Maybe that's why some of my old programs crash from time to time? (See "Millennium Bug"!)

Anyway... use the "unsigned long" data type for holding the result of a call to millis() until you know all the details, and can justify any other choice.

Putting things together...

Now modify what we had before as follows. Run it open the serial monitor.

(The LED won't do anything for a moment...)

#define pinLED 0 // no ; on a #define line
#define bLEDOn 0
#define bLEDOff 1

unsigned long unlongRecentMillis;

void setup() {
pinMode (pinLED,OUTPUT);

Serial.begin(115200);//The value here, and the value chosen for the
  //"baud", lower right-hand corner, Serial Monitor, must match
delay(500);// Give system a chance to "settle". (Messy kludge)
Serial.println();
Serial.println();
Serial.println("Hello world... now wait a moment. (Less than 5 seconds)");
delay(2000);// Give reader a chance to see the output.
}

void loop() {
delay(719);
unlongRecentMillis=millis();
Serial.print(unlongRecentMillis);
Serial.print("   ");
Serial.println(unlongRecentMillis % 6);
}//end of loop()

You should get line after line with two numbers on each.

The first will go up and up, rising by a bit more than 719 each time.

The second may appear to have a pattern, or appear "random", depending on which Arduino you are using... but it should always be in the range 0-5 inclusive.

Think about the code. Get yourself confident that you understand what's going on so far.

Wink the LED.... crudely

Now make the code...

#define pinLED 0 // no ; on a #define line
#define bLEDOn 0
#define bLEDOff 1

unsigned long unlongRecentMillis;
word un16ResultOfRemainder;//Word-type data store an unsigned number of *at least* 16 bits.
//16 bits gives from 0 to 65535... Some processors will store more than 16 bits.

void setup() {
pinMode (pinLED,OUTPUT);

Serial.begin(115200);//The value here, and the value chosen for the
  //"baud", lower right-hand corner, Serial Monitor, must match
delay(500);// Give system a chance to "settle". (Messy kludge)
Serial.println();
Serial.println();
Serial.println("Hello world... now wait a moment. (Less than 5 seconds)");
delay(2000);// Give reader a chance to see the output.
}

void loop() {
unlongRecentMillis=millis();
un16ResultOfRemainder=unlongRecentMillis % 800;

if (un16ResultOfRemainder<720)
{digitalWrite(pinLED,bLEDOff);}
else
{digitalWrite(pinLED,bLEDOn);}
}//end of loop()

(Yes, that is extremely inelegant, inefficient code. Patience!)

If all is well, your LED should be flashing on briefly every 800 ms

Digression?

At this point I'm going to introduce something which may seem irrelevant.

Change the code again...

#define pinLED 0 // no ; on a #define line
#define bLEDOn 0
#define bLEDOff 1

unsigned long unlongRecentMillis;
word un16ResultOfRemainder;//Word-type data store an unsigned number of *at least* 16 bits.
//16 bits gives from 0 to 65535... Some processors will store more than 16 bits.
word un16BigLoop=0,wBigLoopsCount=0;

void setup() {
pinMode (pinLED,OUTPUT);

Serial.begin(115200);//The value here, and the value chosen for the
  //"baud", lower right-hand corner, Serial Monitor, must match
delay(500);// Give system a chance to "settle". (Messy kludge)
Serial.println();
Serial.println();
Serial.println("Hello world... now wait a moment. (Less than 5 seconds)");
delay(2000);// Give reader a chance to see the output.
}

void loop() {
unlongRecentMillis=millis();
un16ResultOfRemainder=unlongRecentMillis % 800;

if (un16ResultOfRemainder<720)
{digitalWrite(pinLED,bLEDOff);}
else
{digitalWrite(pinLED,bLEDOn);}

if (un16BigLoop==0)
 {Serial.print("Hi from big loop ");
  Serial.println(wBigLoopsCount);
  wBigLoopsCount=wBigLoopsCount+1;
  un16BigLoop=60000;}//See text

un16BigLoop=un16BigLoop-1;
}//end of loop()

... and watch the serial monitor while you let it run for about 20 seconds... until you've seen "Hi from Big Loop" a few times. If it doesn't appear a few times by then, go to the line near the end of the program which says...

  un16BigLoop=60000;}//See text

... and make the 60000 much smaller... 10000? ("Too small", and you'll just get the "Hi from big loop" "too often". Big loop is supposed to be a "slow" loop compared to the LEDs on/ off/ on/ off...)

You can't make it much LARGER without going beyond the limit if what a "word" type variable can hold on every Arduino. But you may need a SMALLER number. It all depends on how fast the processor is in your Arduino. I was testing this on an Adafruit Feather Huzzah 8266... which has a FAST processor! Yes, Virginia, it went around the loop 60,000 times so quickly that it was spitting out several "Hi from BigLoops" per second! A single pass through the loop was taking a lot less than a millisecond.

See what we've done? We've got two "loops" running, sort of. And they run independently! One takes care (inefficiently still) of winking the LED, and the other, a slower loop, puts "Hi from Big Loop" on the serial monitor from time to time.

(Digression: I can't swear to my explanation of the following... but I think that the 8266's "word" allows bigger numbers in a "word" type variable. In any case, the compiler didn't complain when I used un16BigLoop=900000;... yes, FIVE zeros.) And unless I'm not reading the code properly... in which case please write me... that made the loop execute nearly a million times before generating "Hi from big loop" again and again. And like that, the interval was still under a second! I MUST be mis-reading the code???).

Onward!...

You've now done all the hard stuff.

There's nothing new in what follows.

But it pains me to be writing the same thing to the output that drives the LED over and over and over again when it isn't necessary. Yes! When we need the output to change to it's other state, fine- do another digitalWrite(). But as our simple code stands, we are doing MANY more digitalWrite()s than that.

So here we go...

#define pinLED 0 // no ; on a #define line
#define bLEDOn 0
#define bLEDOff 1

unsigned long unlongRecentMillis;
word un16ResultOfRemainder;//Word-type data store an unsigned number of *at least* 16 bits.
//16 bits gives from 0 to 65535... Some processors will store more than 16 bits.
word un16BigLoop=0,wBigLoopsCount=0;
byte bLedCurrentState,bLedStateShouldBe;


void setup() {
pinMode (pinLED,OUTPUT);

Serial.begin(115200);//The value here, and the value chosen for the
  //"baud", lower right-hand corner, Serial Monitor, must match
delay(500);// Give system a chance to "settle". (Messy kludge)
Serial.println();
Serial.println();
Serial.println("Hello world... now wait a moment. (Less than 5 seconds)");
delay(2000);// Give reader a chance to see the output.

bLedCurrentState=bLEDOff;
}

void loop() {
unlongRecentMillis=millis();
un16ResultOfRemainder=unlongRecentMillis % 800;

if (un16ResultOfRemainder<720)
 {bLedStateShouldBe=bLEDOff;}
else
 {bLedStateShouldBe=bLEDOn;};

if (bLedCurrentState!=bLedStateShouldBe)
 {digitalWrite(pinLED,bLedStateShouldBe);
  bLedCurrentState=bLedStateShouldBe;};

if (un16BigLoop==0)
 {Serial.print("Hi from big loop ");
  Serial.println(wBigLoopsCount);
  wBigLoopsCount=wBigLoopsCount+1;
  un16BigLoop=900000;}//See text

un16BigLoop=un16BigLoop-1;
}//end of loop()

More better

As a little demo, I think the above is pretty good.

But if you were USING these tricks, deep inside a bigger program, they might make reading the code, staying on top of things, etc, hard.

So... last iteration...

/*ar203_flash_led

vers 20 May 23
started 20 May 23

See...
https://wywtk.com/ardu/aht3flash_LED_with_millis.htm
.. for a guide to this code.

A demo of a way to flash an LED
The pattern won't be absolutely regular (unless you take extra care)
but the trick will be useful from time to time.

The "easy" way would be to use the "delay()" routine, but that is a
blocking routine which can be unhelpful.

A note about the use of the unsigned int data type...
  While the size of that... two bytes or four... is processor dependant,
  which might annoy some users, it is "rollover friendly", which is
  sometimes an advantage.
  https://www.arduino.cc/reference/en/language/variables/data-types/unsignedint/

====
The following assumes an LED on GPIO 0
*/

#define pinLED 0 // no ; on a #define line
#define bLEDOn 0 // the right value to use as xx in digitalWrite(pinLED,xx) to turn LED off.
#define bLEDOff 1

#define unlongFlashCycleLength 800
#define unlongFlashCycleOffTime 720
//The previous two are for use with MaybeChangeStateOfLED...
//The define the overall length of the cycle in ms, and the "off" time
//  in each cycle.

unsigned long unlongRecentMillis;
//The previous must be set to millis() at the start of each pass through
//loop(), if you want to use  MaybeChangeStateOfLED. Execution of loop()
//CAN take a "long" time, compared to unlongFlashCycleLength, but if it
//does, the regularity of the flashing will be compromised. Typically, the
//time to execute loop() should be tiny compared to unlongFlashCycleLength.


word un16ResultOfRemainder;//Word-type data store an unsigned number of *at least* 16 bits.
//16 bits gives from 0 to 65535... Some processors will store more than 16 bits.
word un16BigLoop=0,wBigLoopsCount=0;

byte bLedCurrentState,bLedStateShouldBe;
//The previous two are for use with MaybeChangeStateOfLED

void setup() {
pinMode (pinLED,OUTPUT);

Serial.begin(115200);//The value here, and the value chosen for the
  //"baud", lower right-hand corner, Serial Monitor, must match
delay(500);// Give system a chance to "settle". (Messy kludge)
Serial.println();
Serial.println();
Serial.println("Hello world... now wait a moment. (Less than 5 seconds)");
delay(2000);// Give reader a chance to see the output.

bLedCurrentState=bLEDOff;
}

void loop() {
unlongRecentMillis=millis();
un16ResultOfRemainder=unlongRecentMillis % unlongFlashCycleLength;

MaybeChangeStateOfLED();
/*The above can be used to periodically change the state of any output.
The duty cycle does not need to be 50:50
"LED" is in the name only because driving an LED may be a common use
of the routine.
Programs using the routine should have...

#define pinLED 0 // no ; on a #define line
#define bLEDOn 0 // the right value to use as xx in digitalWrite(pinLED,xx) to turn LED off.
#define bLEDOff 1

#define unlongFlashCycleLength 800
#define unlongFlashCycleOffTime 720
//The previous two are for use with MaybeChangeStateOfLED...
//The define the overall length of the cycle in ms, and the "off" time
//  in each cycle.

unsigned long unlongRecentMillis;
//The previous must be set to millis() at the start of each pass through
//loop(), if you want to use  MaybeChangeStateOfLED. Execution of loop()
//CAN take a "long" time, compared to unlongFlashCycleLength, but if it
//does, the regularity of the flashing will be compromised. Typically, the
//time to execute loop() should be tiny compared to unlongFlashCycleLength.

byte bLedCurrentState,bLedStateShouldBe;
//The previous two are for use with MaybeChangeStateOfLED

In "setup", bLedCurrentState should be set to bLEDOff;
*/

if (un16BigLoop==0)
 {Serial.print("Hi from big loop ");
  Serial.println(wBigLoopsCount);
  wBigLoopsCount=wBigLoopsCount+1;
  un16BigLoop=900000;}//See text

un16BigLoop=un16BigLoop-1;
}//end of loop()

void MaybeChangeStateOfLED(){
//Yes... you could, probably should, write this to use
//global variables less.
//See notes in body of code. They cover what requirements
//  must be met, if you want to use this subroutine.

if (un16ResultOfRemainder<unlongFlashCycleOffTime)
 {bLedStateShouldBe=bLEDOff;}
else
 {bLedStateShouldBe=bLEDOn;};

if (bLedCurrentState!=bLedStateShouldBe)
 {digitalWrite(pinLED,bLedStateShouldBe);
  bLedCurrentState=bLedStateShouldBe;};
};//end MaybeChangeStateOfLED()

And finally... Esoterics of modulo...

Beware... if you, as I was, use "modulo operator" for what the Arduino language calls the "remainder" operator...

"5 modulo 3" = 2, because 5 divided by 3 is "one remainder two", as we learned in early primary school.

As long as both of the numbers (the 5 and the 3 above) are positive, non-zero numbers, all is well, modulo is simply the remainder. And, happily, for our purposes above, both numbers WILL be positive.

If one (and only one!) of the numbers are negative, or if the second number is zero, things become more complicated.

See...

https://forum.arduino.cc/t/the-modulo-function/41676

... if you want to know more. Wikipedia, https://en.wikipedia.org/wiki/Modulo, 20 May 2023, commented darkly... "When exactly one of [the numbers you are using modulo on] is negative, the naive definition breaks down, and programming languages differ in how these values are defined."!



A few words from the sponsors...

Please get in touch if you discover flaws in this page. Please cite the page's URL. (http://wywtk.com/ardu/aht3flash_LED_with_millis.htm).

If you found this of interest, please mention in forums, give it a Facebook "like", Google "Plus", or whatever. If you want more of this stuff, help!? There's not much point in me writing these things, if no one feels they are of any use.



index sitemap
What's New at the Site Advanced search
Search tool (free) provided by FreeFind... whom I've used since 2002. Happy with it, obviously!

Unlike the clever Google search engine, this one merely looks for the words you type, so....
*    Spell them properly.
*    Don't bother with "How do I get rich?" That will merely return pages with "how", "do", "I"....

Please also note that I have three other sites, and that this search will not include them. They have their own search buttons.

My SheepdogSoftware.co.uk site, where you'll find my main homepage. It has links for other areas, such as education, programming, investing.

My SheepdogGuides.com site.

My site at Arunet.




How to email or write this page's editor, Tom Boyd. Please cite page's URL (http://wywtk.com/ardu/aht3flash_LED_with_millis.htm) if you write.


Valid HTML Page has been tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. Mostly passes.

AND passes... Valid CSS!


Why does this page cause a script to run? Because of the Google panels, and the code for the search button. Also, I have my web-traffic monitored for me by eXTReMe tracker. They offer a free tracker. If you want to try one, check out their site. Why do I mention the script? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....