Using millis() for timing. A beginners guide

Part 2

We have seen how to turn an LED on and off at intervals which is fascinating for maybe a minute or two at most but how about using the same principle to change the brightness of an LED ?

I will use an LED on pin 10 in this example as that pin supports PWM output. The basis of the program will be the same as before except that when the period ends the brightness of the LED will be changed. The period will be much less than 1 second, unless you want to wait a long while, but otherwise the same principles will be used.

So here it is. Note how similar it is to the previous sketch.

unsigned long startMillis;
unsigned long currentMillis;
const unsigned long period = 10;  //the value is a number of milliseconds between changes of brightness
const byte ledPin = 10;    //using an LED on a PWM pin.
byte brightness = 0;       //initial brightness
byte increment = 1;        //amount to change the brightness at the end of each period.

void setup()
{
  Serial.begin(115200);  //start Serial in case we need to print debugging info
  pinMode(ledPin, OUTPUT);
  startMillis = millis();  //initial start time
}

void loop()
{
  currentMillis = millis();  //get the current time
  if (currentMillis - startMillis >= period)  //test whether the period has elapsed
  {
    analogWrite(ledPin, brightness);    //set the brightness of the LED
    brightness += increment;    //will wrap round from 255 to 0 because brightness is an unsigned data type
    startMillis = currentMillis;  //IMPORTANT to save the start time of the current LED brightness
  }
}

In this program at the end of the timing period the brightness of the LED is changed rather than turning it on or off.

So what is all the fuss about ? That's the point, there is no fuss. You can use the BWoD principle for long or short periods and do anything you like at the end of the period (within reason), but to be honest you could have done of this using delay(). But suppose you wanted to do something else at the same time ? delay() could interfere with whatever else you want to do but using millis() and the BWoD principle you can execute code in loop() in between checking whether the period has expired and, of course, the other code can use millis() for timing too.

Let's look at a program that does what each of the previous two do but apparently at the same time. They won't actually be done at the same time, but because loop() repeats thousands of times per second it will appear that the program is doing two things at once.

Here it is

unsigned long blinkStartMillis;
unsigned long fadeStartMillis;
unsigned long currentMillis;
const unsigned long blinkPeriod = 1000;  //blink period
const unsigned long fadePeriod = 10;  //fade period
const byte blinkLedPin = 13;    //this LED will blink
const byte fadeLedPin = 10;    //this LED will fade
byte brightness = 0;  //initial brightness of LED
byte increment = 1;  //amount to change PWM value at each change

void setup()
{
  Serial.begin(115200);  //start Serial in case we need to print debugging info
  pinMode(blinkLedPin, OUTPUT);
  blinkStartMillis = millis();  //start time of blinking LED
  fadeStartMillis = millis();  //start time of fading LED
}

void loop()
{
  currentMillis = millis();  //get the current time
  blink();
  fade();
}

void blink()  //function to blink an LED if the blink period has ended
{
  if (currentMillis - blinkStartMillis >= blinkPeriod)  //test whether the period has elapsed
  {
    digitalWrite(blinkLedPin, !digitalRead(blinkLedPin));  //if so, change the state of the LED
    blinkStartMillis = currentMillis;  //IMPORTANT to save the start time of the current LED state.
  }
}

void fade()    //function to fade an LED
{
  if (currentMillis - fadeStartMillis >= fadePeriod)  //test whether the period has elapsed
  {
    analogWrite(fadeLedPin, brightness);    //set the brightness of the LED
    brightness += increment;    //will wrap round because brightness is an unsigned data type
    fadeStartMillis = currentMillis;  //IMPORTANT to save the start time of the current LED state.
  }
}

At first sight it my seem daunting but you have seen all of it before in the two previous programs. Changes have been made as follows :

  1. Separate variables are needed for the start times of the two different periods
  2. Separate variables are needed for the periods
  3. The new variable have been given meaningful names which relate to the code they belong to
  4. The currentMillis variable is shared between the two functions as it the same for both of them
  5. The code for each set of timing has been moved into functions with descriptive names to make maintenance easier but it could equally all have been in loop().

Note that it would not have been possible to combine the two programs if delay() had been used. For one thing the one second delay in the blink program would have prevented the fade working.

OK, we can blink and fade LEDs at the "same" time but what if we want to do something completely different such as reading an input for a period of a few seconds and count how many times the button is pressed in that time ? You cannot do that using delay() because you cannot delay and read an input at the same time, but as the blink and fade program illustrates, using millis() for timing you can do two things so frequently that they appear to happen at the same time.

In the next part that is what we will do.

17 Likes