Skip to main content

4.2. RTC alarm


1. RTC Alarm Interruption

In this tutorial, we will experiment the Alarm feature of the RTC peripheral.

When enabled, the RTC can produce an Alarm interrupt when the clock/calendar reaches a programed date/time.

The interrupt signal is handled by the EXTI (External Interrupt) controller on line 17. Therefore, it basically works as any external interrupt regarding EXTI and NVIC setup.

Add a function into rtc.c that enable RTC (i.e. EXTI line 17) interrupts:

/*
 * BSP_RTC_EXTI_Init()
 * Enable RTC (EXTI17) rising edge Interrupt
 */
 
void BSP_RTC_EXTI_Init()
{
	// Enable EXTI line 17
	EXTI->IMR |= EXTI_IMR_IM17;

	// Enable Rising / Disable Falling trigger
	EXTI->RTSR |=  EXTI_RTSR_RT17;
	EXTI->FTSR &= ~EXTI_FTSR_FT17;
}

And the corresponding declaration in rtc.h:

// Public functions

void BSP_RTC_Clock_Config	(void);
void BSP_RTC_EXTI_Init		(void);
void BSP_RTC_SetTime		(time_t *ptime);
void BSP_RTC_GetTime		(time_t *ptime);

Then update the NVIC settings in bsp.c:

void BSP_NVIC_Init()
{
...
	// Set priority level 1 for RTC interrupt
	NVIC_SetPriority(RTC_IRQn, 1);

	// Enable RTC interrupts
	NVIC_EnableIRQ(RTC_IRQn);
}

Finally, we need to write some interrupt handler. Let us simply do the usual flag thing and set a new global variable rtc_irq:

/*
 * This function handles RTC interrupts
 */
 
extern uint8_t	rtc_irq;

void RTC_IRQHandler()
{
	// Test for RTC Alarm A
	if ((RTC->ISR & RTC_ISR_ALRAF) == RTC_ISR_ALRAF)
	{
		// Clear the interrupt pending bit
		RTC->ISR &= ~RTC_ISR_ALRAF;

		// Clear EXTI pending bit also
		EXTI->PR = EXTI_PR_PR17;

		// Set global flag
		rtc_irq = 1;
	}
}

 

2. RTC Alarm driver

Let us prepare two functions to easily deal with Alarm configurations. The first one to set Alarm time. Add it to rtc.c:

/*
 * BSP_RTC_SetAlarmA(time_t *ptime)
 */
 
void BSP_RTC_SetAlarmA(time_t *ptime)
{
	uint32_t	bcdtime;

	bcdtime = 	( (byte2bcd(ptime->hours))   <<16U) |
			( (byte2bcd(ptime->minutes)) <<8U)  |
			( (byte2bcd(ptime->seconds)) <<0U);

	// Enable Write access for RTC registers
	RTC->WPR = 0xCA;
	RTC->WPR = 0x53;

	// Disable Alarm A
	RTC->CR &= ~RTC_CR_ALRAE;

	// Set Alarm time and discard date matching
	RTC->ALRMAR = RTC_ALRMAR_MSK4 | bcdtime;

	// Enable Alarm A and associated interrupt
	RTC->CR |= RTC_CR_ALRAE | RTC_CR_ALRAIE;

	// Disable Write access for RTC registers
	RTC->WPR = 0xFE;
	RTC->WPR = 0x64;
}

 And a second one to read back Alarm time:

/*
 * BSP_RTC_GetAlarmA(time_t *ptime)
 */
 
void BSP_RTC_GetAlarmA(time_t *ptime)
{
  ptime->hours   = bcd2byte((RTC->ALRMAR & (RTC_ALRMAR_HT_Msk  | 
                             RTC_ALRMAR_HU_Msk )) >>RTC_ALRMAR_HU_Pos);

  ptime->minutes = bcd2byte((RTC->ALRMAR & (RTC_ALRMAR_MNT_Msk | 
                             RTC_ALRMAR_MNU_Msk)) >>RTC_ALRMAR_MNU_Pos);

  ptime->seconds = bcd2byte((RTC->ALRMAR & (RTC_ALRMAR_ST_Msk  | 
                             RTC_ALRMAR_SU_Msk )) >>RTC_ALRMAR_SU_Pos);
}

 

Make sure everything is correctly declared in rtc.h:

// Public functions
void BSP_RTC_Clock_Config	(void);
void BSP_RTC_EXTI_Init		(void);

void BSP_RTC_SetTime		(time_t *ptime);
void BSP_RTC_GetTime		(time_t *ptime);

void BSP_RTC_SetAlarmA		(time_t *ptime);
void BSP_RTC_GetAlarmA		(time_t *ptime);

 

3. Using the RTC with Alarm

3.1.     Application

Again, we want to avoid the RTC to be reconfigured each time a reset occurs. So we start to test whether the code startup is produced by a Power-On event or by the Reset Pin event.

Note that using an “else if” for the second test avoids executing the test (which we know will be true) in case a Power-On event has already been detected. Then:

  • Program the Alarm to occur at 12:00:20

  • Verify the RTC Alarm register state and display a message at startup.

 

Edit the main() function as follows:

...
#include "rtc.h"
...

// Global variables
...
uint8_t rtc_irq = 0;

// Main function
int main()
{
	time_t		now, alarm;
	uint32_t	count;

	// Configure System Clock
	SystemClock_Config();

	// Peripheral Inits
	BSP_LED_Init();
	BSP_Console_Init();

	// This delay is just for JP6 debounce (optional)
	BSP_DELAY_ms(100);

	my_printf("\r\n\n --- Program Starting Over ---\r\n");

        /* Test reset conditions */

	// If this is a Power ON Reset (POR)
	if ( (RCC->CSR & RCC_CSR_PORRSTF_Msk) == RCC_CSR_PORRSTF )
	{
		my_printf("This is a Power ON reset\r\n");

		// Setup RTC clock
		BSP_RTC_Clock_Config();

		// Setup RTC time
		now.hours   = 12;
		now.minutes = 00;
		now.seconds = 00;
		BSP_RTC_SetTime(&now);

                my_printf("RTC time has been set to %02d:%02d:%02d\r\n", 
                           now.hours, now.minutes, now.seconds);

		// Setup Alarm A
		alarm.hours   = 12;
		alarm.minutes = 00;
		alarm.seconds = 20;
		BSP_RTC_SetAlarmA(&alarm);

                my_printf("ALARM A has been set to %02d:%02d:%02d\r\n", 
                alarm.hours, alarm.minutes, alarm.seconds);
	}

	// If reset pin was held low (and no POR detected)
	else if ( (RCC->CSR & RCC_CSR_PINRSTF_Msk) == RCC_CSR_PINRSTF )
	{
		my_printf("Reset pin was low\r\n");
		my_printf("RTC continues...\r\n");
	}

	// Clear reset flags for next reset
	RCC->CSR |= RCC_CSR_RMVF;


	/* Use Backup Registers to hold reset count */

	// (re)enable backup domain register access (after a reset)
	RCC->APB1ENR |= RCC_APB1ENR_PWREN;
	PWR->CR |= PWR_CR_DBP;

	// Read and update reset count
	count = RTC->BKP0R;
	my_printf("Reset #%d\r\n", count);
	count++;
	RTC->BKP0R = count;

	
	/* Test Alarm status */

	// If the Alarm interruption flag is set
	if ((RTC->ISR & RTC_ISR_ALRAF) == RTC_ISR_ALRAF)
	{
		my_printf("An Alarm occurred during reset\r\n");
	}

	// If the Alarm is armed
	if ((RTC->CR & RTC_CR_ALRAE) == RTC_CR_ALRAE)
	{
		my_printf("Alarm A armed");

		// Print Alarm time
		BSP_RTC_GetAlarmA(&alarm);
		my_printf(" -> %02d:%02d:%02d\r\n", alarm.hours, 
                            alarm.minutes, alarm.seconds);

		// (re)enable Alarm Interrupt (after a reset)
		BSP_RTC_EXTI_Init();
	}

	// Otherwise...
	else
	{
		my_printf("Alarm A disarmed\r\n");
	}


	// Enable Interrupts
	BSP_NVIC_Init();


	// Main loop
	while(1)
	{
		// Test for Alarm A interruption
		if (rtc_irq == 1)
		{
			my_printf("\r\n *** ALARM *** \r\n");
			rtc_irq = 0;
		}

		// Dispay RTC current time
		BSP_RTC_GetTime(&now);
		my_printf("RTC Time is %02d:%02d:%02d\r", now.hours, 
                           now.minutes, now.seconds);

		// Delay before looping
		BSP_LED_Toggle();
		BSP_DELAY_ms(100);
	}
}

 

3.2. Testing

Build the project, flash the target, and open a terminal window (Putty).

Generate a Power-On event using JP6 in order to program the Alarm time.

Then wait 20s… You should see the console message at 12:00:20.

You can play with the Reset button before, or after the alarm event. That should work without causing RTC reset.

You can even keep the Reset button down for a long time, around 12:00:20 ! Obviously, the alarm will not be generated in this case. Yet, the application detects (after the Reset button is released) that the RTC has triggered an Alarm during the reset state and you can process this information to something useful.

Below are different scenarios you can test:

  • Let things happen after a Power ON:

 

  • Try the Reset button after a Power ON:

 

  • Holding down Reset while alarm occurred (between 12:00:18 and 12:00:22 for instance):

 

gitlab- commit Commit name "RTC alarm"
- push Push onto Gitlab

 

4. Summary

In this tutorial, you have learned how to setup and use the Alarm feature of the RTC peripheral. This is an important topic as you may need it to wake up from the deepest low-power modes.