Arduino Clock investigation

The first UNO project I built was a clock/temperature/humidity sensor. Since then I have built a few more that log temperature and display time. My aim has always been minimal battery power.
This is the latest venture; placed here because it is does not qualify as a project, in my opinion.

The aim was to investigate the ATMega328P time keeping, using a minimum of parts and very little power. While keeping reasonably accurate time.

Design:
The micro spends most of its time asleep. Timer 2 wakes it up every second to update a counter. Timer 2 is clocked by a 32,768KHz watch crystal. When running, the micro uses the internal 8MHz clock. The push button displays days/hours/minutes/seconds on a 4 digit 7 segment display for 3 seconds. Holding the push button for more than 1 second will switch the micro into set time mode.

Time Keeping test.
6 hours 25 minutes 38 seconds compare with PC running stop watch, same time on both.

Power measurements – USB 5V.
Time display 25.4mA, sleep mode 38uA. (ATMega328P 21uA, Display 17uA when asleep)

Power measurements – LiPo 3.7V 400mAH, Pololu DC DC step up 5V.
All measurements taken at battery +ve.
Pololu DC DC – no load 80uA. (twice the circut current when asleep)
Time display 42mA, sleep mode 129uA.

The problem is the MAX7219 requires 5V and LiPo’s come as 3.7V so we need DC to DC step up.
MAX6951 is exactly the same as the MAX7219 but runs from 2.7V.

So possibly a design using the MAX6851 and 400mAH LiPo would last about a year and maybe a minute or two per month inaccuracy. Pretty good in my opinion.

Arduino code available on request.

3 Likes

@James46717

Do you have any longer time keeping tests using this method? Ie Days/weeks? I would be interested in the longer term accuracy of time keeping in this way.

Hi James,

Very cool! I would like to see your code if you don’t mind posting it!

1 Like

At this time 6 hours is the longest, but stay tuned. I intend to do a longer test when I get it setup better.
This first effort was to check current drain and get basic time accuracy confidence.

Essentially this is like an RTC device.
I have built 2 clocks using RTC’s, One is pretty good, the other gains a minute or so a week.
Admittedly the devices are cheap and not sold as extremely accurate.

Originally I was going to use this to turn on a water solenoid, but found a Raspberry Pi with WiFi a much better option. Easy to change settings from desk than open up cabinet in garden.

Anyway, will post update on accuracy soon.
Cheers

PS will post code soon too, need to tidy it up a bit.

2 Likes

Here is the code. Sorry about not noticing the reformatted option. Looks much better now.
But the documentation information I have at the start would not preformat, don’t know why.

Cheers.

#include <avr/sleep.h>
#include <LedControl.h>

//#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
//#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

#define LED         9      // LED attached    pin 15    // changes state every time micro wakes up, ie 1 sec on 1 second off.
                                                                    // remove in final design as LED uses power half the time.
#define DIN         8      // Display data    pin 14   // 7 seg display control
#define CS          7      // Display enable  pin 13
#define CLK         6      // Display clock   pin 12

#define PUSHBUTTON  2      // Push button on  pin 4     // press button to show counter

#define DEVICES     1             // Only 1 Display
#define ADDRESS     0             // Only 1 Display, address is device 0
#define BRIGHTNESS  1             // Display brightness

#define DAY_U       7             // Days upper char display position 7
#define DAY_L       6             // Days lower char display position 6
#define HOUR_U      5             // Hours upper char display position 5
#define HOUR_L      4             // Hours lower char display position 4
#define MINUTE_U    3             // Minutes upper char display position 3
#define MINUTE_L    2             // Minutes lower char display position 2
#define SECOND_U    1             // Seconds upper char display position 1
#define SECOND_L    0             // Seonds lower char display position 0

volatile bool ButtonON = false;   // flags button has been pressed, used in ISR
unsigned int seconds = 0;
unsigned int minutes = 0;
unsigned int hours = 0;
unsigned int days = 0;

LedControl lc = LedControl(DIN, CLK, CS, DEVICES);

//=================================================================================
void ButtonPressedISR() {         // button pressed interrupt service routine
  sleep_disable();
  ButtonON = true;
}
//==================================================================================================
void setup(){

  ADCSRA= 0;                    // ADC disable, reduces current from 140uA to 21uA in sleep mode

  pinMode(LED, OUTPUT);       
  digitalWrite(LED, HIGH);

  lc.setIntensity(ADDRESS,BRIGHTNESS);  // set brightness
  lc.clearDisplay(ADDRESS);             // clear display 

  pinMode(PUSHBUTTON, INPUT_PULLUP);    // Push buton input, set internal pullup, button active when low level
  attachInterrupt(digitalPinToInterrupt(PUSHBUTTON), ButtonPressedISR, LOW);  // INT0 on D2 pin 4
  EIFR = bit(INTF0);                    // clear interrupt flag for INT0

  Timer2_init();
}
//==================================================================================================
void loop() {

// Push button pressed, display date time for 3 seconds
  if (ButtonON) {
    lc.shutdown(ADDRESS,false);           // startup display

    lc.setChar(ADDRESS,SECOND_U,seconds/10,false);      // upper character
    lc.setChar(ADDRESS,SECOND_L,seconds%10,false);      // lower character
    lc.setChar(ADDRESS,MINUTE_U,minutes/10,false);    
    lc.setChar(ADDRESS,MINUTE_L,minutes%10,true);    
    lc.setChar(ADDRESS,HOUR_U,hours/10,false);    
    lc.setChar(ADDRESS,HOUR_L,hours%10,true);    
    lc.setDigit(ADDRESS,DAY_U,days/10,false);    
    lc.setDigit(ADDRESS,DAY_L,days%10,true);    

    unsigned long t = millis();
    while (millis() < t + 2000) {}      // wait 2 seconds

    lc.clearDisplay(ADDRESS);           // clear display 
    lc.shutdown(ADDRESS,true);          // shutdown display, power down mode

    ButtonON = false;                   // clear push button flag
  }
// back to sleep
  cli();                                // disable interrupts while setting up sleep mode
  set_sleep_mode(SLEEP_MODE_PWR_SAVE);  // set the sleep mode we want, will wake on Timer2 interrupt
  sleep_enable();                       // enables the sleep bit in the MCUCR register.
  sleep_bod_disable();                  // disable brown out detect in sleep
  sei();                                // enable interrupts, Timer 2 and Push button
  sleep_mode();                         // sets SMCR SE bit 0. This causes micro to enter sleep mode.
                                        // micro will start from here after interrupts executed
                                        // sleep disabled in Button press interrupt
}
//==================================================================================================
//==================================================================================================
void Timer2_init(void) {
// Timer 2 setup, CTC Mode. Allows varying the clk if not exactly 32K768Hz.
//    Counter B used. OC2B has clock rate.
//    Register bits cleared on reset.
// 
// TCCR2A  Control Register 
//    COM2A1 = 0, COM2A0 = 0, COM2B1 = 0, COM2B0 = 1, WGM21 = 1, WGM21 = 0.
//    Toggle OC2B on compare match. CTC mode OCRB has compare value = TOP.
// 
// TCCR2B  Control  Register 
//    FOC2A = 0, FOC2B = 0, WGM22 = 0, CS22 = 1, CS21 = 0, CS20 = 1.
//    CS22,21,20 = 101. Prescaler 128.
//
// TIMSK2  Interrupt Mask Register 
//    OCIE2B = 1, OCIE2A = 0, TOIE2 = 0.
//    OCIE2B = 1.  Timer B compare match interrupt.
//
// ASSR  Asynchronous Status Register 
//    EXCLK = 0, AS2 = 1, TCN2UB = 0, OCR2AUB = 0, OCR2BUB = 0, TCR2AUB = 0, TCR2BUB = 0.
//    AS2 = 1.  Timer2 Asynchronous mode.
//    These bits cleared on reset, only need to set AS2.
//
// Interupt frequency  = 32768Hz / (Prescale * count) = 32768/(128 * 256) = 1

  TCCR2A |= _BV(COM2B1);
  TCCR2B |= _BV(WGM22);
  TCCR2A |= _BV(WGM21);
  TCCR2A |= _BV(WGM20);
  TCCR2B |= _BV(CS21);        

  TIMSK2 |= _BV(OCIE2B);    //enable timer2B compare match
  OCR2A = 127;              // compare match value, closest to 1 second
                            // DVM Hz mode measured: 126 = 1.007s, 127 = 0.999s, 128 = 0.991s
  DDRD |= _BV(0x03);        // Set Data Direction PD3, D3, pin 5, OCR2B as output

  ASSR |=_BV(AS2);
}
//==================================================================================================
ISR(TIMER2_COMPB_vect) {

  digitalWrite(LED, !digitalRead(LED));    // toggle LED, could remove in final design
  if (++seconds == 60) {
    seconds = 0;
    if (++minutes == 60) {
      minutes = 0;
      if (++hours == 24) {
        hours = 0;
        if (++days == 100) { days = 0; }   // can only display 99 days
      }
    }
  }
}
//==================================================================================================
//==================================================================================================
//==================================================================================================
1 Like

Hi James,

Would you mind editing your last post using the preformatted text option?
When you post code into the forum, use the </> option to keep all the proper indents and spaces.

Ran clock for 23 hours 54 minutes 38 seconds. It lost 4 seconds compared to actual time.

This is similar to the RTC I used in another clock; it gains a minute every 10 days so.
The web page says it can gain or lose up to 2 seconds per day.

But another clock using a DS1307 chip is pretty accurate.
Anyway, an exercise in minimum parts and current drain.
By far the Arduino clock I made here uses the least parts and is the least power hungry.

2 Likes