Counting time with interruptions

Introduction

This tutorial explains how to create an internal representation of date and time in a microcontroller, using interruptions.

Counting time

First, we define a data structure to maintain the date and time. It will be a 6 positions array that contains the values from years to seconds, as shown in figure 1. Since this structure will probably be accessed by multiple functions, the array should be defined as a global variable.

Timekeeping data structure.
Figure 1 – Timekeeping data structure.

In order to save some resources, the array can be of the byte type. Since an unsigned byte can range from 0 to 255, the only constraint will be in the years representation, on which we will only store the 2 least significant digits (for example, 2015 is stored as 15).

Then, we configure the timer of the microcontroller to fire once each second. So, every second, the defined interrupt routine will be executed. Because the configuration of the timer depends on the microcontroller used, this process will not be covered in the tutorial.

In order to avoid losing events, interrupt routines should run as fast as possible. So, we only use the interrupt routine to signal the main cycle to execute the actual time updating routine. This is done using a counter that represents the seconds elapsed not yet treated.

Important: a counter should be used instead of a flag. If these functions run in a more complex program with long procedures, it may happen that two or more seconds pass before the time updating function can run. If that happens, a flag will not capture how many seconds have elapsed and will wrongly signal that only one has passed.

So, in pseudo code, we will have:

volatile byte secondsElapsed;

byte time[6];

void interruptRoutine(){ //Routine that runs every second

    secondsElapsed++;

}

while(1){             //application main loop

  …

  while(secondsElapsed>0){

    updateTime(time);

    secondsElapsed–-;

  }

  …

}

Depending on the architecture of the microcontroller, we may need to protect the access to the secondsElapsed variable in the main loop. That’s needed if the decrement operation isn’t atomic and there’s the possibility that there will be a conflict with the interrupt routine. This will depend on the type of the architecture and will not be covered in this tutorial.

Finally, it’s important to not forget that the secondsElapsed variable will be modified in an interrupt routine and thus needs to be declared as volatile. This article explains very well why we need to use the keyword volatile in such case.

For the update time function, whenever a new second passes, we need to increment current second in the time structure. If 60 seconds pass, we need to increment the current minute and reset the current second. Then, we check if 60 minutes have passed, and if that’s the case, we increment the current hour, and so on, until the years. This maps to a chain of if conditions:

if(time[5]==60){              //seconds

    time[5]=0;

    time[4]++;

}else returns;

 

If(time[4]==60){              //minutes

    time[4]=0;

    time[3]++;

}else return;

…

The remaining code is similar, we just have to propagate the increments when the threshold for that time unit is crossed.

The only problem arises when we need to check if an increment in the current day crosses the end of the month, since the number of days vary with the current month. In order to solve that, we keep a byte array that contains the number of days per month, as shown in figure 2. We call this array “daysMonth”. Note that February is highlighted in red, since the number os days depends on the current year, a problem that we will address later.

Array with the days of each month.
Figure 2 – Array with the days of each month.

So, in the corresponding if block we have:

if (time[2] == daysMonth[ time[1] – 1 ] +1){

    time[1]++;          //Increment the current month

    time[2]=1;          //Reset the current day

}

Note that the first minus 1 takes in consideration that the months are represented between 1 and 12 and arrays are indexed from 0 to size minus 1. The plus 1 takes into account that days are represented between 1 and the number of days that month has, so the last day of the month is valid.

Finally, we need to address leap years, which cause the number of days in February to vary. So, when a year changes, we check if it is or not a leap year and update the daysMonth array accordingly:

if(time[1]==13){

    time[1]=1; //resets the current month

    time[0]++;

    if(time[0]%4==0) daysMonth[1]=29;     //Leap year

    else daysMonth[1]=28;

}

We need to take into account that the precision of this algorithm depends on the precision of the counter, which depends on the precision of the clock signal of the microcontroller. If the clock is not accurate, a significant error will accumulate over time. One way of solving this issue is to have an external, more accurate, time source, which is used for periodically correcting the current time and date.

Suggested readings

2 thoughts on “Counting time with interruptions”

  1. Pingback: IoT: Temperature logger | techtutorialsx

  2. Pingback: Real Time Clock: DS3231 | techtutorialsx

Leave a Reply

Discover more from techtutorialsx

Subscribe now to keep reading and get access to the full archive.

Continue reading