Arduino Timing Implementation Using delay() vs millis()

Ethan HalimEthan Halim
5 min read

When working on projects with the Arduino (or any other platform of your choice), the element of timing is essential in the program. Being able to control timing allows users to decide when functions should be called, for how long, and more.

However, not all timing functions are created equal.

For my first article, I would like to explore the differences between 2 commonly used functions native to the Arduino C++ language; The delay() function and the millis() function.

To demonstrate the 2 functions, I've built this simple circuit. Connected to the Arduino Uno is an LED circuit (D7) and a voltage divider circuit (A0). The voltage divider configuration allows pin A0 to read the analog value of the LDR.

To examine the differences between the 2 timing functions, I've created 2 sketches. The objective of these 2 sketches is to carry out 2 processes each:

  1. Blink the LED.

  2. Inquire the reading of the LDR at the same time.

Since they are implemented using different timing functions, their performance and operation will differ.

delay() Sketch

//Written by EthanH, 11/12/23

int led = 7; 
int ldr = A0;

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);

pinMode(led, OUTPUT);
pinMode(ldr, INPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
read();
blink();
}

void blink(){
  digitalWrite(led, HIGH);
  delay(1000);  //program does nothing here
  digitalWrite(led, LOW);
  delay(1000);  //as well as here

}

void read(){
  int ldrval = analogRead(A0);
  Serial.print("LDR sensor value = ");
  Serial.println(ldrval);

}

We will first examine the sketch using the delay() function. At first glance, this sketch seems like it has met our desired objective. The program prints the values of the LDR to the serial monitor and the LED blinks correctly.

However, if the value in the delay() function is adjusted to be greater, it becomes more obvious that the program does not behave in the way we outlined in the objective.

If both delay() function values were to be set to 5000mS, the LDR would not be inquiring any data in between the states of the LED being on and off. Therefore, this does not quite meet our objective as the 2 processes are not happening at the same time.

This is because the delay() function essentially puts the entire program on hold and waits for the value passed in the argument (in mS) to pass. During this period, the program will not inquire the values of the LDR.

millis() sketch

//Written by EthanH, 11/12/23

int led = 7; 
int ldr = A0;

unsigned long previous = 0; //data type with value range of 0 to 4,294,967,295

int ledstate = LOW; //initialize LED in the OFF state

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);

pinMode(led, OUTPUT);
pinMode(ldr, INPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
blink();
read(); //Arduino can get LDR sensor value at all times and not pause the program
}

void blink(){
unsigned long current = millis();

   if (current - previous >= 1000){ //condition to check if 1000 microseconds has passed
    previous = current;

    if (ledstate == LOW){ //if statement block to flip LED state
      ledstate = HIGH;
    }

    else {
      ledstate = LOW;
    }

  digitalWrite(led, ledstate);  //don't forget to write the new LED state to D7
   }
}

void read(){
  int ldrval = analogRead(A0);
  Serial.print("LDR sensor value = ");
  Serial.println(ldrval);
}

This sketch differs from the delay() sketch above as it allows the program to work on both processes at the same time. Before we have a look at this program, let's understand how the millis() function works.

The millis() function keeps track of time by counting the number of clock cycles generated by the onboard crystal oscillator. This article will not go in-depth on the intricacies of the hardware clock; To keep things simple for now, the function basically counts the number of clock cycles that have passed in the time that was passed in the function parameter. This allows for an accurate implementation of timing for projects.

It also does this in the background which means that it does not pause the program to wait for a certain duration to pass.

Do note that although different Arduino Uno clones may have different clock frequencies ranging from 16Mhz to 480Mhz, the millis() function can be used universally across the board ;)

Running this sketch will show that the LED will constantly blink and not interfere with the data acquisition of the LDR circuit. This implementation therefore meets our outlined objective.

A note for readers.

  1. When implementing the millis() function as an unsigned long variable, do remember not to declare said variable that keeps track of current time as a global variable. For example, in the millis() sketch above, the "current" unsigned long variable is declared in the void blink() function and not above the void setup() with the other variable used. If it were to be declared as a global variable, the function would never update and would be stuck at 0.

  2. If a user wishes to perform an operation on an unsigned long variable, do make sure that both operands are unsigned long variables. If one of the operands is not declared as an unsigned long, the variable may be forced as an unsigned long within the operation by adding "ul" or "UL" following the operand. For example: "3300UL".

0
Subscribe to my newsletter

Read articles from Ethan Halim directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ethan Halim
Ethan Halim

Electronics engineer from Singapore with a specialization in Microelectronics and an interest to learn as much as possible about engineering as a whole. Hacking through hardware at the moment and learning as much as I can everyday while using Hashnode to document the content :) Mainly working with Python, Arduino, Raspberry Pis, Jetson Nano and soon a Xilinx FPGA.