Tutorial: Arduino timing methods with millis()

In this article we introduce the millis(); function and put it to use to create various timing examples.

Millis? Nothing to do with lip-syncers… hopefully you recognised milli as being the numerical prefix for one-thousandths; that is multiplying a unit of measure by 0.001 (or ten to the power of negative 3).

Interestingly our Arduino systems will count the number of milliseconds (thousands of a second) from the start of a sketch running until the count reaches the maximum number capable of being stored in the variable type unsigned long (a 32-bit [four byte] integer – that ranges from zero to (2^32)-1.

(2^32)-1, or 4294967295 milliseconds converts to 49.71027-odd days. The counter resets when the Arduino is reset, it reaches the maximum value or a new sketch is uploaded. To get the value of the counter at a particular juncture, just call the function – for example:

start=millis();

Where start is an unsigned long variable. Here is a very simple example to show you millis() in action:

/*
  millis() demonstration
*/

unsigned long start, finished, elapsed;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  Serial.println("Start...");
  start=millis();
  delay(1000);
  finished=millis();
  Serial.println("Finished");
  elapsed=finished-start;
  Serial.print(elapsed);
  Serial.println(" milliseconds elapsed");
  Serial.println();
  delay(500);
}

The sketch stores the current millis count in start, then waits one second, then stores the value of millis again in finished. Finally it calculates the elapsed time of the delay.  In the following screen dump of the serial monitor, you can see that the duration was not always exactly 1000 milliseconds:

ex37p1sshot

To put it simply, the millis function makes use of an internal counter within the ATmega microcontroller at the heart of your Arduino. This counter increments every clock cycle – which happens (in standard Arduino and compatibles) at a clock speed of 16 Mhz. This speed is controlled by the crystal on the Arduino board (the silver thing with T16.000 stamped on it):

xtal

Crystal accuracy can vary depending on external temperature, and the tolerance of the crystal itself. This in turn will affect the accuracy of your millis result. Anecdotal experience has reported the drift in timing accuracy can be around three or four seconds per twenty-four hour period.

If you are using a board or your own version that is using a ceramic resonator instead of a crystal, note that they are not as accurate and will introduce the possibility of higher drift levels. If you need a much higher level of timing accuracy, consider specific timer ICs such as the Maxim DS3231.

Now we can make use of the millis  for various timing functions. As demonstrated in the previous example sketch, we can calculate elapsed time. To take this idea forward, let’s make a simple stopwatch. Doing so can be as simple or as complex as necessary, but for this case we will veer towards simple.

On the hardware perspective, we will have two buttons – Start and Stop – with the 10k ohm pull-down resistors connected to digital pins 2 and 3 respectively.

When the user presses start the sketch will note the value for millis – then after stop is pressed, the sketch will again note the value for millis, calculate and display the elapsed time. The user can then press start to repeat the process, or stop for updated data. Here is the sketch:

/*
  Super-basic stopwatch using millis();
*/

unsigned long start, finished, elapsed;

void setup()
{
  Serial.begin(9600);
  pinMode(2, INPUT); // start button
  pinMode(3, INPUT); // stop button
  Serial.println("Press 1 for Start/reset, 2 for elapsed time");
}

void displayResult()
{
  float h,m,s,ms;
  unsigned long over;
  elapsed=finished-start;
  h=int(elapsed/3600000);
  over=elapsed%3600000;
  m=int(over/60000);
  over=over%60000;
  s=int(over/1000);
  ms=over%1000;
  Serial.print("Raw elapsed time: ");
  Serial.println(elapsed);
  Serial.print("Elapsed time: ");
  Serial.print(h,0);
  Serial.print("h ");
  Serial.print(m,0);
  Serial.print("m ");
  Serial.print(s,0);
  Serial.print("s ");
  Serial.print(ms,0);
  Serial.println("ms");
  Serial.println();
}

void loop()
{
  if (digitalRead(2)==HIGH)
  {
    start=millis();
    delay(200); // for debounce
    Serial.println("Started...");
  }
  if (digitalRead(3)==HIGH)
  {
    finished=millis();
    delay(200); // for debounce
    displayResult();
  }
}

The calls to delay() are used to debounce the switches – these are optional and their use will depend on your hardware. Below is an example of the sketch’s serial monitor output – the stopwatch has started, and then button two pressed six times across periods of time:

ex37p1sshot

If you had a sensor at the start and end of a fixed distance, speed could be calculated: speed = distance ÷ time.

You can also make a speedometer for a wheeled form of motion, for example a bicycle. At the present time we do not have a bicycle to mess about with, however we can describe the process to do so – it is quite simple. (Disclaimer – do so at your own risk etc.)

First of all, let’s review the necessary maths. You will need to know the circumference of the wheel. Hardware – you will need a sensor. For example – a reed switch and magnet. Consider the reed switch to be a normally-open button, and connect as usual with a 10k ohm pull-down resistor.

Others may use a hall-effect sensor – each to their own). Remember from maths class, to calculate the circumference – use the formula:

circumference = 2πr 

where r is the radius of the circle. Now that you have the wheel circumference, this value can be considered as our ‘fixed distance’, and therefore the speed can be calculated by measuring the elapsed time between of a full rotation.

Your sensor – once fitted – should act in the same method as a normally-open button that is pushed every rotation. Our sketch will measure the time elapsed between every pulse from the sensor.

To do this, our example will have the sensor output connected to digital pin 2 – as it will trigger an interrupt to calculate the speed. (Interrupts? See chapter three). The sketch will otherwise be displaying the speed on a normal I2C-interface LCD module. The I2C interface is suggested as this requires only 4 wires from the Arduino board to the LCD – the less wires the better.

Here is the sketch for your perusal:

/*
Basic speedometer using millis();
*/

#include "Wire.h" // for I2C bus LCD
#include "LiquidCrystal_I2C.h" // for I2C bus LCD module - http://bit.ly/m7K5wt
LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display

float start, finished;
float elapsed, time;
float circMetric=1.2; // wheel circumference relative to sensor position (in meters)
float circImperial; // using 1 kilometer = 0.621371192 miles
float speedk, speedm;    // holds calculated speed vales in metric and imperial

void setup()
{
  attachInterrupt(0, speedCalc, RISING); // interrupt called when sensors sends digital 2 high (every wheel rotation)
  start=millis();
  // setup LCD
  lcd.init();      // initialize the lcd
  lcd.backlight(); // turn on LCD backlight
  lcd.clear();
  lcd.println(" Wear a helmet! ");
  delay(3000);
  lcd.clear();
  Serial.begin(115200);
  circImperial=circMetric*.62137; // convert metric to imperial for MPH calculations
}

void speedCalc()
{
  elapsed=millis()-start;
  start=millis();
  speedk=(3600*circMetric)/elapsed; // km/h
  speedm=(3600*circImperial)/elapsed; // Miles per hour
}

void loop()
{
  lcd.setCursor(0,0);
  lcd.print(int(speedk));
  lcd.print(" km/h ");
  lcd.print(int(speedm));
  lcd.print(" MPH   ");
  lcd.setCursor(0,1);
  lcd.print(int(elapsed));
  lcd.print(" ms/rev      ");
  delay(1000); // adjust for personal preference to minimise flicker
}

There isn’t that much going on – every time the wheel completes one revolution the signal from the sensor will go from low to high – triggering an interrupt which calls the function speedCalc().

This takes a reading of millis() and then calculates the difference between the current reading and the previous reading – this value becomes the time to cover the distance (which is the circumference of the wheel relative to the sensor – stored in

float circMetric=1.2;

and is measured in metres). It finally calculates the speed in km/h and MPH. Between interrupts the sketch displays the updated speed data on the LCD as well as the raw time value for each revolution for curiosity’s sake. In real life I don’t think anyone would mount an LCD on a bicycle, perhaps an LED display would be more relevant.

In the meanwhile, you can see how this example works in the following short video clip. Instead of a bike wheel and reed switch/magnet combination, I have connected the square-wave output from a function generator to the interrupt pin to simulate the pulses from the sensor, so you can get an idea of how it works:

That just about sums up the use of millis() for the time being. There is also the micros(); function which counts microseconds.

So there you have it – another practical function that can allow more problems to be solved via the world of Arduino. As always, now it is up to you and your imagination to find something to control or get up to other shenanigans.

This post brought to you by pmdway.com everything for makers and electronics enthusiasts, with free delivery worldwide.

To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.

8 thoughts on “Tutorial: Arduino timing methods with millis()

  1. David Bourke

    Just a question with Example 37.2
    is there any way of having a third input as a pause button ?
    then resumes with ether start button or by pressing the pause button a second time
    and then shows the time data once stopped.

    1. John Boxall

      Sure – use a normal button (non-interrupt) for the start button. Then use interrupt 1 for a pause, at which point the normal start button could be used as ‘resume’. And the second interrupt for stop.

  2. Nishant Kalbhor

    Hello,
    I tried the above code with Honeywell’s ss40a, but it doesn’t seem to show correct values. Can you suggest some changes so that it can be used with SS40A sensor.

    Regards,
    Nishant.

Comments are closed.