5.1. RTC introduction


1. Introduction

Real Time Clock (RTC) is a very special peripheral. It is basically a timer with dedicated registers to store calendar and time data such as year, month, day, hour, minutes, and seconds. You can therefore use this peripheral to keep track of time, just like you do with a standard watch.

In addition, the RTC plays a major role in power-saving strategies as it can be the only domain (i.e. power, clock, registers) to stay awake while every other peripheral has been turned into stand-by or sleep mode. Note that RTC own power consumption is very low. You can even turn the STM32 device off VDD and still keep the RTC running as long as you power supply the VBAT pin  (using a button battery for instance).

In this tutorial, we will only address a very simple use of the RTC in a time display application.

For now, add two source files rtc.c and rtc.h under /bsp/src and /bsp/inc in your project structure:

image012.png
 
 

2. RTC Clock setup

RTC clock source is completely independent from the CPU clock scheme. It has its own oscillator, either internal or external.

On the Nucleo boards, quartz crystal X2 is mounted to be used as an external, low-speed (LSE) clock source for RTC.

 image013.png

image014.png

Note that X2 crystal provides a 32.768kHz frequency reference, which corresponds to a period of exactly:


 

Let us implement the RTC clock setting function. First is the prototype declaration in rtc.h:

/*
 * rtc.h
 *
 *  Created on: 20 août 2017
 *      Author: Laurent
 */

#ifndef BSP_INC_RTC_H_
#define BSP_INC_RTC_H_

#include "stm32f0xx.h"

// Public functions

void BSP_RTC_Clock_Config	(void);

#endif /* BSP_INC_RTC_H_ */

And then, the function implementation in rtc.c:

/*
 * rtc.c
 *
 *  Created on: 20 août 2017
 *      Author: Laurent
 */

#include "rtc.h"

/*
 * RTC_Config()
 * Setup RTC clock and start RTC
 */

void BSP_RTC_Clock_Config()
{
	// Enable the PWR clock
	RCC->APB1ENR |= RCC_APB1ENR_PWREN;

	// Enable access to RTC and Backup registers
	PWR->CR |= PWR_CR_DBP;

	// Resets Backup Domain Config
	RCC->BDCR |= RCC_BDCR_BDRST;
	RCC->BDCR &= ~RCC_BDCR_BDRST;

	// Set driving capability to medium high
	RCC->BDCR &= ~RCC_BDCR_LSEDRV_Msk;
	RCC->BDCR |= (0x02 <<RCC_BDCR_LSEDRV_Pos);

	// Start LSE clock
	RCC->BDCR |= RCC_BDCR_LSEON;

	// Wait until LSE is ready
	while ( (RCC->BDCR & RCC_BDCR_LSERDY) != RCC_BDCR_LSERDY);

	// Select LSE as RTC clock source
	RCC->BDCR &= ~RCC_BDCR_RTCSEL_Msk;
	RCC->BDCR |= RCC_BDCR_RTCSEL_LSE;

	// Enable RTC clock
	RCC->BDCR |= RCC_BDCR_RTCEN;
}

The main purpose of the above function is to start LSE oscillator and to use it as RTC clock.

image020.png
 

Note that the driving capability setting can produce unexpected results. Driving too high or too low may prevent the oscillator from working.

For this reason, you may want to have a look on the LSE clock by editing the SystemClock_Config() function so that PA8 pin outputs LSE clock instead of the System Clock as we did before:

static void SystemClock_Config()
{
	...
	
	/*--- Use PA8 as LSE output ---*/

	// Set MCO source as LSE
	RCC->CFGR &= ~RCC_CFGR_MCO_Msk;
	RCC->CFGR |=  RCC_CFGR_MCOSEL_LSE;     // Change here

	// No prescaler
	RCC->CFGR &= ~RCC_CFGR_MCOPRE_Msk;
	
	// Enable GPIOA clock
	RCC->AHBENR |= RCC_AHBENR_GPIOAEN;

	// Configure PA8 as Alternate function
	GPIOA->MODER &= ~GPIO_MODER_MODER8_Msk;
	GPIOA->MODER |= (0x02 <<GPIO_MODER_MODER8_Pos);

	// Set to AF0 (MCO output)
	GPIOA->AFR[1] &= ~(0x0000000F);
	GPIOA->AFR[1] |=  (0x00000000);

	...
}

 

Probing PA8 now exhibits the LSE clock. You should get a stable clock at 32.768kHz:

image023.png

 

3. Using the RTC

 

3.1. Set & Display current time

We need to implement a few functions to make RTC use easier:

  • A function that sets RTC time      →     BSP_RTC_SetTime(…)
  • A function that reads RTC time    →     BSP_RTC_GetTime(…)

Because “time” is actually made of hours, minutes, seconds, let us define the dedicated data structure type time_t.

And because time and date data in RTC register is stored in a BCD (Binary Coded Decimal) format, let us write both ways conversion functions (byte2bcd, bcd2byte).

Add the definitions to you rtc.h header:

/*
 * rtc.h
 *
 *  Created on: 20 août 2017
 *      Author: Laurent
 */

#ifndef BSP_INC_RTC_H_
#define BSP_INC_RTC_H_

#include "stm32f0xx.h"

// Typedefs

typedef struct
{
	uint8_t hours;
	uint8_t minutes;
	uint8_t seconds;
} time_t;

// Static functions

static uint8_t byte2bcd	(uint8_t byte);
static uint8_t bcd2byte	(uint8_t bcd);

// Public functions

void BSP_RTC_Clock_Config	(void);

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

#endif /* BSP_INC_RTC_H_ */

Then add the following implementations into rtc.c. First one is for the BSP_RTC_SetTime(…) function:

/*
 * BSP_RTC_SetTime(uint32_t time)
 * - Sets RTC Prescalers
 * - Sets RTC time
 */

void BSP_RTC_SetTime(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;

	// Enter Init
	RTC->ISR |= RTC_ISR_INIT;
	while ((RTC->ISR & RTC_ISR_INITF) != RTC_ISR_INITF);

	// Setup prescalers for 1s RTC clock
	RTC->PRER = 0x007F00FF;

	// Set time
	RTC->TR = bcdtime;

	// Exit Init
	RTC->ISR &= ~RTC_ISR_INIT;

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

Note that prescalers are set to:

image027.png

in order to get a 1s clock for the RTC counter.

 

Next, comes the BSP_RTC_GetTime(…) function:

/*
 * BSP_RTC_GetTime()
 * Retreive current RTC time in binary format
*/

void BSP_RTC_GetTime(time_t *ptime)
{
   ptime->hours   = bcd2byte((RTC->TR & (RTC_TR_HT_Msk  | RTC_TR_HU_Msk )) >>RTC_TR_HU_Pos);
   ptime->minutes = bcd2byte((RTC->TR & (RTC_TR_MNT_Msk | RTC_TR_MNU_Msk)) >>RTC_TR_MNU_Pos);
   ptime->seconds = bcd2byte((RTC->TR & (RTC_TR_ST_Msk  | RTC_TR_SU_Msk )) >>RTC_TR_SU_Pos);
}

And finally the two conversion functions:

/*
 * Converts 2 digit Decimal to BCD format
 * param: 	Byte to be converted.
 * retval: BCD Converted byte
 */

static uint8_t byte2bcd(uint8_t byte)
{
  uint8_t bcdhigh = 0;

  while (byte >= 10)
  {
    bcdhigh++;
    byte -= 10;
  }

  return  ((uint8_t)(bcdhigh << 4) | byte);
}

/*
 * Convert from 2 digit BCD to Binary
 * param  BCD value to be converted.
 * retval Converted word
 */

static uint8_t bcd2byte(uint8_t bcd)
{
  uint8_t tmp = 0;

  tmp = ((uint8_t)(bcd & (uint8_t)0xF0) >> (uint8_t)0x4) * 10;

  return (tmp + (bcd & (uint8_t)0x0F));
}

Now, we’re ready to experiment RTC within the main() function:


...

#include "rtc.h"

...

// Main program

void main()
{
	time_t		now;

	// Configure System Clock
	SystemClock_Config();

	// Configure RTC Clock
	BSP_RTC_Clock_Config();

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

	// Console init
	BSP_Console_Init();
	my_printf("\r\nConsole Ready!\r\n");

	// LED init
	BSP_LED_Init();

	// Main loop
	while(1)
	{
	   BSP_RTC_GetTime(&now);

           my_printf("RTC Time is %02d:%02d:%02d\r", now.hours, now.minutes, now.seconds);

	   BSP_LED_Toggle();
	   BSP_DELAY_ms(100);
	}
}

The above code simply initializes RTC time registers to 12:00:00. Then, the infinite loop displays the current time from there, at regular 100ms intervals.

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

You should see a clock displaying the current time, starting from 12:00:00:

image032.png
 

Wait some time, and then press the Reset (black) button on the Nucleo board. You can see that your clock is starting over at 12:00:00 every time you press the Reset button.

image033.png
 

 

  Commit name "RTC basics"
  Push onto Gitlab

 

That would be nice if the clock could keep track of current time regardless reset events…

Let us do that!

 

3.2. Prevent RTC reset in case of hardware reset

To avoid setting RTC time in the initialization part of the main() function, we first need to know by which mechanism the code starts executing. It can be for instance:

  • A Power On event (i.e. the device has just been powered on)
  • A Reset event
  • A Watchdog event
  •     …

Note that a Power On event will most likely also produce a Reset event. This is because a capacitor is always connected to the reset pin (NRST) to make sure that reset signal goes high a small amount of time after power is stable in the device.

The RCC peripheral features register flags that are updated depending on the way the code execution starts.

Edit the main() function as follows:

// Main program

void main()
{
	time_t		now;

	// Configure System Clock
	SystemClock_Config();

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

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

	// If this is a Power ON reset
	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 to 12:00:00
		now.hours   = 12;
		now.minutes = 00;
		now.seconds = 00;
		BSP_RTC_SetTime(&now);

		my_printf("RTC time has been reset to 12:00:00\r\n");
	}

	// If reset pin was held low
	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;

	// Main loop
	while(1)
	{
	   BSP_RTC_GetTime(&now);
	   my_printf("RTC Time is %02d:%02d:%02d\r", now.hours, now.minutes, now.seconds);
	   BSP_LED_Toggle();
	   BSP_DELAY_ms(100);
	}
}


 

By pressing the Reset button, you trig a Reset Pin event, without powering off the device. Therefore, no Power-On is found at startup.

In order to generate a Power-On startup event, you can for instance take the JP6 (IDD) jumper off for a moment and then back. You can do the same with JP5.

image038.png

 

Test the program using both Reset button and IDD jumper. You should be able to detect the startup condition, and then keep track of time when reset comes from the Reset button. For the reason above explained, the powering on the device also produces a Reset Pin event.

image040.png

 

  Commit name "RTC dealing with reset"
  Push onto Gitlab

 

3.3 Using RTC backup registers

The RTC peripheral provides five 32-bit registers, you can use as you want to save/read data that must survive a complete shutdown the MCU power supply (assuming VBAT is still powered).

Add for instance this code:

// Main program

void main()
{
	time_t		now;
	uint32_t	count;

...

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

	// If this is a Power ON reset
	if ( (RCC->CSR & RCC_CSR_PORRSTF_Msk) == RCC_CSR_PORRSTF )
	{
		...
	}

	// If reset pin was held low
	if ( (RCC->CSR & RCC_CSR_PINRSTF_Msk) == RCC_CSR_PINRSTF )
	{
		...
	}

	// Use Backup registers to count reset events
	RCC->APB1ENR |= RCC_APB1ENR_PWREN;
	PWR->CR |= PWR_CR_DBP;

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

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

	// Main loop
	while(1)
	{
		...
	}
}



Now you can count the number of times you pressed the Reset button:

image042.png

 

  Commit name "RTC backup registers"
  Push onto Gitlab

 

4. Summary

In this tutorial, you have learned the very basic use of the RTC peripheral. In addition, you have learned how to select correct initialization steps depending on the startup condition.