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