Why is my time slow?

I'm trying to make a gift for a friends that's just a 180 day countdown timer. The format of the timer is like this: (first line) D:{days} H:{Hours} (second line) M:{Minute} S:{seconds}. To accomplish this, I'm using an arduino nano that is connected to a 16X2 LCD display. The code below works, but for some reason the seconds take about 4 times as long to update. Other than that, it counts down properly. as a bonus i want to try to make it so in the event its unplugged it stores the last time so it doesn't just restart the timer from 180 days, but i have no clue how to do it.

Heres my code

#include <TimeLib.h>
#include <LiquidCrystal.h>

const int rs = 12;
const int en = 11;
const int d4 = 5;
const int d5 = 4;
const int d6 = 3;
const int d7 = 2;

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

unsigned long targetTime;
bool timeReached = false;  // Flag to track whether "Time Up!" has been displayed

void setup() {
  // Initialize the LCD
  lcd.begin(16, 2);

  targetTime = now() + (180UL * 24 * 60 * 60);  // 180 days in seconds

  lcd.setCursor(0, 0);
  lcd.print("Countdown Timer");
}

void loop() {
  if (!timeReached) {  // Check if "Time Up!" has not been displayed
    unsigned long currentTime = now();
    unsigned long timeDiff = targetTime - currentTime;

    if (timeDiff > 0) {
      unsigned long days = timeDiff / 86400;
      unsigned long hours = (timeDiff % 86400) / 3600;
      unsigned long minutes = (timeDiff % 3600) / 60;
      unsigned long seconds = timeDiff % 60;

      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("D:");
      lcd.print(days);
      lcd.print(" H:");
      lcd.print(hours);
      lcd.setCursor(0, 1);
      lcd.print("M:");
      lcd.print(minutes);
      lcd.print(" S:");
      lcd.print(seconds);
    } else {
      // If countdown is over
      timeReached = true;  // Set the flag to true
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Time Up!");
    }
  }

  delay(1000);  // Update every second
}

The time is slow because the crystal or resonator used for the MCU clock is not very accurate.

For accurate time without connection to the internet, you need a temperature compensated real time clock module, like the DS3231.

i want to try to make it so in the event its unplugged it stores the last time

A battery backed DS3231 keeps time during power off. Store the starting date and time of the interval to be timed in EEPROM, and on bootup, calculate the offset to the current time and date.

@jamesk123
Please show the link to the TimeLib.h library you used

is there any way to counter the inaccurate clock without the time clock module? i got the news late and im on a time crunch, i dont think i can order one in time

Are you saying it's running at 1/4 speed?

I didn't really "study" your code but it looks like ALL of the timing relies on the 1000ms delay. If that's what you're doing... NO GOOD! All of the other program steps take some time so that time is added to the loop time. (I wouldn't expect that other stuff to take 3 seconds, but if you've got nested loops that could be the case.)

Look at millis() and Blink Without Delay.

millis() runs in the background so it's not slowed-down by whatever else the program is doing. But it rolls-over after about 45 days and you'll have to research how to do with that.

I made your project in Wokwi:

You can calibrate the CPU clock and apply a correction factor for the time passed. Unfortunately the TimeLib.h library has no easy way to do that, except to update the current time from an accurate external source.

One way to calibrate the CPU clock is to count the number of CPU cycles between two ticks of a GPS PPS (pulse per second) output, which is accurate to a few nanoseconds. The following code, derived from code in this tutorial does that.

// Frequency timer using input capture unit
// Author: Nick Gammon
// Date: 31 August 2013
// added averaging JR 2015
// Input: GPS 1PPS capture signal on Pin D8

volatile boolean first;
volatile boolean triggered;
volatile unsigned int overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;

// timer overflows (every 65536 counts)
ISR (TIMER1_OVF_vect)
{
  overflowCount++;
}  // end of TIMER1_OVF_vect

ISR (TIMER1_CAPT_vect)
{
  // grab counter value before it changes any more
  unsigned int timer1CounterValue;
  timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;

  // if just missed an overflow
  if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 0x7FFF)
    overflowCopy++;

  // wait until we noticed last one
  if (triggered)
    return;

  if (first)
  {
    startTime = (overflowCopy << 16) + timer1CounterValue;
    first = false;
    return;
  }

  finishTime = (overflowCopy << 16) + timer1CounterValue;
  triggered = true;
  TIMSK1 = 0;    // no more interrupts for now
}  // end of TIMER1_CAPT_vect

void prepareForInterrupts ()
{
  noInterrupts ();  // protected code
  first = true;
  triggered = false;  // re-arm for next time
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;

  TIFR1 = bit (ICF1) | bit (TOV1);  // clear flags so we don't get a bogus interrupt
  TCNT1 = 0;          // Counter to zero
  overflowCount = 0;  // Therefore no overflows yet

  // Timer 1 - counts clock pulses
  TIMSK1 = bit (TOIE1) | bit (ICIE1);   // interrupt on Timer 1 overflow and input capture
  // start Timer 1, no prescaler
  TCCR1B =  bit (CS10) | bit (ICES1);  // plus Input Capture Edge Select (rising on D8)
  interrupts ();
}  // end of prepareForInterrupts


void setup ()
{
  Serial.begin(115200);
  Serial.println("Frequency Counter");

  pinMode(8, INPUT_PULLUP);
  pinMode(7, OUTPUT);
  digitalWrite(7, LOW);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);


  // set up for interrupts
  prepareForInterrupts ();
} // end of setup

void loop ()
{
  static unsigned long average = 0;
  static int n = 0;
  // wait till we have a reading
  if (!triggered)
    return;

  PINB |= (1 << 5); //blink LED

  // period is clock cycles in one second
  unsigned long elapsedTime = finishTime - startTime;

  Serial.println (elapsedTime);
  average += elapsedTime;
  n++;
  if (n == 10) {
    Serial.print("System clock count, average of ten: ");
    Serial.println(average / 10);
    n = 0;
    average = 0;
  }


  // so we can read it
  delay (500);

  prepareForInterrupts ();
}   // end of loop

I rewrote almost the entire sketch to get rid of the TimeLib library. Now you can adjust the speed of the clock by changing the value of the ONE_SECOND constant. You said your clock was running at 1/4 speed, so I set this constant to 250000 to compensate. Decrease this number to speed up the clock. Increase it to slow down the clock. Let it run for a few minutes and compare it to your phone stopwatch or something to see how accurately it runs, and adjust it accordingly.

#include <LiquidCrystal.h>

const int rs = 12;
const int en = 11;
const int d4 = 5;
const int d5 = 4;
const int d6 = 3;
const int d7 = 2;

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

int ddd=180, hh=0, mi=0, ss=0;
bool timeUp = false;
unsigned long lastSecond = 0UL;
const unsigned long ONE_SECOND = 250000UL;

void setup() {
  // Initialize the LCD
  lcd.begin(16, 2);

  lcd.setCursor(0, 0);
  lcd.print("Countdown Timer");

  printTime();
}

void loop() {
  if (timeUp == false) {
    if ((micros() - lastSecond) >= ONE_SECOND) {
      lastSecond += ONE_SECOND;
      // count down one second
      ss--;
      if (ss < 0) { ss += 60; mi--; }
      if (mi < 0) { mi += 60; hh--; }
      if (hh < 0) { hh += 24; ddd--; }
      if (ddd < 0) {
        ss = 0;
        mi = 0;
        hh = 0;
        ddd = 0;
        timeUp = true;
      }
      printTime();
    }
  }
}

void printTime() {
  lcd.setCursor(0, 1);
  if (timeUp == true) {
    lcd.print("*** Time Up! ***");
  }
  else {
    // Example:
    // 0123456789012345
    // 179d 23h 59m 59s
    if (ddd<100) lcd.print(' ');
    if (ddd<10) lcd.print(' ');
    lcd.print(ddd);
    lcd.print("d ");
    if (hh<10) lcd.print(' ');
    lcd.print(hh);
    lcd.print("h ");
    if (mi<10) lcd.print(' ');
    lcd.print(mi);
    lcd.print("m ");
    if (ss<10) lcd.print(' ');
    lcd.print(ss);
    lcd.print('s');
  }
}

I did not look at the post carefully enough to notice that your clock appears to be slow by a factor of four, and that certainly should not be the case.

TimeLib.h should keep time to within a couple of minutes per day, running off the CPU clock. That is demonstrated by this simple code running on an Arduino Uno R3, which sets the clock to the correct time, then prints the clock time every five minutes.

You can see that successive five minute intervals run a bit slow (about 50 ms every 5 minutes) compared to a PC timestamp. Try it on your setup and compare results.

18:30:59.326 -> unixTime = 1703615460
18:30:59.326 -> now() = 1703615460
18:30:59.326 -> 26/12/2023 18:31:00
18:34:59.358 -> 26/12/2023 18:35:00
18:39:59.400 -> 26/12/2023 18:40:00
18:44:59.450 -> 26/12/2023 18:45:00

Code

//time library clock demo, running off the CPU timebase
#include "TimeLib.h"

tmElements_t te;  //Time elements structure
time_t unixTime; // a time stamp

void setup() {
  Serial.begin(115200);

  // set internal clock time and date. Alternatives 
  // setTime(hr,min,sec,day,mnth,yr);
  te.Second = 0;
  te.Hour = 18; //11 pm
  te.Minute = 31;
  te.Day = 26;
  te.Month = 12;
  te.Year = 2023 - 1970;  //epoch is 1/1/1970
  unixTime =  makeTime(te); 
  Serial.print("unixTime = ");
  Serial.println(unixTime);
  setTime(unixTime); //set the current time to the above entered
  Serial.print("now() = ");
  Serial.println(now());
  // print as date_time
  print_date_time();
 }
 
void print_date_time() { //easy way to print date and time
  char buf[40];
  sprintf(buf, "%02d/%02d/%4d %02d:%02d:%02d", day(), month(), year(), hour(), minute(), second());
  Serial.println(buf);
}

void loop() {
  static int minutes_past_midnight, last_print_time = 0;

  // check the clock
  minutes_past_midnight = hour() * 60 + minute();
  
  // every 5 minutes, print the data and time
  if (minutes_past_midnight != last_print_time && minutes_past_midnight % 5 == 0) {
    last_print_time = minutes_past_midnight;
    print_date_time();
  }
}

The Time library runs a software real-time clock based off the millis() timer. The function now() returns a timestamp (the unix timestamp if you set the time correctly), so the easiest way to run code at one-second intervals is to watch for when the value returned by now() increments.

Storing the count when power was lost will do you no good, because you need to know how long the power was off in order to maintain the fixed target date. You need either a hardware RTC or an external time reference, which generally would be either a GPS receiver of an NTP server.

I really can't think of anything that would cause the seconds to be 1/4 speed, unless you are running a processor off the internal oscillator and have the fuses set wrong.

Apparently your mcu seems to run on 4MHz.

Did you try delay(250)?

Or better set a millis timer to 250 as suggested above?

If that's the case (in which case OP would be using the internal oscillator which is far less accurate than the external 16 MHz one), they should set the clock setting in the IDE to 4 MHz. Then millis() and micros() and related functions do run at the expected speed.

Would buttons for resetting the time be an option?
@jamesk123 Do you have two buttons that you could put on the clock as inputs for setting the time? The sketch could be rewritten so that the time could be set using buttons. But that would involve a rewrite to include a setting menu.