External RTC via 32.768khz crystal

Hi I have a 32 768hz crystal. How can I use this as a RTC for Arduino uno? I have built several oscillator circuits using this crystal so I understand its operation. Thanks

You can't, PB6+PB7 are used for the onboard 16MHz crystal. In order to do what you want, you will need a barebone ATmega328 clocked from its internal oscillator, the 32KHz crystal connected to PB6+7 and then use Timer2 in async operation.

I use this RTC code for several sensors, and it works well with a "bare bones" ATmega328.

Fuses have to be set for 8MHz internal oscillator, and you need a 32 kHz crystal + 2x30 pF caps on PB6&7.

// low power BCD RTC using bare bones ATMega328p, no regulator, 3-5V
// update 5/2021
//
// This code creates an RTC formed by Timer2. Requires 32768 Hz xtal + 2x30 pF caps on OSC pins
// Low power operation borrowed heavily from https://www.gammon.com.au/power

// SET FUSES: 8 MHz internal RC clock
// S. James Remington 3/2013
//tested on 8MHz internal clock mini-duino http://www.crossroadsfencing.com/BobuinoRev17/

#include <avr/sleep.h>
#include <util/delay.h>

// Global variables for RTC
volatile unsigned char RTC_buf[] = {0, 0, 0, 0, 0, 0}; //initialize BCD digits coding for hh:mm:ss
volatile unsigned int dayno = 0; //days since startup

#define BUF_LEN 20
char buf[BUF_LEN];  //print message buffer

void setup() {
  Serial.begin(9600);
  delay(1000);
  Serial.println("Simple RTC");

  //PRR Power Reduction Register (set PRADC after ADCSRA=0)
  //Bit 7 - PRTWI: Power Reduction TWI
  //Bit 6 - PRTIM2: Power Reduction Timer/Counter2
  //Bit 5 - PRTIM0: Power Reduction Timer/Counter0
  //Bit 3 - PRTIM1: Power Reduction Timer/Counter1
  //Bit 2 - PRSPI: Power Reduction Serial Peripheral Interface
  //Bit 1 - PRUSART0: Power Reduction USART0
  //Bit 0 - PRADC: Power Reduction ADC

  ADCSRA = 0; //disable ADC
  // turn off  unneeded modules. 
  PRR |= (1 << PRTWI) | (1 << PRTIM1) | (1 << PRSPI) | (1 << PRADC); // | (1 << PRUSART0) ;

  // reprogram timer 2 for this application

  timer2_init();
}

void loop() {

  static char t = 0;  //must be static or global
  unsigned int h, m, s;

    // wake up on Timer2 overflow (1/sec)
    // output day since startup and time on serial monitor every ... (10 seconds now)

    if (t != RTC_buf[4]) { //have 10 seconds passed?

      t = RTC_buf[4];
      s = 10 * RTC_buf[4] + RTC_buf[5]; //format time
      m = 10 * RTC_buf[2] + RTC_buf[3];
      h = 10 * RTC_buf[0] + RTC_buf[1];

/* turn on ADC if needed
      PRR &= ~((1 << PRADC) | (1 << PRUSART0)); //turn on ADC and USART
      ADCSRA = (1 << ADEN); //set up ADC properly
      ADCSRA |= (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2); // ADC Prescaler=128
*/
      //    print day, time

      snprintf(buf, BUF_LEN, "%u,%02u:%02u:%02u", dayno, h, m, s);
      Serial.println(buf);
      Serial.flush(); //finish printing before going to sleep

      // back to sleep with modules off

/* adc back off, if on
      ADCSRA = 0; //ADC off
      PRR |= (1 << PRADC) | (1 << PRUSART0);  //turn off ADC and USART
*/
    } //end if (t)
    
// go back to sleep
    set_sleep_mode(SLEEP_MODE_PWR_SAVE);
    sleep_enable();
    cli(); //time critical steps follow
    MCUCR = (1 << BODS) | (1 << BODSE); // turn on brown-out enable select
    MCUCR = (1 << BODS);         //Brown out off. This must be done within 4 clock cycles of above
    sei();
    sleep_cpu();
}

//******************************************************************
//  Timer2 Interrupt Service
//  32 kKz / 256 = 1 Hz with Timer2 prescaler 128
//  provides global tick timer and BCD binary Real Time Clock
//  no check for illegal values of RTC_buffer upon startup!

ISR (TIMER2_OVF_vect) {

  // RTC function, keep time in BCD binary HH:MM:SS, and days since startup

  RTC_buf[5]++; // increment second

  if (RTC_buf[5] > 9)
  {
    RTC_buf[5] = 0; // increment ten seconds
    RTC_buf[4]++;
    if ( RTC_buf[4] > 5)
    {
      RTC_buf[4] = 0;
      RTC_buf[3]++; // increment minutes
      if (RTC_buf[3] > 9)
      {
        RTC_buf[3] = 0;
        RTC_buf[2]++; // increment ten minutes

        if (RTC_buf[2] > 5)
        {
          RTC_buf[2] = 0;
          RTC_buf[1]++; // increment hours
          char b = RTC_buf[0]; // tens of hours, handle rollover at 19 or 23
          if ( ((b < 2) && (RTC_buf[1] > 9)) || ((b == 2) && (RTC_buf[1] > 3)) )
          {
            RTC_buf[1] = 0;
            RTC_buf[0]++; // increment ten hours and day number, if midnight rollover
            if (RTC_buf[0] > 2) {
              RTC_buf[0] = 0;
              dayno++;  //count days since startup
            }
          }
        }
      }
    }
  }
}

//
// initialize Timer2 as asynchronous 32768 Hz timing source
//

void timer2_init(void) {

  TCCR2A = 0;
  TCCR2B = 0;  //stop Timer 2
  TIMSK2 = 0; // disable Timer 2 interrupts
  ASSR = (1 << AS2); // select asynchronous operation of Timer2
  TCNT2 = 0; // clear Timer 2 counter
  TCCR2B = (1 << CS22) | (1 << CS20); // select prescaler 128 => 1 sec between each overflow

  while (ASSR & ((1 << TCN2UB) | (1 << TCR2BUB))); // wait for TCN2UB and TCR2BUB to be cleared

  TIFR2 = (1 << TOV2); // clear interrupt-flag
  TIMSK2 = (1 << TOIE2); // enable Timer2 overflow interrupt
}

An RTC without non volatile time really isn't too useful for most things. But if you must do it, on the genuine UNO, you can program the secondary processor to produce a PPS signal. This way the only hardware mod is one added wire jumper between GPIO's. That support IC has a real quartz crystal not a resonator, so it keeps much better time.

You could also build one of your familiar 32kHz oscillator circuits, and just sample it with an interrupt enabled input pin.

ATMega328P has a special "power save" mode that shuts off almost everything except Timer2, which can use a watch crystal to clock itself with less power and more accuracy than the watchdog timer.

Thanks everyone for the replies as I shall now rephrase my objective. First I don't have the knowledge to undertake your recommendations at this time. I was thinking of using a pierce oscillator I previously designed which has several outputs (1 hz, 1/60hz, 1/3600hz). Instead of programming the Arduino to rely on this signal as its clock, is it possible to "reset or update" the time drift on the Arduino say once per hour using the pierce oscillator circuit? Perhaps this is what the scl and sda ports are good for but that's beyond my scope.

You are looking for a really simple way in software? Use the Time library but keep time in Unix format in a 32 bit variable that you increment every time your 1 hz output toggles. You can do that with a simple input pin interrupt, with the pin connected to your counter+oscillator. Then use the Time library functions to translate to "normal" human times.

Normally you would design that as a time source function that the Time library synchronizes to, but you said you "don't have the knowledge" for other recommendations. Certainly your "shot in the dark" about SCL and SDA completely missed. As do most guesses in this business...

Yes for now simplicity is key for my understanding. "Use the Time library but keep time in Unix format in a 32 bit variable that you increment every time your 1 hz output toggles." is what I was looking for. I would even settle for a push button that I manually press to "synch" to a reliable timepiece which can then obviously be my pierce oscillator. Since I would be doing this via software, there is always going to be some relatively large error due to the processor being busy with other tasks no? This is not really an issue because my clock will only display hours and minutes. Losing up to a minute is not a concern as long as I can reset the clock when the drift becomes arbitrarily too large.

No, I said use an interrupt pin to sense the 1 PPS.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.