Sleep mode slows down timer2

Hi guys,
I use a standalone Atmega328p with a16Mhz crystal. 16Mhz are necessary to do many calculations controlled by a time driven scheduler. In idle periods the Atmega shall sleep.

The scheduler operats with timer2 ticks to enable energy saving. The ticks will be generated and counted in an isr to get a period of 250ms. In loop function the ticks are compared to start tasks or run idle. At the end of loop the Atmega is going to sleep to save energy.
If the follwing code runs without sleep, timer2 is operating as expected with 4Hz and good accuracy below 1% - everything fine.
If I enablee.g. sleepmode_pwr_save (which can be awake by timer2) the frequency of the ticks drops to 2,6Hz.
What is causing this delay and how to solve (I tried with timer2 settings for apox 6Hz the output ticks are nearly 4Hz but with with poor accurcy of 5% - not a really good solution).

Below the testcode:

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

const unsigned int MEAS_TICKS = 4; // meas period 1s

unsigned int lastMeasTick;
int ledPin;
bool led;
unsigned int ticks;

// f = Sysclk / (scaleFactor * Prescaler * (256 - counterStart)) = 16 Mhz / (125 * 256 (256 - 131)) = 4 Hz _ 250 ms period
byte counterStart = 131;  // for 4 Hz ticks runs exactly without sleep
unsigned int scaleFactor = 125; 
//byte counterStart = 189;  // for aprox 6 Hz settings  generates aprox 4Hz ticks with sleep
//unsigned int scaleFactor = 124;


void timer2Setup(void)
{ 
	TCCR2A = 0x00; // OC2A and OC2B disconnected; Wave Form Generation Mode 0: Normal Mode
	TCCR2B = (1<<CS22) + (1<<CS21); // prescaler = 256 for 16 MHz
	TIMSK2 = (1<<TOIE2); // interrupt when TCNT2 is overflowed
	TCNT2 = counterStart;
}

void setup() 
{ 
	lastMeasTick = 0;
	ticks = 0;
	Serial.begin(9600);	
	timer2Setup();
	ledPin = 8;
	pinMode(ledPin, OUTPUT);
}

// ################### ISR service routine ####################################
ISR(TIMER2_OVF_vect)
{
	static int counter = 0;
  	TCNT2 = counterStart;
 	counter++;
  	if (counter==scaleFactor)
	{
		ticks++;
      	counter = 0;
	}
}

void loop() 
{	
	// ################## schedule measurment every delayInMillis ####################
	// all operations run in timer ticks of 250 ms 
	if ((ticks - lastMeasTick) >= MEAS_TICKS)
	{
		lastMeasTick = ticks; // store actual tick operating this branch
		
		PORTB ^= (1<<PB0); // toggle pin 8 LED
		Serial.println(ticks);
	}
	// for sleep operations
	/* Serial.flush();
	set_sleep_mode(SLEEP_MODE_PWR_SAVE); // choose power down mode
	//cli(); // deactivate interrupts
	sleep_enable(); // sets the SE (sleep enable) bit
	//sleep_bod_disable();
	//sei(); // 
	sleep_cpu(); // sleep now!! wakes up by timer2 ticks
	sleep_disable(); // deletes the SE bit
	//power_all_enable(); */
}

Here the time line outputs

4 Hz operation no sleep (counts by 4 every 1 second - fine):
12:09:02.396 -> 4
12:09:03.421 -> 8
12:09:04.408 -> 12
12:09:05.407 -> 16
12:09:06.421 -> 20
12:09:07.394 -> 24
12:09:08.402 -> 28

4Hz operation with sleep (counts by 4 every 1,5 second - poor)
10:15:32.557 -> 4
10:15:34.088 -> 8
10:15:35.605 -> 12
10:15:37.127 -> 16
10:15:38.604 -> 20
10:15:40.141 -> 24
10:15:41.647 -> 28

Thanks for explanations and ideas to correct behaviour

  1. Variables shared with interrupt routines MUST be declared volatile.
  2. Multibyte variables shared with interrupt routines must be protected from corruption, when accessed by the main program.

For example:

volatile unsigned int ticks = 0;
...
noInterrupts();
unsigned int ticks_copy = ticks;
interrupts();
Serial.println(ticks_copy);
...

Thanks for this general hint - you are right. But unfortunatly correcting the code like suggested hasno effect on the problem - still persists

Edit: I think your approach fails because the Timer2 clock source is enabled in power_save sleep mode, only when Timer2 is in asynchronous mode. Study the data sheet very carefully.

You may get some ideas from the following Timer2 RTC code, which I've been using for several years. It works correctly and keeps very accurate time. A 32 kHz crystal is required on the oscillator pins, and the 8 MHz internal RC oscillator runs the main loop code:

// low power BCD RTC using bare bones ATMega328p, no regulator, 3-5V
// update 5/2021
// ASCII RTC buffer, maintains C-string HH:MM:SS
//
// 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
//                                 0   1       3   4       6   7
volatile unsigned char RTC_buf[]={'0','0',':','0','0',':','0','0',0}; //hh:mm:ss, with zero terminator
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

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

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

      t = RTC_buf[6];

/* 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
      noInterrupts();
     unsigned int dayno_copy = dayno;
     interrupts();
      snprintf(buf, BUF_LEN, "%s/%u", RTC_buf, dayno_copy);
      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 ASCII Real Time Clock
//  no check for illegal values of RTC_buffer upon startup!

ISR (TIMER2_OVF_vect) {

// RTC function

    RTC_buf[7]++;    // increment second

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

          if (RTC_buf[3] > '5')
          {
          RTC_buf[3]='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++;}
              }
          }
        }
      }
    }
}

//
// 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
}
1 Like

Thanks jremington my inestigations studying the datasheet and another thread https://forum.arduino.cc/t/how-long-does-atmega-take-to-wake-up-from-sleep-mode/139408/13 intruduced me to think about wake up times. The result seems to be that in "SLEEP_MODE_PWR_SAVE" the core needs 16K cycles for restart. This time is 1ms at 16Mhz. The isr with the above settings will run 125 times to count a tick every 250ms. This means in sleep i add a delay of 125*1ms which is exactly the measured value. Programming timer2 to 6Hz will generate the real 4 Hz ticks.
If you use the timer2 with the small range of 8 bit to generate longer periods you must take into account the wake up times .
To solve my problem I try to calculate new settings for timer2 to generate a higher "compensated" frequency that has no fractional part. Unfortunatly 6.0 Hz seems not to be possible.

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