11 Getting Started with RTC

11.1 Introduction

Author: Victor Berzan, Microchip Technology Inc.

This technical brief describes how the Real-Time Counter (RTC) module works on tinyAVR® 0- and 1-series, megaAVR® 0-series and AVR® DA devices. It covers the following use cases:

  • RTC Overflow Interrupt:

    Initialize the RTC, enable the overflow interrupt, and toggle an LED on each overflow.

  • RTC Periodic Interrupt:

    Initialize the RTC PIT, enable the periodic interrupt, and toggle an LED on each periodic interrupt.

  • RTC PIT Wake from Sleep:

    Initialize the RTC PIT, enable the periodic interrupt, configure device sleep mode, put CPU in Sleep, the PIT interrupt will wake the CPU.

Note: For each use case described in this document, there are two code examples: One bare metal developed on ATmega4809, and one generated with MPLAB® Code Configurator (MCC) developed on AVR128DA48.

11.2 Overview

The RTC peripheral offers two timing functions: A Real-Time Counter (RTC) and a Periodic Interrupt Timer (PIT). The PIT functionality can be enabled independently of the RTC functionality.

The RTC counts (prescaled) clock cycles in a Counter register and compares the content of the Counter register to a Period register and a Compare register. The RTC can generate both interrupts and events on compare match or overflow. It will generate a compare interrupt and/or event at the first count after the counter equals the Compare register value, and an overflow interrupt and/or event at the first count after the counter value equals the Period register value. The overflow will also reset the counter value to zero.

Using the same clock source as the RTC function, the PIT can request an interrupt or trigger an output event on every nth clock period (‘n’ can be selected from {4, 8, 16,.. 32768} for interrupts, and from {64, 128, 256,... 8192} for events).

Figure 11-130. Block Diagram

The PIT and RTC functions are running off the same counter inside the prescaler. Writing the PRESCALER bit field in the RTC.CTRLA register configures the period of the clock signal that increments the CNT. The PERIOD bit field in RTC.PITCTRLA selects the bit from the 15-bit prescaler counter to be used as PIT period output.

11.3 RTC Overflow Interrupt

This code example shows how to use the RTC with overflow interrupt enabled to toggle an LED. The overflow period is 500 ms. The on-board LED will be toggled each time the overflow interrupt occurs.

To operate the RTC, the source clock for the RTC counter must be configured before enabling the RTC peripheral and the desired actions (interrupt requests, output events). In this example, the 32.768 kHz external oscillator is used as the source clock.

To configure the oscillator, first, it must be disabled by clearing the ENABLE bit in the CLKCTRL.XOSC32KCTRLA register:

Figure 11-131. CLKCTRL.XOSC32KCTRLA – Clear the ENABLE Bit
uint8_t temp;
temp = CLKCTRL.XOSC32KCTRLA;
temp &= ~CLKCTRL_ENABLE_bm;
ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);

The user must then wait for the corresponding status bit to become ‘0’:

Figure 11-132. CLKCTRL.MCLKSTATUS – Read XOSC32KS
while(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)
{
	;
}

Select the external oscillator by clearing the SEL bit in the CLKCTRL.XOSC32KCTRLA register:

Figure 11-133. CLKCTRL.XOSC32KCTRLA – Clear the SEL Bit
temp = CLKCTRL.XOSC32KCTRLA;
temp &= ~CLKCTRL_SEL_bm;
ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);

Then, enable the oscillator by setting the ENABLE bit in the CLKCTRL.XOSC32KCTRLA register:

temp = CLKCTRL.XOSC32KCTRLA;
temp |= CLKCTRL_ENABLE_bm;
ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);

Afterward, the user must wait for all registers to be synchronized:

Figure 11-134. RTC.STATUS
while (RTC.STATUS > 0)
{
	;
}

The RTC period is set in the RTC.PER register:

Figure 11-135. RTC.PER – Set Period

The 32.768 kHz External Crystal Oscillator clock is selected in the RTC.CLKSEL register:

Figure 11-136. RTC.CLKSEL – Clock Selection
RTC.CLKSEL = RTC_CLKSEL_TOSC32K_gc;

To enable the RTC to also run in Debug mode, the DBGRUN bit is set in the RTC.DBGCTRL register:

Figure 11-137. RTC.CLKSEL – Set the DBGRUN Bit
RTC.DBGCTRL |= RTC_DBGRUN_bm;

The RTC prescaler is set in the RTC.CTRLA register. Set the RUNSTDBY bit in RTC.CTRLA to enable the RTC to also run in Standby mode. Set the RTCEN bit in RTC.CTRLA to enable the RTC.

Figure 11-138. RTC.CTRLA – Set the Prescaler, RUNSTDBY Bit, RTCEN Bit
RTC.CTRLA = RTC_PRESCALER_DIV32_gc | RTC_RTCEN_bm | RTC_RUNSTDBY_bm;

Enable the overflow interrupt by setting the OVF bit in the RTC.INTCTRL register:

Figure 11-139. RTC.INTCTRL – Set the OVF Bit
RTC.INTCTRL |= RTC_OVF_bm;

For the interrupt to occur, the global interrupts must be enabled:

sei();

The Interrupt Service Routine (ISR) for the RTC overflow will toggle an LED in the example below:

ISR(RTC_CNT_vect)
{
    RTC.INTFLAGS = RTC_OVF_bm;
    LED0_toggle();
}
Note: Clear the OVF bit from the RTC.INTFLAGS register by writing a ‘1’ to it inside the ISR function.
Tip: The full code example is available in the Appendix section.

An MCC generated code example for AVR128DA48, with the same functionality as the one described in this section, can be found here:

11.4 RTC Periodic Interrupt

This code example shows how to use the PIT timing function of the RTC. The on-board LED will be toggled each second when the periodic interrupt occurs.

The source clock configuration for this particular example is the same as for the RTC overflow interrupt example. Enable the periodic interrupt by setting the PI bit in the RTC.PITINTCTRL register.

Figure 11-140. RTC.PITINTCTRL – Set the PI Bit
RTC.PITINTCTRL = RTC_PI_bm;

The PIT period is set in the RTC.PITCTRLA register. Enable the PIT by setting the PITEN bit in RTC.PITCTRLA.

Figure 11-141. RTC.PITCTRLA – Set the PITEN Bit
RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc | RTC_PITEN_bm;

For the interrupt to occur, the global interrupts must be enabled:

sei();

The Interrupt Service Routine (ISR) for the RTC PIT will toggle an LED in the example below:

ISR(RTC_PIT_vect)
{
    RTC.PITINTFLAGS = RTC_PI_bm;
    LED0_toggle();
}
Note: Clear the PI bit from the RTC.PITINTFLAGS register by writing a ‘1’ to it inside the ISR function.
Tip: The full code example is available in the Appendix section.

An MCC generated code example for AVR128DA48, with the same functionality as the one described in this section, can be found here:

11.5 RTC PIT Wake from Sleep

This code example shows how to use the PIT timing function of the RTC to wake up the CPU from sleep. The on-board LED will be toggled each second when the periodic interrupt occurs, meaning it will be toggled when the CPU wakes up from sleep.

The sleep mode is configured in the SLPCTRL.CTRLA register. The sleep feature is enabled by setting the SEN bit in SLPCTRL.CTRLA.

Figure 11-142. SLPCTRL.CTRLA – Set the Sleep Mode, SEN Bit
SLPCTRL.CTRLA |= SLPCTRL_SMODE_PDOWN_gc;
SLPCTRL.CTRLA |= SLPCTRL_SEN_bm;

The CPU can be put to sleep by calling the following function:

sleep_cpu();

The PIT interrupt will wake the CPU from sleep. For the interrupt to occur, the global interrupts must be enabled:

sei();

The Interrupt Service Routine (ISR) for the RTC PIT will toggle an LED in the example below:

ISR(RTC_PIT_vect)
{
    RTC.PITINTFLAGS = RTC_PI_bm;
    LED0_toggle();
}
Note: Clear the PI bit from the RTC.PITINTFLAGS register by writing a ‘1’ to it inside the ISR function.
Tip: The full code example is available in the Appendix section.

An MCC generated code example for AVR128DA48, with the same functionality as the one described in this section, can be found here:

11.6 Appendix

RTC Overflow Interrupt Code Example

/* RTC Period */
#define RTC_EXAMPLE_PERIOD            (511)

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

void RTC_init(void);
void LED0_init(void);
inline void LED0_toggle(void);

void RTC_init(void)
{
    uint8_t temp;
    
    /* Initialize 32.768kHz Oscillator: */
    /* Disable oscillator: */
    temp = CLKCTRL.XOSC32KCTRLA;
    temp &= ~CLKCTRL_ENABLE_bm;
    /* Writing to protected register */
    ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);
    
    while(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)
    {
        ; /* Wait until XOSC32KS becomes 0 */
    }
    
    /* SEL = 0 (Use External Crystal): */
    temp = CLKCTRL.XOSC32KCTRLA;
    temp &= ~CLKCTRL_SEL_bm;
    /* Writing to protected register */
    ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);
    
    /* Enable oscillator: */
    temp = CLKCTRL.XOSC32KCTRLA;
    temp |= CLKCTRL_ENABLE_bm;
    /* Writing to protected register */
    ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);
    
    /* Initialize RTC: */
    while (RTC.STATUS > 0)
    {
        ; /* Wait for all register to be synchronized */
    }

    /* Set period */
    RTC.PER = RTC_EXAMPLE_PERIOD;

    /* 32.768kHz External Crystal Oscillator (XOSC32K) */
    RTC.CLKSEL = RTC_CLKSEL_TOSC32K_gc;

    /* Run in debug: enabled */
    RTC.DBGCTRL |= RTC_DBGRUN_bm;
    
    RTC.CTRLA = RTC_PRESCALER_DIV32_gc  /* 32 */
    | RTC_RTCEN_bm            /* Enable: enabled */
    | RTC_RUNSTDBY_bm;        /* Run In Standby: enabled */
    
    /* Enable Overflow Interrupt */
    RTC.INTCTRL |= RTC_OVF_bm;
}

void LED0_init(void)
{
    /* Make High (OFF) */
    PORTB.OUT |= PIN5_bm;
    /* Make output */
    PORTB.DIR |= PIN5_bm;
}

inline void LED0_toggle(void)
{
    PORTB.OUTTGL |= PIN5_bm;
}

ISR(RTC_CNT_vect)
{
    /* Clear flag by writing '1': */
    RTC.INTFLAGS = RTC_OVF_bm;
    
    LED0_toggle();
}

int main(void)
{
    LED0_init();
    RTC_init();
    
    /* Enable Global Interrupts */
    sei();
    
    while (1) 
    {
    }
}

RTC Periodic Interrupt Code Example

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

void RTC_init(void);
void LED0_init(void);
inline void LED0_toggle(void);

void RTC_init(void)
{
    uint8_t temp;
    
    /* Initialize 32.768kHz Oscillator: */
    /* Disable oscillator: */
    temp = CLKCTRL.XOSC32KCTRLA;
    temp &= ~CLKCTRL_ENABLE_bm;
    /* Writing to protected register */
    ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);
    
    while(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)
    {
        ; /* Wait until XOSC32KS becomes 0 */
    }
    
    /* SEL = 0 (Use External Crystal): */
    temp = CLKCTRL.XOSC32KCTRLA;
    temp &= ~CLKCTRL_SEL_bm;
    /* Writing to protected register */
    ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);
    
    /* Enable oscillator: */
    temp = CLKCTRL.XOSC32KCTRLA;
    temp |= CLKCTRL_ENABLE_bm;
    /* Writing to protected register */
    ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);
    
    /* Initialize RTC: */
    while (RTC.STATUS > 0)
    {
        ; /* Wait for all register to be synchronized */
    }

    /* 32.768kHz External Crystal Oscillator (XOSC32K) */
    RTC.CLKSEL = RTC_CLKSEL_TOSC32K_gc;

    /* Run in debug: enabled */
    RTC.DBGCTRL = RTC_DBGRUN_bm;
    
    RTC.PITINTCTRL = RTC_PI_bm; /* Periodic Interrupt: enabled */
    
    RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc /* RTC Clock Cycles 32768 */
                 | RTC_PITEN_bm; /* Enable: enabled */
}

void LED0_init(void)
{
    /* Make High (OFF) */
    PORTB.OUT |= PIN5_bm;
    /* Make output */
    PORTB.DIR |= PIN5_bm;
}

inline  void LED0_toggle(void)
{
    PORTB.OUTTGL |= PIN5_bm;
}

ISR(RTC_PIT_vect)
{
    /* Clear flag by writing '1': */
    RTC.PITINTFLAGS = RTC_PI_bm;
    
    LED0_toggle();
}

int main(void)
{
    LED0_init();
    RTC_init();
    
    /* Enable Global Interrupts */
    sei();
    
    while (1) 
    {
    }
}

RTC PIT Wake from Sleep Code Example

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

void RTC_init(void);
void LED0_init(void);
inline void LED0_toggle(void);
void SLPCTRL_init(void);

void RTC_init(void)
{
    uint8_t temp;
    
    /* Initialize 32.768kHz Oscillator: */
    /* Disable oscillator: */
    temp = CLKCTRL.XOSC32KCTRLA;
    temp &= ~CLKCTRL_ENABLE_bm;
    /* Writing to protected register */
    ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);
    
    while(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)
    {
        ; /* Wait until XOSC32KS becomes 0 */
    }
    
    /* SEL = 0 (Use External Crystal): */
    temp = CLKCTRL.XOSC32KCTRLA;
    temp &= ~CLKCTRL_SEL_bm;
    /* Writing to protected register */
    ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);
    
    /* Enable oscillator: */
    temp = CLKCTRL.XOSC32KCTRLA;
    temp |= CLKCTRL_ENABLE_bm;
    /* Writing to protected register */
    ccp_write_io((void*)&CLKCTRL.XOSC32KCTRLA, temp);
    
    /* Initialize RTC: */
    while (RTC.STATUS > 0)
    {
        ; /* Wait for all register to be synchronized */
    }

    /* 32.768kHz External Crystal Oscillator (XOSC32K) */
    RTC.CLKSEL = RTC_CLKSEL_TOSC32K_gc;

    /* Run in debug: enabled */
    RTC.DBGCTRL = RTC_DBGRUN_bm;
    
    RTC.PITINTCTRL = RTC_PI_bm; /* Periodic Interrupt: enabled */
    
    RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc /* RTC Clock Cycles 32768 */
                 | RTC_PITEN_bm; /* Enable: enabled */
}

void LED0_init(void)
{
    /* Make High (OFF) */
    PORTB.OUT |= PIN5_bm;
    /* Make output */
    PORTB.DIR |= PIN5_bm;
}

inline void LED0_toggle(void)
{
    PORTB.OUTTGL |= PIN5_bm;
}

ISR(RTC_PIT_vect)
{
    /* Clear flag by writing '1': */
    RTC.PITINTFLAGS = RTC_PI_bm;
    
    LED0_toggle();
}

void SLPCTRL_init(void)
{
    SLPCTRL.CTRLA |= SLPCTRL_SMODE_PDOWN_gc;
    SLPCTRL.CTRLA |= SLPCTRL_SEN_bm;
}

int main(void)
{
    LED0_init();
    RTC_init();
    SLPCTRL_init();
    
    /* Enable Global Interrupts */
    sei();
    
    while (1) 
    {
        /* Put the CPU in sleep */
        sleep_cpu();
        
        /* The PIT interrupt will wake the CPU */
    }
}