Part 1
It is not usually long before new Arduino users discover that although the delay() function is easy to use it has side effects, the main one of which is that its stops all activity on the Arduino until the delay is finished (not quite true, I know, but that is usually how the problem presents itself). When this occurs the new user is usually directed to the BlinkWithoutDelay example sketch
(BWoD) and/or the excellent thread entitled Several things at the same time. Several things at the same time
Quite often this only serves to confuse the new user because they don't just want to blink an LED or can't get their head around doing more than one thing at (apparently) the same time. What they need is to understand the BWod principle before they can see how to apply it to their own situation.
The programs presented here overlap with those in that thread but I have put my own spin on using millis() and described the programs in my own way. Between the two you should have a clearer understanding of how to use millis() for non blocking timing. In this thread I will try to explain the principles of using millis() for timing and apply it to some common areas where questions arise. The principle is easy to describe but there are some gotchas along the way that you need to look out for.
To use millis() for timing you need to record the time at which an action took place to start the timing period and then to check at frequent intervals whether the required period has elapsed. If so, you presumably want to do something, otherwise why would you be timing ?
If the period has not yet elapsed then you can go on your way and possibly do other things until the next check.
In the sample programs below there are some assumptions made as follows :
- You know how to set the pinMode() of an input and output
- Pins A1, A2 and A3 are used as digital inputs but any pin other than 0 and 1 can be used.
- Input pins are defined as INPUT_PULLUP in pinMode() and that closing the associated switch takes the pin LOW
- Pins 10, 11, 12 and 13 each have LEDs attached via a current limiting resistor to 5V, so taking the pin LOW turns on the LED
- Pins 10 and 11 are capable of PWM output when required.
- Variables used will be declared as globals for ease of use but purists may want to declare some of them locally
- The programs in this thread have been written and tested on a Uno but will run on most/all Arduino boards
Let's get started
In order to use millis() for timing the program is going to need to know its current value, perhaps more than once in each time through loop(). Whilst it is possible to read the value each time it is required it is more convenient to read it once in each pass so that within the program its value can be used as many times as needed and that it is consistent. It is also convenient to do this at the start of loop() and you do it like this
currentMillis = millis();
Simple enough, but this line of code embodies a number of important ideas :
- The variable must previously have been declared.
- It is an unsigned long because that is what millis() returns.
- It is important that it is unsigned (more on this later)
- It has a descriptive name. Feel free to use your own name but this is the one that I will be using.
We are going to need at least 2 other variables in order to determine whether the required period has elapsed. We know the current value of millis(), but when did the timing period start and how long is the period ?
At the start of the program declare 3 global variables, as follows
Code: [Select]
unsigned long startMillis;
unsigned long currentMillis;
const unsigned long period = 1000; //the value is a number of milliseconds, ie 1 second
Let's use those variables in a program that blinks an LED, not quite the same as the example in the IDE, but one that shows the use of millis() nevertheless. Remember the principle, as laid out above "you need to record the time at which an action took place to start the timing period and then to check at frequent intervals whether the required period has elapsed." If so, you presumably want to do something, otherwise why would you be timing ?"
If the period has not yet elapsed then you can go on your way and possibly do other things until the next check. More of this later.
The program :
unsigned long startMillis; //some global variables available anywhere in the program
unsigned long currentMillis;
const unsigned long period = 1000; //the value is a number of milliseconds
const byte ledPin = 13; //using the built in LED
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" (actually the number of milliseconds since the program started)
if (currentMillis - startMillis >= period) //test whether the period has elapsed
{
digitalWrite(ledPin, !digitalRead(ledPin)); //if so, change the state of the LED. Uses a neat trick to change the state
startMillis = currentMillis; //IMPORTANT to save the start time of the current LED state.
}
}
Follow the code through and see how the current value of millis() is compared with the start time to determine whether the period has expired. If you don't understand how the state of the LED is changed don't worry but for information it reads the current state of the LED pin (HIGH or LOW) and writes the opposite state (HIGH or LOW) to it.
Probably the most important thing is to remember to save the start time when the LED state is changed, ie the start of the next timing period.
Copy the program into the IDE, upload it and watch in amazement as the LED blinks. Do not resist the temptation to change the blink period.
Now let's tackle what might be an elephant in the room. The program will sit there quite happily comparing the current time with the start time and turning the LED on and off if it is time to do so. After some time, actually just over 49 days, the value returned by millis() will get so large that it will not fit in an unsigned long variable and it will roll over to zero and start incrementing again.
I don't suppose that you will leave this program running for 49 days to see what happens (please feel free to do so), but suppose that you had used the principle for something more critical that simply must not do something silly after 49 and a bit days. Fear not. Using unsigned variables and subtraction for the elapsed period the comparison will work even if/when millis() rolls over to zero when the program is running. This is not the place to have a diversion into the reasons why this works but trust me, it does.
OK, that is blinking a single LED with a symmetrical on/off period done and dusted using the principle of testing the elapsed time since an event. What next ? Well, we could blink more than one LED with a different period, we could have different on/off periods and even do a combination of both. The best way to do this is to use arrays of values but it would mean introducing the principles of using arrays and if you are reading this I suspect that it will overload you and be too large a diversion from the subject in hand. You could change the on/off periods by simply changing the value of the period variable when you change the state of the LED
So what next ?
How about applying the principle to changing the brightness of an LED instead of turning it on and off ?
In part 2 that is what we will do and after that we will make the program appear to do two things at once using the magic of millis()