Read WDT timer value after external interrupt?

I am counting pulses using an external interrupt, but I put the processor to sleep in between pulses using wdt_enable, using the 8-second interval.

When the interrupt occurs and the processor wakes up to service the interrupt, is it possible to find out how long into the 8-second period I am? That is, can I read the wdt timer to see how long it has been running (or how long it has to go)?

The WDT counter register can be cleared, but not read.

If you want to keep accurate track of time while the processor is asleep, there are two possibilities: an external RTC, or an internal RTC.

You can create an internal RTC by using a 32768 Hz watch crystal on the oscillator pins, which will run Timer2 in sleep mode. The processor has to use the 8 MHz internal RC oscillator in that case (when awake). The advantage of this is that Timer2 can also wake up the processor at, say, 1 second intervals, for time and date keeping and other activities.

That makes sense. Thanks for explaining.

If I put a crystal as you suggest, how would that affect power consumption and battery life in sleep mode on a ATMega328P?

Can you point me to some code on how to set up and read timer 2?

Here is example code for a simple internal RTC that works as I described. It also uses the 32kHz crystal to calibrate the internal RC oscillator for better accuracy for the serial port baud rate.

Every minute it outputs the day, time of day and battery voltage on the serial monitor and goes back to sleep.

I use this as the basis for remote 3.3V sensors (with 3-5V level shifters for the I/O), and the Arduino CPU running on 3xAA batteries.

The battery life can be years, depending on how much power the sensors require and how much time the processor spends sleeping.

I don't have a processor to test it on at the moment -- they are all in service. Let me know if you have difficulties and I'll fix it.

See code posted in reply #6 below

Very cool. Thank you so much. Just down to trying this.

Turns out my board is a Mega with a 16MHz external crystal on it. So the timer runs fast by 16MHz/32768KHz, or about 488x.

Timer2 is 8 bit, as I understand it. Can I divide down much further to a proper 1-second tick? And I can't use other timers since they are not running on the external crystal during sleep. Is that right?

My approach with the ATMega328 is to run the processor off the internal calibrated RC oscillator (8 MHz) and have a 32768 Hz watch crystal on the external oscillator pins as the clock source for Timer2. 32768/128/256 = 1 interrupt/second.

You can do exactly the same thing with the ATMega2560 but the details will probably be somewhat different. See the section on Timer2 with asynchronous clock input.

Or, as you say, use a 16 bit timer with an odd divisor to get a 1 second interrupt. The Mega has FOUR 16 bit timers.

I made a working Arduino version of the simple, self contained RTC and tested it on a bare bones ATMega328, running on the 8 MHz internal RC oscillator and a 32768 Hz watch crystal connected to the xtal oscillator pins.

Works great!

This version just prints the day, time and battery voltage on the serial monitor every 10 seconds, and sleeps otherwise.

// low power BCD RTC using bare bones ATMega328p, no regulator, 3-5V

// This code creates an RTC formed by Timer2 and a 32768 xtal+2x30 pF caps on OSC pins
// The internal RC oscillator is calibrated by comparison with the 32768 standard,
// Low power operation borrowed heavily from https://www.gammon.com.au/power

// SET FUSES: 8 MHz internal RC clock, LP xtal (32768 Hz) on OSC pins
// S. James Remington 3/2013

//reminder: 8 MHz internal RC
//#define F_CPU 8000000UL

#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 MAX_MESSAGE_LEN 20

char buf[MAX_MESSAGE_LEN];  //print message buffer

void blink() {  //blink LED on PORTD, PIN3
  PORTD |= (1 << 3);
  for (int i = 1; i < 20; i++) _delay_ms(10);
  PORTD &= ~(1 << 3);
  for (int i = 1; i < 20; i++) _delay_ms(10);
}
void setup() {

  DDRD |= (1 << 3); //output
  Serial.begin(9600);
  Serial.println("Simple RTC");
  _delay_ms(15); //finish printing

  //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

  OSCCAL_calibrate();  //calibrate the RC osc using the 32 kHz crystal
  Serial.print("OSCCAL: ");
  Serial.println(OSCCAL);
  _delay_ms(15); //finish printing before sleep
  blink();

  // turn off  unneeded modules.  With Timer0 off, **can't use delay()**!
  PRR |= (1 << PRTWI) | (1 << PRTIM0) | (1 << PRTIM1) | (1 << PRSPI) | (1 << PRADC) |(1 << PRUSART0) ; //need Timer2

  // reprogram timer 2 for this application

  timer2_init();
  sei();
  PORTD &= ~(1 << 3); //led off
}

void loop() {

  char t = 0;
  unsigned int batt, h, m, s;

  while (1) {

    // wake up on Timer2 overflow (1/sec)
    // output day, time and cpu voltage on serial monitor every minute

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

      blink();
      
      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];

      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

      _delay_ms(1); //let ADC and USART stabilize

      batt = readVcc();  //read battery voltage

//    print day, time and battery voltage

      sprintf(buf, "%u,%0u:%0u:%0u,%u", dayno, h, m, s, batt); //max ~16 characters
      Serial.println(buf);
      _delay_ms(15); //finish printing before going to sleep

// back to sleep with modules off

      ADCSRA = 0; //ADC off
      PRR |= (1 << PRADC) | (1 << PRUSART0);  //turn off ADC and USART

    } //end if (t)

    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();

  } //end while(1)
}

//******************************************************************
//  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

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

  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
  TCCR2A = 0; 		//normal count up mode, no port output
  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
}

// Read 1.1V reference against AVcc
// EVERY PROCESSOR MUST BE CALIBRATED INDIVIDUALLY!

unsigned int readVcc(void) {

  unsigned int result;

  // set the reference to Vcc and the measurement to the internal 1.1V reference

  ADMUX = (1 << REFS0) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1);
  _delay_ms(2); // Wait for Vref to settle

  ADCSRA |= (1 << ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // wait until done
  result = ADC;

  // two is better than one

  ADCSRA |= (1 << ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // wait until done
  result = ADC;

  // calibrated for Miniduino board
  result = 1195700UL / (unsigned long)result; //1126400 = 1.1*1024*1000
  return result; // Vcc in millivolts
}

//
// Calibrate the internal OSCCAL byte, using the external 32768 Hz crystal as reference.
//

void OSCCAL_calibrate(void)  //This version specific to ATmegaXX8 (tested with ATmega328)

{
  unsigned char calibrate = 0; //FALSE;
  unsigned int temp;

  TIMSK1 = 0; //disable Timer1,2 interrupts
  TIMSK2 = 0;

  ASSR = (1 << AS2);      //select asynchronous operation of timer2 (32,768kHz)
  OCR2A = 200;            // set timer2 compare value
  TCCR1A = 0;
  TCCR1B = (1 << CS11);   // start timer1 with prescaler 8
  TCCR2A = 0;
  TCCR2B = (1 << CS20);   // start timer2 with no prescaling (ATmega169 use TCCR2A!)

  while (ASSR & ((1 << TCN2UB) | (1 << TCR2BUB))); //wait for TCN2UB and TCR2BUB to be cleared
  for (temp = 0; temp < 300; temp++) _delay_ms(10); //3 seconds to allow xtal osc to stabilize

  while (!calibrate)
  {
    cli(); // disable global interrupt

    TIFR1 = 0xFF;   // clear TIFR1 flags
    TIFR2 = 0xFF;   // clear TIFR2 flags

    TCNT1 = 0;      // clear timer1 counter
    TCNT2 = 0;      // clear timer2 counter

    while ( !(TIFR2 & (1 << OCF2A)) ); // wait for timer2 compareflag

    TCCR1B = 0; // stop timer1

    sei();			// enable global interrupts

    if ( (TIFR1 & (1 << TOV1)) ) temp = 0xFFFF; //overflow, load max
    else   temp = TCNT1;

    if (temp > 6140)  //expect about (1e6/32768)*201 = 6134 ticks
    {
      OSCCAL--;   //RC oscillator runs too fast, decrease OSCCAL
    }
    else if (temp < 6130)
    {
      OSCCAL++;   //RC oscillator runs too slow, increase OSCCAL
    }
    else calibrate = 1; //done

    TCCR1B = (1 << CS11); // (re)start timer1

  } //end while(!calibrate)
} //return

So I tried this using Timer1 instead of Timer2, since I have a 16MHz crystal and need more division of the clock to get a 1-sec interrupt. The problem is that SLEEP_MODE_PWR_SAVE for the sleep mode turns off Timer1. To keep it on, you need SLEEP_MODE_IDLE, which costs a LOT more power (3.5 mA instead of 0.5).

So it looks like I need to get a 32768 crystal.

Thanks so much for your help on this!