1

I am making an Arduino clock. You can set time to it, and then it keeps on going. The problem is that it is always within a minute off. It is not a major issue, but one that I would like to fix. I use the millis function. The program checks if millis is evenly divisible by 60,000(1 minute). After I set the time, millis is NOT at 0, meaning that is ahead of what I set by a number of seconds. How can I fix this? Code:

#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
int hours=0;
int minutes=0;
const int modePin=6; //button
int prevModeState=HIGH; //prev state of button
unsigned long timer;
int modeState;//current state
int mode=0;//mode of alarm
int buttonCounter=0;
const int incrementPin=9;
const int switchPin=7;
int incrementState;
int switchState;
int prevIncrementState=HIGH;
int prevSwitchState=HIGH;
int switching=0;



void setup() {
lcd.begin(16, 2);
lcd.setCursor(6, 0);
lcd.print("Time:");
lcd.setCursor(6, 1);
lcd.print("00:00");
pinMode(modePin, INPUT_PULLUP);
pinMode(incrementPin, INPUT_PULLUP);
pinMode(switchPin, INPUT_PULLUP);
}

void loop() {
modeState=digitalRead(modePin);
if(prevModeState!=modeState){
      prevModeState=modeState;
   if(modeState==LOW){
    buttonCounter++;
  if(buttonCounter%2==0){
    mode=0;
    lcd.clear();
    lcd.setCursor(6, 0);
    lcd.print("Time:");
    writeTime();
}
  else{
    mode=1;
    switching=0;
    lcd.clear();
    lcd.setCursor(4, 0);
    lcd.print("Set Time:");
}

   }
  } 
if(mode==0){
  timer=millis();
  if(timer%60000==0){
     minutes++;
    writeTime();
    }
}
else if(mode==1){
 incrementState=digitalRead(incrementPin);
  switchState=digitalRead(switchPin);
  if(prevSwitchState!=switchState){
    prevSwitchState=switchState;
    if(switchState==LOW){
    switching++;
    }
    if(switching==4){
      switching=0;
    }

    }





  if(prevIncrementState!=incrementState){
    prevIncrementState=incrementState;
    if(incrementState==LOW){
      if(switching==0){
        if(hours<14){
        hours+=10;
        }
        else if((hours>=14)&&(hours<20)){
        hours-=10;
        }
        else{
          hours-=20;
        }
        }

        else if(switching==1){
          if((hours==9)||(hours==19)){
            hours-=9;
          }
          else if(hours==23){
            hours-=3;
          }
          else{
          hours++;
          }
        }

        else if(switching==2){
          if(minutes<50){
             minutes+=10;
          }
          else{
            minutes-=50;
          }

        }
        else if(switching==3){
          if(minutes%10==9){
            minutes-=9;
          }
          else{
          minutes++;
          }
        }


    }

  }

  writeTime();
}
}

void writeTime(){
  lcd.setCursor(6, 1);
   if(minutes>=60){
        minutes=0;
        hours++;
        }
     if(hours>=24){
        hours=0;
     }
if(minutes<10){

      if(hours<10){
        lcd.print(String("0")+hours+":"+"0"+minutes);
      }
      else{
        lcd.print(hours+String(":")+"0"+minutes);
      }
    }
    else{
      if(hours<10){
         lcd.print(String("0")+hours+":"+minutes);
      }
      else{
         lcd.print(hours+String(":")+minutes);//possibly split if problems arise
      }

    }
}
3

Three things you can do - any combination of these is good, but the more the better:

  1. Use an RTC module to keep more accurate track of the time.
  2. Connect the Arduino to the internet and use NTP to get the exact time.
  3. Use the time provided by GPS.

The RTC chip can keep track of time when there is no external power, using just a very small button cell. The GPS and internet time are both very accurate.

Using either or both of those sources to set and maintain the correct time in the RTC chip is the best and most accurate way of keeping the right time on the Arduino.

To increase the accuracy of your existing method you should count seconds, not minutes.

Start by recording the current millis. When 1000 of them have passed you can increase your seconds. Don't rely on millis() % 1000 since you may miss it whilst doing other things. Also don't rely on your millisecond counter check executing at exactly 1000ms intervals - work on the principle of "at least 1000ms" and move the next check to 1000ms from where it should have happened, not where it did happen. This can help to combat some clock drift.

static unsigned long mscount = millis();

if (millis() - mscount >= 1000) {
    mscount += 1000;
    seconds ++;
}

Then every 60 seconds you can increase your minutes:

if (seconds == 60) {
    seconds = 0;
    minutes ++;
}
  • I am completely satisfied with the accuracy of the method I am using. What I need is a way to have the program to start at 0 milliseconds once I set the time. – Nick Solonko Jun 13 '16 at 22:44
  • 1
    @NickSolonko You could count in seconds instead of minutes - that would increase your accuracy to 1 second. Every 1000ms increase a seconds counter by 1. Every 60 seconds increase the minutes by 1. You control the seconds then, and decide where 0 seconds starts from. You can even include it in your display if you want. Also, don't look for "exactly % 1000" since you may miss it. Instead look for "at least 1000ms since I last ticked" and subtract 1000 from your tick counter. – Majenko Jun 13 '16 at 22:48
  • I am doing %60000 now and it works fine. I have been testing the clock for the last 3 hours and it is still only 7 seconds off. Is there a reason why doing %1000 will not work? – Nick Solonko Jun 13 '16 at 23:08
  • You only have a 1 millisecond window with %, and there is now way of saying only to execute it once for that millisecond. It is very unsafe. Do it the way I have shown you. – Majenko Jun 13 '16 at 23:12
  • 1
    @Gerben Nah - we should decimalize time. 100 seconds to the minute, 100 seconds to the hour, 10 hours to the day :P – Majenko Jun 14 '16 at 17:50
3

First of all, let me second Majenko in suggesting the use of an RTC. The Arduino Uno is clocked from a ceramic resonator, and these are nowhere accurate enough for proper timekeeping. C.f. the answers to How do I get an accurate time?.

Next, instead of counting seconds, I suggest you instead count milliseconds. I know it sounds like overkill, but it's actually simpler because you already have millis() returning a millisecond count:

void loop()
{
    uint32_t now = millis();
    static uint32_t last_minute;

    // Increment the time every minute.
    if (now - last_minute >= 60000) {
        last_minute += 60000;
        if (++minutes >= 60) {
            minutes = 0;
            if (++hours >= 24)
                hours = 0;
        }
    }

    // Do not advance the seconds when the user sets the time.
    if (time_is_being_set_by_the_user()) {
        last_minute = now;
    }
}

Oh, wait! No! Do not count milliseconds, instead count microseconds:

void loop()
{
    uint32_t now = micros();
    static uint32_t last_minute;

    // Increment the time every minute.
    if (now - last_minute >= 60000000) {
        last_minute += 60000000;

        // The rest does not change...
    }
}

Why? Because, as I said before, the ceramic resonator of your Arduino is not very accurate. You will eventually find out that it drifts significantly. However, if you compare to a good reference, you will be able to calibrate the drift. If, for example, you find that it drifts forward by 0.0025% (about 15 seconds per week, quite optimistic), you will be able to fix it simply by replacing 60000000 by 60001500. The same would work if counting milliseconds, except that you would only be able to do a coarse tuning. Oh, and do not worry about micros() overflowing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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