3

I have an arduino Uno board with a screen attached on top (http://www.freetronics.com.au/pages/16x2-lcd-shield-quickstart-guide#.VUf0tMWN0iT This is the screen).

I'm trying to get it so that the screen displays the time in hours, minutes and seconds and counts on a 24 hours loop. Like a digital clock. I have it so that it can count but it just counts up to 99 then repeats.

I have this code which counts milliseconds but I need minutes and hours

    #include <Wire.h>
    #include <LiquidCrystal.h>

    LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

    void setup()
{
    lcd.begin( 16, 2 ); 
}

    void loop()
{
    lcd.setCursor ( 0, 1);
    lcd.print(millis());
}

7 Answers 7

1

Why not trying the timer interrupt from atmega328 uC. You'll also learn something usefully and I think is more interesting than using a library and some methods ( functions) without knowing how they are implemented.

It's really simple to make a digital clock just by interrupting every second. In ISR you'll just have to update the the values for seconds/minutes/hours. For displaying on the LCD, if you don't want to mess with LCD commands and bytes, you can use LiquidCrystal.

Example of code:(here I also used an analog pin for modifying time)

#define F_CPU   16000000UL
#include <avr/delay.h>
#include <avr/io.h>
#include <string.h>
#include <avr/interrupt.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);


const short buttonPin = 5;
volatile unsigned char seconds;
volatile unsigned char minutes;
volatile unsigned char hours;
void update_clock()
{
    seconds++;
    if (seconds == 60)
    {
        seconds = 0;
        minutes++;
    }
    if(minutes==60)
    {
      minutes=0;
      hours++;
    }
    if(hours>23)
    {
      hours=0;
    }


}

ISR(TIMER1_COMPA_vect)
{

  update_clock(); 

}

void setup() 
{                



  // initialize Timer1
  pinMode(A5, INPUT_PULLUP);
  cli();          // disable global interrupts
  TCCR1A = 0;     // set entire TCCR1A register to 0
  TCCR1B = 0;     // same for TCCR1B

  // set compare match register to desired timer count:
  OCR1A = 15624;
  // turn on CTC mode:
  TCCR1B |= (1 << WGM12);
  // Set CS10 and CS12 bits for 1024 prescaler:
  TCCR1B |= (1 << CS10);
  TCCR1B |= (1 << CS12);
  // enable timer compare interrupt:
  TIMSK1 |= (1 << OCIE1A);
  sei();          // enable global interrupts
  lcd.begin(16, 2);
  lcd.print("HH:MM:SS");
  Serial.begin(9600); 


}

void display_on_lcd()
{

  lcd.setCursor(0, 1);
  lcd.print(hours);
  lcd.setCursor(2,1);
  lcd.print(":");
  lcd.setCursor(3,1);  
  lcd.print(minutes);
  lcd.setCursor(5,1);
  lcd.print(":");
  lcd.setCursor(6,1);
  lcd.print(seconds);


}


void loop()
{

  display_on_lcd();

  Serial.println(analogRead(5));
  if(analogRead(buttonPin)<1000)
  {
    if(minutes==59)
    {
      minutes=0;
      if(hours==23)
        hours=0;
      else
        hours++;
    }
    else
    {
      minutes++;
    }
  }


}

Just search and you'll find a lot of examples.

5
  • 1
    Nice sample! Do you think it's better to use the Timer Interrupts instead of the millis() function?
    – Gonza
    Commented May 7, 2015 at 18:46
  • 1
    I really don't know how millis() is implemented to say why my implementations in bettter that one with millis(). I thought that is more "natural" to implement with timer interrupt compare, cause in real embedded you don't have a millis() function. Also, millis() return an unsigned long and that is a lot of bits. If you have a microcontroller with limited memory, you can't declare & store a lot of unsigned long variables just for calculating time. Also, think about that, for example, in the code with millis() you call the function on each iteration in while-loop and update the values.
    – 23ars
    Commented May 7, 2015 at 19:12
  • 1
    those calls to function will generate delays ( clock cycles). also, for this type of problem you have to use extra variables? why to declare 3 more variables unsigned long currentMillis, previousMillis, elapsedMillis; when I only need seconds/minutes/hours. extra code! not talking about how much clock cycles take the branches. Again, not saying that is better with timer interrupts compare, but maybe the code is cleaner, less memory is used etc. millis() is more like a nice to have function, and was developed just to make development simpler but not to use in real projects.
    – 23ars
    Commented May 7, 2015 at 19:19
  • 1
    Thanks for the clarification! yes, im with you, its more reliable and cost-efective. Thanks again
    – Gonza
    Commented May 7, 2015 at 20:54
  • Simple but really unnecessary. You already have a timer interrupt doing this in the core, why do you need a second one? Now you've got two timers tied up instead of one. Now Timer0 and Timer1 are both tied up counting time and now you've made Timer1 unsuitable for PWM. Why not just stick with the timer interrupt that's already incrementing milliseconds and calculate the time from that?
    – Delta_G
    Commented May 30, 2020 at 23:11
4

Im with @arvid-jense on this one, if you are working on a serious Digital Clock, then you should get the Real Time Clock.

Now if you are learning Arduino and want to play with your LCD Minidisplay and build a clock, you must know some important limitations of the millis() function on this project:

  • The mills function will overflow, after approximately 50 days.
  • Your clock will be inexact because the cycles of the Arduino loop, you will be checking how much mills has passed on each loop. (it will be lagging ~ 10 seconds each day, so on day 7 you will be one minute late)
  • the parameter for millis is an unsigned long, errors may be generated if a programmer tries to do math with other datatypes such as ints.

That being said

#include <Wire.h>
#include <LiquidCrystal.h>

/**
 * Clock Variables
 */
unsigned long currentMillis, previousMillis, elapsedMillis;
int seconds, minutes, hours;

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup()
{
    lcd.begin( 16, 2 ); 
}

void loop()
{
    setClock();
    /**
     * After set clock now you have 3 int variables with the current time
     */
     //seconds
     //minutes
     //hours
     lcd.setCursor ( 0, 1);
     lcd.print(millis());
}

void setClock()
{
    currentMillis = millis();
    elapsedMillis += currentMillis - previousMillis;

    /**
     * If we use equals 1000 its possible that because of the mentioned loop limitation
     * you check the difference when its value is (999) and on the next loop its value is (1001)
     */
    if (elapsedMillis > 999){
        seconds++;
        elapsedMillis = elapsedMillis - 1000;
    }

    if (seconds == 60){
        minutes++;
        seconds = 0;
    }
    if (minutes == 60){
        hours++;
        minutes = 0;
    }
    if (hours == 24){
        hours = 0;
    }

    previousMillis = currentMillis;
}

Nice tutorial on building an Alarm Clock

6
  • 2
    Your MILLIS_OVERFLOW is wrong. On recent versions of the Arduino library, millis() overflows at 2^32 (49.7 days), which means that testing for the overflow is useless. Commented May 10, 2015 at 12:27
  • Didn't know, thanks! i updated the constant MILLIS_OVERFLOW. (it's a digital clock, testing for the overflow isn't useless). Anyway, i think that the proposed solution by @23ars using Timer Interrupts is better than mine.
    – Gonza
    Commented May 11, 2015 at 14:03
  • This code is still incorrect. Testing for the overflow is definitely useless: per the rules of modular arithmetic, which are guaranteed for unsigned numbers by the C and C++ languages, currentMillis - previousMillis will always yield the right answer, regardless of overflows. Commented May 11, 2015 at 15:05
  • @EdgarBonet sorry, but i don't get it. Did you test this code on Arduino? Please could you explain how this code should work without testing for the overflow. Thanks!
    – Gonza
    Commented May 11, 2015 at 19:39
  • 2
    I tested it right now. When millis() overflows, it goes like crazy changing the time at each loop iteration. If you remove the test for the overflow, it works just fine. If you read carefully the liked article on modular arithmetic, it should be obvious that the test is useless. Commented May 11, 2015 at 20:02
2

Others mention a Real-Time Clock (RTC), and it may look like the obvious choice, but maybe it isn't. All a RTC does is keep counters for hours, minutes, seconds, date and month, but your microcontroller can do that as well.
Unfortunately the Uno doesn't have a crystal, but a ceramic resonator, which isn't nearly as accurate. Not that it matters much, even a crystal-based RTC may deviate several seconds a day. You can measure the error over a long time, and correct at regular times in software, but this is tedious.

If you want long-time accuracy there are two cheap ways:

  1. use an atomic clock receiver. In Europe there's DCF77, in North-America it's WWVB which broadcasts very accurate date and time information. A receiver module for these costs around 10 dollar
  2. use the mains frequency to clock your clock. On the long term this is extremely accurate (just think about your alarm clock: how often do you have to adjust that?) In Europe you would count 100 half periods per second (120 in the US). When your counter reaches that count, reset it and increment the seconds. If seconds reaches 60, reset it and increment minutes. And so on. If you're not exactly electronics-savvy you can ask on electronics.stackexchange.com to help you out. It's not difficult.
3
  • Getting a RTC over an Arduino will do you much better. The whole point of an RTC is to use a temperature stable crystal that is guaranteed to keep N seconds of accuracy every year. The accuracy varies by model but the arduino xtals or ceramics will drift wildly faster
    – benathon
    Commented May 11, 2015 at 12:46
  • @portforwardpodcast: yes, the Arduino ceramic resonators will drift more than a crystal, but even a crystal-controlled RTC can drift a couple of seconds a day. That's a minute a month. You'll have to synchronize your clock from time to time anyway, so why not allow the Arduino's relative inaccuracy and sync once a day. With an RTC you'll have to do that too, only less frequent. Commented May 11, 2015 at 13:50
  • Also, while there exist temperature-compensated oscillators, the 32kHz crystals used for RTCs are not temperature stable. Commented May 11, 2015 at 14:38
1

Not sure what you're asking, but getting a Real Time Clock module will make your life a lot easier when dealing with time. Maybe post some sample code to show what your issue is.

1

Well you need to break this problem down.

If you are currently able to count in milliseconds, then you know 1000 milliseconds equal 1 second, so every time the milliseconds equals 1000, you reset it to 0 and increment the seconds by 1.

You keep going from here, if seconds equals 60, then you increase minutes by 1, and reset seconds to zero.. You get the picture.

1

Thanks everyone for your help! I've ended up with the code below which works quite well, there's the odd glitch with the seconds which is for some reason fixed if milliseconds is added on the end, but other than that it works very well.

#include <Wire.h>
#include <LiquidCrystal.h>

#define MILLIS_OVERFLOW 34359738

/**
* Clock Variables
*/
unsigned long currentMillis, previousMillis, elapsedMillis;
int seconds, minutes, hours;

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup()
{
  Serial.begin(9600);
  lcd.begin( 16, 2 ); 

}

void loop()
{
    setClock();
    /**
   * After set clock now you have 3 int variables with the current time
   */
   //seconds
   //minutes
   //hours
     lcd.setCursor ( 0, 1);
     lcd.print(hours);
     lcd.print(":");
     lcd.print(minutes);
     lcd.print(":");
     lcd.print(seconds);
     lcd.print(":");
     lcd.print(elapsedMillis);

}

void setClock()
{
    currentMillis = millis();
    /**
     * The only moment when currentMillis will be smaller than            previousMillis
     * will be when millis() oveflows
    */
    if (currentMillis < previousMillis){
    elapsedMillis += MILLIS_OVERFLOW - previousMillis + currentMillis;
} else {
    elapsedMillis += currentMillis - previousMillis;
}

/**
 * If we use equals 1000 its possible that because of the mentioned loop limitation
 * you check the difference when its value is (999) and on the next loop its value is (1001)
 */
if (elapsedMillis > 999){
    seconds++;
    elapsedMillis = elapsedMillis - 1000;
}

if (seconds == 60){
    minutes++;
    seconds = 0;
}
if (minutes == 60){
    hours++;
    minutes = 0;
}
if (hours == 24){
    hours = 0;
}

previousMillis = currentMillis;
}
1

The takeaway from the previous answers is that your best option is to use an RTC (real-time clock). Failing that, you can emulate the RTC using millis(), at the cost of a significant drift, especially if your Arduino is clocked off a ceramic resonator.

I would just like to add that you do not have to write the logic of that millis()-based “soft RTC” yourself, as this has already implemented in the RTC_Millis class from Adafruit's RTClib:

#include <RTClib.h>
#include <LiquidCrystal.h>

RTC_Millis rtc;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup() {
    rtc.begin(DateTime(F(__DATE__), F(__TIME__)));
    lcd.begin(16, 2);
}

void loop() {
    static DateTime last_time_printed;
    DateTime now = rtc.now();
    if (now != last_time_printed) {
        lcd.setCursor(0, 1);
        lcd.print(now.timestamp(DateTime::TIMESTAMP_TIME));
        last_time_printed = now;
    }
}

One nice thing about this option is that, if you later opt for a real RTC, you will only need to do minimal changes to your code. Another nice thing is that, if you measure the drift of your clock, you can easily correct it: just replace RTC_Millis with RTC_Micros and call rtc.adjustDrift() in setup(). You will be able to adjust the drift with a 1 ppm resolution. Note that both RTC_Millis and RTC_Micros are immune to rollover problems.

2
  • How stable is a ceramic resonator though? Doesn't the drift vary? A quartz crystal based clock is quite stable, so an adjustment factor should be quite effective, but doesn't the speed of a ceramic resonator vary by a good bit more than a quartz crystal?
    – Duncan C
    Commented May 30, 2020 at 22:27
  • @DuncanC: It does vary quite a bit indeed, but the average drift is typically larger than the drift variations. Commented May 31, 2020 at 0:42

Not the answer you're looking for? Browse other questions tagged or ask your own question.