Counting time with Arduino (basic clock)

 Author:   Posted on:   Updated on:  2018-11-17T16:33:28Z
When it comes to counting time using an Arduino or other MCU platform, the use of a RTC (real time clock) circuit is highly recommended. These devices are cheap, have good accuracy and keep counting time even when the main MCU is not powered. They run from a small battery and draw low current. There are quite a lot of Arduino libraries that deal with time and make the interaction with a time source easy. One of these is the Time library by Paul Stoffregen. It is a well written library with internal time based on standard Unix time time_t (number of seconds passed since Jan 1, 1970).

But I wanted something even more basic. I wrote a 50 lines of code function that increments seconds variable each time it is called. When seconds overflow (reach 60), it increments minutes variable and resets seconds. And so on. Only when a time variable changes, it is printed on the output device (16x2 LCD in this case). Around this function I added code that turns the Arduino into a common clock. As a prototype as used the LCD and keypad shield fitted to an Arduino Uno compatible board. The code should compile on any other development board because it doesn't use specific functions or libraries. You will have to adjust LCD pin configuration and keypad buttons (require modification if using buttons connected to digital input pins instead of the analog keypad).

Counting time with Arduino (basic clock)
The time variables are global, defined at the beginning of the sketch. Also, short string arrays of months and week days are defined.
byte hh, mm, ss;
byte dd, MM, YY;
byte wd;

byte daysOfMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const char* months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
const char* wdays[7] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
I also have functions that display each of these variables at specific locations on LCD screen. Let’s get to the time counting function, which I will be explaining in detail. At each call of this function, seconds (ss) are incremented.
void countTime() {
  // count seconds
  ss++;
  if (ss < 60) {
    dispSec();
    return;
  }
  ss = 0;
  dispSec();

  // count minutes
  mm++;
  if (mm < 60) {
    dispMin();
    return;
  }
  mm = 0;
  dispMin();

  // count hours
  hh++;
  if (hh < 24) {
    dispHour();
    return;
  }
  hh = 0;
  dispHour();

  dd++; wd++;
  if (wd == 7) wd = 0;
  if (dd < daysOfMonth[MM]) {
    dispDay();
    return;
  }
  dd = 0;
  dispDay();

  MM++;
  if (MM < 12) {
    dispMonth();
    return;
  }
  MM = 0;
  dispMonth();

  YY++;
  if ((YY % 4) == 0) daysOfMonth[1] = 29;
  else daysOfMonth[1] = 28;
  if ((YY % 100) == 0) daysOfMonth[1] = 28;
  dispYear();
}
The first lines of code in the function increment seconds. As long as they haven’t reach 60, the function updates display and returns. When seconds reach 60 their value defaults to 0 and the function no longer returns at this point. It’s time to increment minutes. Similar to seconds, if minutes haven’t reached 60, the function displays them and returns. When minutes reach 60, they are cleared and function advances to hour. The same happens to hours as long as they are less than 24. When hours reach 24 it’s time to increment not only day, but also week day variable. Weekday variable counts from 0 to 6 and days maximum value depends on current month (taken from array). The month increments up to 12 and then year is incremented. Year does not have a limit. But at this point we must take care of leap years. If an year is multiple of four it has 366 days. There is an exception to this: if the year is a multiple of 100, but not divisible by 400 then it is not a leap year and has 365 days. There is no need to deal with 400 divisibility because my code is not ready to reach year 2400. I’ll probably release a new version by then 😉 For any leap year, the number of days for February must be adjusted in the array.

That’s pretty much everything needed for this function to run properly. But there’s something that needs to be taken into consideration: user should be able to edit time and date variables. I came up with a simple 2 buttons only interface that’s common among digital clocks. You push one button to cycle through minute, hour, day, month and year and another button to increment selected variable. The selected variable blinks on LCD. When it overflows, it should return to initial value (0 for hour and minute, 1 for day and month, 2000 for year).

The overflow of day and the increment of month and year requires adjustment of week day. For days overflow, the weekday should return to the value it was in the first day of the current month. If the days in a month would be divisible by 7, this would happen. But since they are not, we calculate a remainder from the closest multiple of 7 to the days in current month and adjust current weekday with it (i.e. 35 – 31 = 4, for a 31 days month).
        case 3: dd = (dd + 1) % daysOfMonth[MM];
          wd = (wd + 1) % 7;
          if (dd == 0) wd = (wd + 35 - daysOfMonth[MM]) % 7;
Month increment code adds the days from the current month to weekdays variable. After that it can perform the increment. Months overflow requires a similar approach to days overflow. We calculate the remainder from the closest multiple of 7 to the days in current year and add it to week days (i.e. 371 – 365 = 6, for a non leap year).
        case 4: wd = (wd + daysOfMonth[MM]) % 7;
          MM = (MM + 1) % 12;
          if (MM == 0) {
            wd = (wd + 6) % 7;
            if ((YY % 4) == 0) wd = (wd + 5) % 7;
          }
Year increment is easier. We adjust week days with the number of days in the year. But what happens at overflow? To simplify things I chose the overflow to happen at 56 years. Why 56? Well, from 2000 to 2055, there are 14 leap years. The important thing is that the total number of days between Jan 1, 2000 and Dec 31, 2055 is divisible by 7. Therefore if the first is on Saturday, the latter must be on Friday and weekdays can increment without adjustment.
          unsigned int days = 365;
          if ((YY % 4) == 0) days = 366;
          if ((YY % 100) == 0) days = 365;
          wd = (wd + days) % 7;
          YY = (YY + 1) % 56;
The complete sketch contains about 300 lines of code and is available on GitHub. The next step would be adding the alarm function.

No comments :

Post a Comment

Please read the comments policy before publishing your comment.