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.
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
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.)
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.
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.
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.
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.