Timer2 Prescaler for use as asynchronous RTC ( real time clock )

Go To Last Post
6 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey all,

 

I'm trying to use the Timer2 asynchronous external clock feature of the ATmega328PB in order to configure an RTC.

 

I have an external 32.768 kHz crystal attached to the XTAL pins.  I am running from the internal 8 MHz processor.

 

I would post my fuse bytes but avrdude doesn't play nicely with the ATmega328PB, and as a result I can't figure out how to read the fuse bytes from the command line.  If anyone has the command to do so hidden up their sleeve, I'm all eyes.

 

 

Anyways, on to my question:

 

I have everything working as expected, except that Timer2 seems to be overflowing at half the frequency that I am expecting.

 

I set Timer2 to be asynchronously clocked from the external crystal, and have set the prescaler for Timer2 to 128, which should knock the 32.768 kHz frequency down to 256 Hz, right?  That means that (since Timer2 is 8-bit) that Timer2 should overflow once per second, right?

 

The observed behaviour I'm getting is an overflow on Timer2 every 2 seconds.

 

If I change the prescaler to 64, I get an overflow once per second.

 

What the frig is going on here?

 

DAT CODE:

 

/*
 * RTC_clock.cpp
 *
 * Created: 7/27/2018 11:47:09 AM
 * Author : Boompy
 */ 

#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>

//The following macros are used to switch to asynchronous external RTC clock using
//the safe method described on page 230 of the ATmega328PB datasheet

//STEP 1 --> Disable the TC2 interrupts by clearing OCIE2x and TOIE2.
//Macro to disable timer 2 interrupts
#define DISABLE_TIMER2_INTERRUPTS    TIMSK2 &= ~( (1<<OCIE2A) | (1<<OCIE2B) | (1<<TOIE2) )

//STEP 2 --> Select clock source by setting AS2 as appropriate.
//Macro to configure timer 2 to clock from external RTC oscillator
#define SET_TIMER2_ASYNC_EXTERNAL_CLK ASSR |= (1<<AS2)

//STEP 3 --> Write new values to TCNT2, OCR2x, and TCCR2x.
//Macro to set the clock prescaler to 128 (makes the timer/count tick at frequency of 256 Hz... overflow once per second!)
#define RESET_TIMER2_PRESCALER      GTCCR |= (1<<PSRASY)
#define TIMER2_PRESCALER_RESETTING  (GTCCR & (1<<PSRASY))
#define SET_TIMER2_PRESCALER_128    TCCR2B = 0b00000101
#define SET_TIMER2_PRESCALER_64     TCCR2B = 0b00000100


//STEP 4 --> To switch to asynchronous operation: Wait for TCN2xUB, OCR2xUB, and TCR2xUB.
//Macro that returns true if the TCNT2 register is in the midst of being written to (means we can't acces it yet)
#define TIMER2_COUNTER_UPDATE_BUSY    (ASSR & (1<<TCN2UB))
//Macro that returns true if either of the OCR2x registers are in the midst of being written to
#define TIMER2_OUTPUT_COMPARE_UPDATE_BUSY  ( (ASSR & (1<<OCR2AUB)) | (ASSR & (1<<OCR2BUB)) )
//Macro that returns true if either of the TCCR2X (timer 2 control register X) is in the midst of being written to
#define TIMER2_CONTROL_UPDATE_BUSY    ((ASSR & (1<<TCR2AUB)) | (ASSR & (1<<TCR2BUB)) )
//Macro that returns true if any of the temporary registers are currently in the process of being latched (i.e. not ready yet)
#define TIMER2_REGISTERS_LATCHING  (TIMER2_COUNTER_UPDATE_BUSY | TIMER2_OUTPUT_COMPARE_UPDATE_BUSY | TIMER2_COUNTER_UPDATE_BUSY)

//STEP 5 --> Clear the TC2 interrupt flags.
#define TIMER2_CLEAR_OUTPUT_COMPARE_A_INTERRUPT_FLAG  TIFR2 &= ~(1<<OCF2A)
#define TIMER2_CLEAR_OUTPUT_COMPARE_B_INTERRUPT_FLAG  TIFR2 &= ~(1<<OCF2B)
#define TIMER2_CLEAR_OVERFLOW_INTERRUPT_FLAG      TIFR2 &= ~(1<<TOV2)
#define TIMER2_CLEAR_ALL_INTERRUPT_FLAGS        TIFR2 &= (0b11111000)

//STEP 6 --> Enable interrupts, if needed.
#define TIMER2_ENABLE_TIMER_OVERFLOW_INTERRUPT    TIMSK2 |= (1<<TOIE2)


volatile uint32_t unixTime = 0;
volatile bool unhandled_timer_2_overflow = false;
volatile uint8_t led_state = LOW;

//ISR for timer 2 overflow
ISR(TIMER2_OVF_vect){
  unixTime++;
  unhandled_timer_2_overflow = true;
  led_state = !led_state;
}



void setupRTC(){
  //STEP 1 --> Disable the TC2 interrupts by clearing OCIE2x and TOIE2.
  DISABLE_TIMER2_INTERRUPTS;
  
  //STEP 2 --> Select clock source by setting AS2 as appropriate.
  SET_TIMER2_ASYNC_EXTERNAL_CLK;
  
  //STEP 3 --> Write new values to TCNT2, OCR2x, and TCCR2x.
  RESET_TIMER2_PRESCALER; //Reset the prescaler
  //Wait for prescaler to be reset
  while(TIMER2_PRESCALER_RESETTING){
  }
  SET_TIMER2_PRESCALER_128;   //For some reason, this results in an overflow rate of 0.5 Hz instead of 1.0 Hz
//  SET_TIMER2_PRESCALER_64;    //For some reason, this results in an overflow rate of 1.0 Hz instead of 2.0 Hz
  
  //STEP 4 --> To switch to asynchronous operation: Wait for TCN2xUB, OCR2xUB, and TCR2xUB.
  while(TIMER2_REGISTERS_LATCHING){
  }
  
  //STEP 5 --> Clear the TC2 interrupt flags.
  TIMER2_CLEAR_ALL_INTERRUPT_FLAGS;
  
  //STEP 6 --> Enable interrupts, if needed.
  TIMER2_ENABLE_TIMER_OVERFLOW_INTERRUPT;
}

const byte ledPin1 = 5;
const byte ledPin2 = 9;
const byte ledPin3 = 10;
const byte ledPin4 = 2;

void setup(){

  delay(1000);  //Wait 2 seconds for the TC2 to stabilize

  setupRTC();

  //Setup UART for serial debugging
  Serial.begin(9600);
  
  //Setup LED output pins
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
  pinMode(ledPin4, OUTPUT);
 
  set_sleep_mode(SLEEP_MODE_PWR_SAVE);
  sei();  //Enable global interrupts
}

void loop(){
    sleep_mode();
    //AWAKE--------------
    
    //Change LEDs
    digitalWrite(ledPin1, led_state); //Turn the LED on or off, depending on setting
    digitalWrite(ledPin2, led_state); //Turn the LED on or off, depending on setting
    digitalWrite(ledPin3, led_state); //Turn the LED on or off, depending on setting
    digitalWrite(ledPin4, led_state); //Turn the LED on or off, depending on setting
    //Check if we woke up because of an RTC tick
    if(unhandled_timer_2_overflow){
      unhandled_timer_2_overflow = false;
      //To avoid re-entering sleep mode during this TOSC cycle, write something inconsequential to OCR2
      //and keep checking whether this value has been latched to OCR2 yet.  Once it has been latched,
      //it is safe to re-enter sleep mode
      OCR2A = 123;
      //May as well do some useful stuff while we are waiting for the latch
      Serial.print("Unix time = ");
      Serial.println(unixTime, DEC);
      Serial.flush();
      //Wait for TOSC cycle to complete (OCR2A gets latched)
      while(TIMER2_REGISTERS_LATCHING){ 
      }
    }  
}

    


 

This topic has a solution.

I love the smell of burning silicon in the morning

Last Edited: Fri. Jul 27, 2018 - 07:10 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Arduino?  don't they have a default setup for each timer?  And all of your "setup" makes use of the |= you must have purchased when on sale in full-reel quantities.

 

Do yourself and all of us a favor...have a single assignment line for each control register.

 

And/or read back and report the values of the timer control registers.

 

 

jaza_tom wrote:
#define TIMER2_CLEAR_ALL_INTERRUPT_FLAGS TIFR2 &= (0b11111000)

That is quite interesting, as the datasheet says

Alternatively, OCF2B is cleared by writing a logic one to the flag.
 

...for each flag bit.  I don't think that even your RMW will clear them.

 

[edit] A scan of the page shows no references in your code to TCCR2A.  Thus, I guess you are assuming Mode 0.  To what mode does Arduino set the timer?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Fri. Jul 27, 2018 - 06:41 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the clues @theusch

 

I have cleaned up my code to make it a bit more readable (got rid of unused #defines)  and have also added code to output the values of the registers I'm changing before/after changing them.

 

Here is the serial output with the revised code:

 

TIMSK2 == 0
ASSR == 0
TCCR2A == 1
TCCR2B == 100
TIFR2 == 111
------------------
TIMSK2 == 1
ASSR == 100000
TCCR2A == 0
TCCR2B == 101
TIFR2 == 0

Unix time = 1

 

By adding the line

TCCR2A = 0;

to the setupRTC() function, I was able to get the 128 prescaler to result in an overflow frequency of 1 Hz, as intended.

 

Before that line of code was added, Timer2 was setup to run in Phase Correct PWM mode.

 

From the datasheet:

 

In phase correct PWM mode the counter is incremented until the counter value matches TOP. When the
counter reaches TOP, it changes the count direction.

The Timer/Counter Overflow flag (TOV2) is set each time the counter reaches BOTTOM. The interrupt
flag can be used to generate an interrupt each time the counter reaches the BOTTOM value.

So, starting to sound reminiscent of the symptom I was seeing (half the expected overflow frequency).

 

YAY FOR THIS FORUM!!!

I love the smell of burning silicon in the morning

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, apparently I can't edit my original post, so here is the cleaned up code:

 

/*
 * RTC_clock.cpp
 *
 * Created: 7/27/2018 11:47:09 AM
 * Author : Boompy
 */

#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>

//The following macros are used to switch to asynchronous external RTC clock using
//the safe method described on page 230 of the ATmega328PB datasheet

//STEP 1 --> Disable the TC2 interrupts by clearing OCIE2x and TOIE2.
//Macro to disable timer 2 interrupts
#define DISABLE_TIMER2_INTERRUPTS    TIMSK2 &= ~( (1<<OCIE2A) | (1<<OCIE2B) | (1<<TOIE2) )

//STEP 2 --> Select clock source by setting AS2 as appropriate.
//Macro to configure timer 2 to clock from external RTC oscillator
#define SET_TIMER2_ASYNC_EXTERNAL_CLK ASSR |= (1<<AS2)

//STEP 3 --> Write new values to TCNT2, OCR2x, and TCCR2x.
//Macro to set the clock prescaler to 128 (makes the timer/count tick at frequency of 256 Hz... overflow once per second!)
#define SET_TIMER2_PRESCALER_128    TCCR2B = 0b00000101
#define SET_TIMER2_PRESCALER_64     TCCR2B = 0b00000100


//STEP 4 --> To switch to asynchronous operation: Wait for TCN2xUB, OCR2xUB, and TCR2xUB.
//Macro that returns true if the TCNT2 register is in the midst of being written to (means we can't acces it yet)
#define TIMER2_COUNTER_UPDATE_BUSY    (ASSR & (1<<TCN2UB))
//Macro that returns true if either of the OCR2x registers are in the midst of being written to
#define TIMER2_OUTPUT_COMPARE_UPDATE_BUSY  ( (ASSR & (1<<OCR2AUB)) | (ASSR & (1<<OCR2BUB)) )
//Macro that returns true if either of the TCCR2X (timer 2 control register X) is in the midst of being written to
#define TIMER2_CONTROL_UPDATE_BUSY    ((ASSR & (1<<TCR2AUB)) | (ASSR & (1<<TCR2BUB)) )
//Macro that returns true if any of the temporary registers are currently in the process of being latched (i.e. not ready yet)
#define TIMER2_REGISTERS_LATCHING  (TIMER2_COUNTER_UPDATE_BUSY | TIMER2_OUTPUT_COMPARE_UPDATE_BUSY | TIMER2_COUNTER_UPDATE_BUSY)

//STEP 5 --> Clear the TC2 interrupt flags.
#define TIMER2_CLEAR_ALL_INTERRUPT_FLAGS        TIFR2 |= (0b00000111)

//STEP 6 --> Enable interrupts, if needed.
#define TIMER2_ENABLE_TIMER_OVERFLOW_INTERRUPT    TIMSK2 |= (1<<TOIE2)


volatile uint32_t unixTime = 0;
volatile bool unhandled_timer_2_overflow = false;
volatile uint8_t led_state = LOW;

//ISR for timer 2 overflow
ISR(TIMER2_OVF_vect){
  unixTime++;
  unhandled_timer_2_overflow = true;
  led_state = !led_state;
}



void setupRTC(){
  //STEP 1 --> Disable the TC2 interrupts by clearing OCIE2x and TOIE2.
  DISABLE_TIMER2_INTERRUPTS;

  //STEP 2 --> Select clock source by setting AS2 as appropriate.
  SET_TIMER2_ASYNC_EXTERNAL_CLK;

  //STEP 3 --> Write new values to TCNT2, OCR2x, and TCCR2x.
  TCCR2A = 0; //Clear any bits that are in TCCR2A
  SET_TIMER2_PRESCALER_128;   //For some reason, this results in an overflow rate of 0.5 Hz instead of 1.0 Hz
//  SET_TIMER2_PRESCALER_64;    //For some reason, this results in an overflow rate of 1.0 Hz instead of 2.0 Hz

  //STEP 4 --> To switch to asynchronous operation: Wait for TCN2xUB, OCR2xUB, and TCR2xUB.
  while(TIMER2_REGISTERS_LATCHING){
  }

  //STEP 5 --> Clear the TC2 interrupt flags.
  TIMER2_CLEAR_ALL_INTERRUPT_FLAGS;

  //STEP 6 --> Enable interrupts, if needed.
  TIMER2_ENABLE_TIMER_OVERFLOW_INTERRUPT;
}

const byte ledPin1 = 5;
const byte ledPin2 = 9;
const byte ledPin3 = 10;
const byte ledPin4 = 2;

void setup(){

  delay(1000);  //Wait 2 seconds for the RTC crystal to stabilize


  //Setup UART for serial debugging
  Serial.begin(9600);
  delay(1000); //Wait for serial monitor to behave

  Serial.flush();
  Serial.print("TIMSK2 == ");
  Serial.println(TIMSK2, BIN);
  Serial.flush();

  Serial.print("ASSR == ");
  Serial.println(ASSR, BIN);
  Serial.flush();

  Serial.print("TCCR2A == ");
  Serial.println(TCCR2A, BIN);
  Serial.flush();

  Serial.print("TCCR2B == ");
  Serial.println(TCCR2B, BIN);
  Serial.flush();

  Serial.print("TIFR2 == ");
  Serial.println(TIFR2, BIN);
  Serial.flush();


  setupRTC();


  Serial.println("------------------");
  Serial.flush();
  Serial.print("TIMSK2 == ");
  Serial.println(TIMSK2, BIN);
  Serial.flush();

  Serial.print("ASSR == ");
  Serial.println(ASSR, BIN);
  Serial.flush();

  Serial.print("TCCR2A == ");
  Serial.println(TCCR2A, BIN);
  Serial.flush();

  Serial.print("TCCR2B == ");
  Serial.println(TCCR2B, BIN);
  Serial.flush();

  Serial.print("TIFR2 == ");
  Serial.println(TIFR2, BIN);
  Serial.flush();

  //Setup LED output pins
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
  pinMode(ledPin4, OUTPUT);

  set_sleep_mode(SLEEP_MODE_PWR_SAVE);
  sei();  //Enable global interrupts

}

void loop(){
    sleep_mode();
    //AWAKE--------------

    //Change LEDs
    digitalWrite(ledPin1, led_state); //Turn the LED on or off, depending on setting
    digitalWrite(ledPin2, led_state); //Turn the LED on or off, depending on setting
    digitalWrite(ledPin3, led_state); //Turn the LED on or off, depending on setting
    digitalWrite(ledPin4, led_state); //Turn the LED on or off, depending on setting
    //Check if we woke up because of an RTC tick
    if(unhandled_timer_2_overflow){
      unhandled_timer_2_overflow = false;
      //To avoid re-entering sleep mode during this TOSC cycle, write something inconsequential to OCR2
      //and keep checking whether this value has been latched to OCR2 yet.  Once it has been latched,
      //it is safe to re-enter sleep mode
      OCR2A = 123;
      //May as well do some useful stuff while we are waiting for the latch
      Serial.print("Unix time = ");
      Serial.println(unixTime, DEC);
      Serial.flush();
      //Wait for TOSC cycle to complete (OCR2A gets latched)
      while(TIMER2_REGISTERS_LATCHING){
      }
    }
}

 

I love the smell of burning silicon in the morning

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jaza_tom wrote:
//STEP 5 --> Clear the TC2 interrupt flags. #define TIMER2_CLEAR_ALL_INTERRUPT_FLAGS TIFR2 |= (0b00000111)

Close.  You want a simple assignment with = .  In this case it won't hurt, but as you are introducing RMW effects it might bite you in teh future.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jaza_tom wrote:
apparently I can't edit my original post, so here is the cleaned up code:

 

don't modify prior posts, it breaks the continuity of the thread!

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...