Making the move to millis()-based code can be daunting. You have to rethink your logic, implement flags, program a state machine — and more importantly, start using millis(). Generally in forums and on IRC people will just point to the “blink without delay” example, hoping the commented code is enough for a new user. It’s not enough.
I have a growing list of millis()-based tasks posted in my millis() cookbook. But sometimes that those examples might be too simple or not close enough to your project’s end target. That got me thinking about different ways to help explain how millis() works. I thought “wow, you need to understand every line of an example” which leads me to: line-by-line.
Give this “blink without delay line by line” tutorial a shot if you’ve had trouble understanding other millis() examples.
If you aren’t familiar with the blink without delay example, here is a stripped-down version.
const int ledPin = 13;
int ledState = LOW;
unsigned long previousMillis = 0;
unsigned long interval = 1000;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
if (ledState == LOW)
ledState = HIGH;
else
ledState = LOW;
digitalWrite(ledPin, ledState);
}
}
Global Variables
const int ledPin = 13;
Like many example scripts, this code uses the Arduino onboard LED. If you’re using a LaunchPad with Energia, you would want to change this to something like “GREEN_LED.”
Notice that this is an integer variable type with the const. This keyword tells the compiler the value of “ledPin” cannot change. There is also some optimizations that occur with avr-gcc for variables declared as const.
The difference between const and #define is defined in another post, if you want to dig a little deeper.
| |
This state variable, or flag variable, is going to track the state of the LED. When using millis() it is pretty common to create these “state variables” to track what is happening in the code.
Are you new to “flags” and “states”? Then check out this post on flag variables and this one on state machines with an enum.
| |
Blink without delay needs to keep track from loop-to-loop. This variable will let us know the “last” time we checked millis().
Since millis() returns an unsigned long, this variable needs the same variable type.
| |
We need to wait a certain amount of time between states. Here we call the variable “interval”.
Unless I am tight on RAM, I’ll define “interval” as an unsigned long, even if the number could have fit into an int.
Using the same variable types avoids casting problems, or more likely, bugs because I don’t always understand how the compiler casts variables.
setup()
| |
Okay, this is more than 1 line of code. But the only thing we need to do is set our LED’s pin to OUTPUT.
loop()
| |
The key to remember is that code inside of loop() will run for forever. In millis()-based code, we want to write everything with the mindset: “This will run thousands of times a second.”
Write millis()-based code with the mindset: “This will run thousands of times a second.”
What does that mindset mean? It means lots of if-statements and variable checks. No, not working? Okay, I’ll use an analogy to get through the rest of the code.
Sandwich example
Let’s say you order a delicious sandwich. The waiting time is 10 minutes. So you look at your watch and see that it is 12:04 pm. After sitting down you check your watch again, now it is 12:05 pm—9 minutes to go. Bored you start reading /r/funny.
Now you look at your watch, and it is 12:09 pm—5 minutes to go. Wow, time is flying. At this point, you decide to see read an analysis of last week’s Mr. Robot. You check your watch, and it is 12:13 pm.
Great, only one more minute.
Pack up your things, look at your watch and it says 12:14 pm—0 minutes! Excellent, time to go and get your delicious sandwich.
The key here is that you didn’t use a “timer” or “alarm.” Instead, while you were busy doing other things (aka multitasking) you kept checking to see how much time had passed since the start.
| |
Each time loop() runs, we need to check the current time. Think of this as looking at your watch to see what time it is right now.
(The very first time we go through loop(), previousMillis is still going to be set to “0”. Technically that was the FIRST time we looked at the time.)
| |
Here is the magic code that scares people new to millis()-based programs. This single line of code appears in more than just “blink without delay”. For example, all of my millis()-cookbook examples have this line of code .
The code simply reads subtract the current time from the previous time. Then check if more than our waiting time has passed.
Using subtraction like this handles the case where millis() “rolls-over” in 49 days.
This post goes into detail about how to avoid millis() rollover. Most people try to reset millis(), when all you need to do is handle roll over.
| |
Here’s where we update previousMillis for the first time. Whenever our waiting period has passed, we need to reset the initial time.
If your millis() code doesn’t seem to be working, check to make sure you are resetting previousMillis.
| |
Even those this isn’t a single line, the C-complier sees it as 1 line. Basically, we need to toggle the state of ledState so that it will turn ON or OFF.
| |
The last thing we do is set the LED to its state variable.
This code is the breakdown of the example included with the Arduino(-based) IDEs. In my code, I generally leave the digitalWrite() outside of the if-statement.
Exercise for the reader
Ready to test your knowledge? Okay, add a push-button to this code to enable or disable the blinking.

