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):
- Commit name "RTC alarm" - 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.