Working with STM32 and internal RTC Part 2: Driver development

In the previous guide (here), we took a look at the feature of internal RTC of STM32F4. In this guide, we shall develop the driver to read/write from/to RTC.

In this guide, we shall cover the following:

  • Initializing the RTC.
  • Update RTC.
  • Read from RTC.
  • Code.
  • Results.

5. Initializing the RTC:

Create new source and header file with name of rtc.c and rtc.h respectively.

Within the header file, declare the following enum which holds the days of week:

typedef enum
{

	Monday		=0x01,
	Tuesday		=0x02,
	Wednesday	=0x03,
	Thursday	=0x04,
	Friday		=0x05,
	Saturday	=0x06,
	Sunday		=0x07
}DoW;

Also, the following structure which holds the variables:

typedef struct ts {
    uint8_t sec;         /* seconds */
    uint8_t min;         /* minutes */
    uint8_t hour;        /* hours */
    uint8_t mday;        /* day of the month */
    uint8_t mon;         /* month */
    int16_t year;        /* year */
    uint8_t wday;        /* day of the week */
    uint8_t yday;        /* day in the year */
    uint8_t isdst;       /* daylight saving time */
    uint8_t year_s;      /* year in short notation*/

}ts;

Also, the following the functions:

void RTC_Init();
void rtc_update(ts *ts);
void RTC_Get(ts *ts);

Hence, the entire header file as following:

#ifndef RTC_H_
#define RTC_H_


#include "stdint.h"


typedef enum
{

	Monday		=0x01,
	Tuesday		=0x02,
	Wednesday	=0x03,
	Thursday	=0x04,
	Friday		=0x05,
	Saturday	=0x06,
	Sunday		=0x07
}DoW;



typedef struct ts {
    uint8_t sec;         /* seconds */
    uint8_t min;         /* minutes */
    uint8_t hour;        /* hours */
    uint8_t mday;        /* day of the month */
    uint8_t mon;         /* month */
    int16_t year;        /* year */
    uint8_t wday;        /* day of the week */
    uint8_t yday;        /* day in the year */
    uint8_t isdst;       /* daylight saving time */
    uint8_t year_s;      /* year in short notation*/

}ts;


void RTC_Init();
void rtc_update(ts *ts);
void RTC_Get(ts *ts);


#endif /* RTC_H_ */

Now, we move the source rtc.c file:

First step of the initializing is to enable clock access to the power interface.

In order to find which bus is power interface is connected to, we need to refer to the block diagram of STM32F411 which can be found in the datasheet:

Hence, we can find that power interface and RTC module are both connected to APB1 bus. Hence, we can enable power interface clock access as following:

Declare a function with name of void RTC_Init() which return nothing and takes no argument.

Within the function:

	/*Enable clock access to PWR */
	RCC->APB1ENR |= RCC_APB1ENR_PWREN;

Now, within power control register, disable backup domain write protection as following:

Note: there is typo, 0 means disabled and 1 means enabled

	/*Enable Backup access to config RTC*/
	PWR->CR |=PWR_CR_DBP;

Since the STM32F411 Nucleo-64 has onboard 32.768KHz oscillator which is far better when comes to stability and accuracy compared to the internal one, we shall enable the it and wait for it be set:

	/*Enable Low Speed External (LSE)*/
	RCC->BDCR |=RCC_BDCR_LSEON;

	/*Wait for LSE to be ready*/
	while((RCC->BDCR & RCC_BDCR_LSERDY) != RCC_BDCR_LSERDY){}

Set the RTC clock source to be the external low speed oscillator:

	/*Set RTC clock source to LSE*/
	RCC->BDCR |=RCC_BDCR_RTCSEL_0;
	RCC->BDCR &=~ RCC_BDCR_RTCSEL_1;

Now, enable the RTC:

	/*Enable the RTC*/
	RCC->BDCR |=RCC_BDCR_RTCEN;

In order to initialize the RTC properly, we need to unlock the write protection as following:

	#define RTC_WRITE_PROTECTION_ENABLE_1 ((uint8_t)0xCAU)
	#define RTC_WRITE_PROTECTION_ENABLE_2 ((uint8_t)0x53U)
	/*Disable RTC registers write protection*/
	RTC->WPR = RTC_WRITE_PROTECTION_ENABLE_1;
	RTC->WPR = RTC_WRITE_PROTECTION_ENABLE_2;

Enter the initializing mode by setting INIT bit in RTC_ISR register:

	/*Start init mode*/
	RTC->ISR |= RTC_ISR_INIT;

Wait until the initializing is entered:

	/*Wait until Initializing mode is active*/
	while((RTC->ISR &RTC_ISR_INITF)!=RTC_ISR_INITF);

Set the Asynch prescaler:

	#define RTC_ASYNCH_PREDIV          ((uint32_t)0x7F)
	/*Set Asynch prescaler*/
	RTC->PRER&=~(0x7F<<RTC_PRER_PREDIV_A_Pos);
	RTC->PRER|=(RTC_ASYNCH_PREDIV<<RTC_PRER_PREDIV_A_Pos);

Set the Synch prescaler:

	#define RTC_SYNCH_PREDIV           ((uint32_t)0x00F9)
	/*Set Sync prescaler*/
	RTC->PRER&=~(0x7FFF<<RTC_PRER_PREDIV_S_Pos);
	RTC->PRER|=(RTC_SYNCH_PREDIV<<RTC_PRER_PREDIV_S_Pos);

Note: Those values are taken from STM32CubeMX.

Exit the initializing mode by reseting INIT bit in ISR register:

/*Exit the initialization mode*/
	RTC->ISR&=~RTC_ISR_INIT;

Wait for synchronization by polling the INITF bit in ISR:

	/*Wait for synchronization*/
	while((RTC->ISR &RTC_ISR_INITF)==RTC_ISR_INITF);

Enable write protection by writing any value to WPR register:

	/*Enable RTC registers write protection*/
	RTC->WPR = 0xFF;

Finally disable backup access:

	/*Disable Backup access*/
	PWR->CR &=~PWR_CR_DBP;

Now, the RTC is running and start counting.

6. Update RTC:

Declare the following function:

void rtc_update(ts *ts)

Which return nothing and pointer to ts structure as argument.

Within the function:

Enable backup access to configure the RTC once again:

	/*Enable Backup access to config RTC*/
	PWR->CR |=PWR_CR_DBP;

Disable RTC write protection:

	/*Disable RTC registers write protection*/
	RTC->WPR = RTC_WRITE_PROTECTION_ENABLE_1;
	RTC->WPR = RTC_WRITE_PROTECTION_ENABLE_2;

Start the initializing and wait for it:

/*Start init mode*/
	RTC->ISR |= RTC_ISR_INIT;

	/*Wait until Initializing mode is active*/
	while((RTC->ISR & RTC_ISR_INITF)!=RTC_ISR_INITF);

Before continuing, we need to declare the following two function:

  • Set RTC date
  • Set RTC time

The set RTC date:

static void rtc_date_config(uint32_t WeekDay, uint32_t Day, uint32_t Month, uint32_t Year)

The function takes four arguments, day of the week, day of the month, the month and the year and return nothing.

Within the data, we can use the following to set the date:

uint32_t temp = 0U;

  temp = (WeekDay << RTC_DR_WDU_Pos)                                                        | \
         (((Year & 0xF0U) << (RTC_DR_YT_Pos - 4U)) | ((Year & 0x0FU) << RTC_DR_YU_Pos))   | \
         (((Month & 0xF0U) << (RTC_DR_MT_Pos - 4U)) | ((Month & 0x0FU) << RTC_DR_MU_Pos)) | \
         (((Day & 0xF0U) << (RTC_DR_DT_Pos - 4U)) | ((Day & 0x0FU) << RTC_DR_DU_Pos));

Since the format is based on BCD format, and each field is based on two values, for example, Year tens in BCD formant and Year uint in BCD format:

Finally write the date to RTC->DR register:

RTC->DR=(temp);

For setting the time, it is similar to setting the date as formula:

static void rtc_time_config( uint32_t Hours, uint32_t Minutes, uint32_t Seconds)
{
   uint32_t temp = 0U;

  temp = (((Hours & 0xF0U) << (RTC_TR_HT_Pos - 4U)) | ((Hours & 0x0FU) << RTC_TR_HU_Pos))     | \
         (((Minutes & 0xF0U) << (RTC_TR_MNT_Pos - 4U)) | ((Minutes & 0x0FU) << RTC_TR_MNU_Pos)) | \
         (((Seconds & 0xF0U) << (RTC_TR_ST_Pos - 4U)) | ((Seconds & 0x0FU) << RTC_TR_SU_Pos));
  RTC->TR=(temp);
}

Also, we need a method to change the integer to BCD format, the function as following:

static uint8_t rtc_convert_bin2bcd(uint8_t value)
{
	return  (uint8_t)((((value) / 10U) << 4U) | ((value) % 10U));
}

We shall continue with rtc_update,

Now, we can update the date and time using the function declared before:

	/*Set desired date */
	rtc_date_config(rtc_convert_bin2bcd(ts->wday),rtc_convert_bin2bcd(ts->mday),rtc_convert_bin2bcd(ts->mon),rtc_convert_bin2bcd(ts->year_s));

	/*Set desired time */
	rtc_time_config(rtc_convert_bin2bcd(ts->hour),rtc_convert_bin2bcd(ts->min),rtc_convert_bin2bcd(ts->sec));

Exit the initializing mode:

	/*Exit the initialization mode*/
	RTC->ISR&=~RTC_ISR_INIT;

	/*Wait for synchro*/
	while((RTC->ISR &RTC_ISR_INITF)==RTC_ISR_INITF);

	/*Enable RTC registers write protection*/
	RTC->WPR = 0xFF;

7. Get RTC data:

Declare the following function:

void RTC_Get(ts *ts)

Which takes pointer to ts structure as argument and return nothing:

Before continuing, we shall declare functions to get the data as following:

static uint32_t rtc_date_get_day(void)
{
 return (uint32_t)((READ_BIT(RTC->DR, (RTC_DR_DT | RTC_DR_DU))) >> RTC_DR_DU_Pos);
}

static uint32_t rtc_date_get_year(void)
{
 return (uint32_t)((READ_BIT(RTC->DR, (RTC_DR_YT | RTC_DR_YU))) >> RTC_DR_YU_Pos);
}

static uint32_t rtc_date_get_month(void)
{
 return (uint32_t)((READ_BIT(RTC->DR, (RTC_DR_MT | RTC_DR_MU)))>> RTC_DR_MU_Pos);
}

static uint32_t rtc_time_get_second(void)
{
 return (uint32_t)(READ_BIT(RTC->TR, (RTC_TR_ST | RTC_TR_SU)) >> RTC_TR_SU_Pos);
}

static uint32_t rtc_time_get_minute(void)
{
 return (uint32_t)(READ_BIT(RTC->TR, (RTC_TR_MNT | RTC_TR_MNU))>> RTC_TR_MNU_Pos);
}


static uint32_t rtc_time_get_hour(void)
{
  return (uint32_t)((READ_BIT(RTC->TR, (RTC_TR_HT | RTC_TR_HU))) >> RTC_TR_HU_Pos);
}


static uint32_t rtc_date_get_DoW(void)
{
	return (uint32_t)((READ_BIT(RTC->DR, (RTC_DR_WDU ))) >> RTC_DR_WDU_Pos);

}

Each function will read the required register and return the BCD format of the variable:

Take second for example:

(READ_BIT(RTC->TR, (RTC_TR_ST | RTC_TR_SU)) >> RTC_TR_SU_Pos);

The second part has two variable , second ten and second unit. We need to read both values and we need to read both and convert the value from BCD to integer as following:

static uint8_t rtc_convert_bcd2bin(uint8_t value )
{
	return (uint8_t)(((uint8_t)((value) & (uint8_t)0xF0U) >> (uint8_t)0x4U) * 10U + ((value) & (uint8_t)0x0FU));
}

Hence, we can fill the ts structure as following:

	/*Time */
	ts->sec		= rtc_convert_bcd2bin(rtc_time_get_second());
	ts->min		= rtc_convert_bcd2bin(rtc_time_get_minute());
	ts->hour	= rtc_convert_bcd2bin(rtc_time_get_hour());
	/*Date*/
	ts->mday	= rtc_convert_bcd2bin(rtc_date_get_day());
	ts->mon		= rtc_convert_bcd2bin(rtc_date_get_month());
	ts->year	= rtc_convert_bcd2bin(rtc_date_get_year());
	ts->wday	= rtc_convert_bcd2bin(rtc_date_get_DoW());

Hence, the source file as following:

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


#define RTC_WRITE_PROTECTION_ENABLE_1 	((uint8_t)0xCAU)
#define RTC_WRITE_PROTECTION_ENABLE_2 	((uint8_t)0x53U)


#define RTC_ASYNCH_PREDIV          		((uint32_t)0x7F)
#define RTC_SYNCH_PREDIV           		((uint32_t)0x00F9)




static uint8_t rtc_convert_bin2bcd(uint8_t value)
{
	return  (uint8_t)((((value) / 10U) << 4U) | ((value) % 10U));
}

static uint8_t rtc_convert_bcd2bin(uint8_t value )
{
	return (uint8_t)(((uint8_t)((value) & (uint8_t)0xF0U) >> (uint8_t)0x4U) * 10U + ((value) & (uint8_t)0x0FU));
}



static void rtc_date_config(uint32_t WeekDay, uint32_t Day, uint32_t Month, uint32_t Year)
{
   uint32_t temp = 0U;

  temp = (WeekDay << RTC_DR_WDU_Pos)                                                        | \
         (((Year & 0xF0U) << (RTC_DR_YT_Pos - 4U)) | ((Year & 0x0FU) << RTC_DR_YU_Pos))   | \
         (((Month & 0xF0U) << (RTC_DR_MT_Pos - 4U)) | ((Month & 0x0FU) << RTC_DR_MU_Pos)) | \
         (((Day & 0xF0U) << (RTC_DR_DT_Pos - 4U)) | ((Day & 0x0FU) << RTC_DR_DU_Pos));

  RTC->DR=(temp);
}



static void rtc_time_config( uint32_t Hours, uint32_t Minutes, uint32_t Seconds)
{
   uint32_t temp = 0U;

  temp = (((Hours & 0xF0U) << (RTC_TR_HT_Pos - 4U)) | ((Hours & 0x0FU) << RTC_TR_HU_Pos))     | \
         (((Minutes & 0xF0U) << (RTC_TR_MNT_Pos - 4U)) | ((Minutes & 0x0FU) << RTC_TR_MNU_Pos)) | \
         (((Seconds & 0xF0U) << (RTC_TR_ST_Pos - 4U)) | ((Seconds & 0x0FU) << RTC_TR_SU_Pos));
  RTC->TR=(temp);
}






void RTC_Init()
{

	/*Enable Backup access to config RTC*/
	PWR->CR |=PWR_CR_DBP;

	/*Enable Low Speed External (LSE)*/
	RCC->BDCR |=RCC_BDCR_LSEON;

	/*Wait for LSE to be ready*/
	while((RCC->BDCR & RCC_BDCR_LSERDY) != RCC_BDCR_LSERDY){}


	/*Set RTC clock source to LSE*/
	RCC->BDCR |=RCC_BDCR_RTCSEL_0;
	RCC->BDCR &=~ RCC_BDCR_RTCSEL_1;

	/*Enable the RTC*/
	RCC->BDCR |=RCC_BDCR_RTCEN;

	/*Disable RTC registers write protection*/
	RTC->WPR = RTC_WRITE_PROTECTION_ENABLE_1;
	RTC->WPR = RTC_WRITE_PROTECTION_ENABLE_2;

	/*Start init mode*/
	RTC->ISR |= RTC_ISR_INIT;

	/*Wait until Initializing mode is active*/
	while((RTC->ISR &RTC_ISR_INITF)!=RTC_ISR_INITF);

	/*Set Asynch prescaler*/
	RTC->PRER&=~(0x7F<<RTC_PRER_PREDIV_A_Pos);
	RTC->PRER|=(RTC_ASYNCH_PREDIV<<RTC_PRER_PREDIV_A_Pos);

	/*Set Sync prescaler*/
	RTC->PRER &=~(0x7FFF<<RTC_PRER_PREDIV_S_Pos);
	RTC->PRER|=(RTC_SYNCH_PREDIV<<RTC_PRER_PREDIV_S_Pos);

	/*Exit the initialization mode*/
	RTC->ISR&=~RTC_ISR_INIT;

	/*Wait for synchronization*/
	while((RTC->ISR &RTC_ISR_INITF)==RTC_ISR_INITF);

	/*Enable RTC registers write protection*/
	RTC->WPR = 0xFF;

	/*Disable Backup access*/
	PWR->CR &=~PWR_CR_DBP;

}

void rtc_update(ts *ts)
{
	/*Enable Backup access to config RTC*/
	PWR->CR |=PWR_CR_DBP;

	/*Disable RTC registers write protection*/
	RTC->WPR = RTC_WRITE_PROTECTION_ENABLE_1;
	RTC->WPR = RTC_WRITE_PROTECTION_ENABLE_2;


	/*Start init mode*/
	RTC->ISR |= RTC_ISR_INIT;

	/*Wait until Initializing mode is active*/
	while((RTC->ISR & RTC_ISR_INITF)!=RTC_ISR_INITF);

	/*Set desired date */
	rtc_date_config(rtc_convert_bin2bcd(ts->wday),rtc_convert_bin2bcd(ts->mday),rtc_convert_bin2bcd(ts->mon),rtc_convert_bin2bcd(ts->year_s));

	/*Set desired time */
	rtc_time_config(rtc_convert_bin2bcd(ts->hour),rtc_convert_bin2bcd(ts->min),rtc_convert_bin2bcd(ts->sec));


	/*Exit the initialization mode*/
	RTC->ISR&=~RTC_ISR_INIT;

	/*Wait for synchro*/
	while((RTC->ISR &RTC_ISR_INITF)==RTC_ISR_INITF);

	/*Enable RTC registers write protection*/
	RTC->WPR = 0xFF;
}



static uint32_t rtc_date_get_day(void)
{
 return (uint32_t)((READ_BIT(RTC->DR, (RTC_DR_DT | RTC_DR_DU))) >> RTC_DR_DU_Pos);
}

static uint32_t rtc_date_get_year(void)
{
 return (uint32_t)((READ_BIT(RTC->DR, (RTC_DR_YT | RTC_DR_YU))) >> RTC_DR_YU_Pos);
}

static uint32_t rtc_date_get_month(void)
{
 return (uint32_t)((READ_BIT(RTC->DR, (RTC_DR_MT | RTC_DR_MU)))>> RTC_DR_MU_Pos);
}

static uint32_t rtc_time_get_second(void)
{
 return (uint32_t)(READ_BIT(RTC->TR, (RTC_TR_ST | RTC_TR_SU)) >> RTC_TR_SU_Pos);
}

static uint32_t rtc_time_get_minute(void)
{
 return (uint32_t)(READ_BIT(RTC->TR, (RTC_TR_MNT | RTC_TR_MNU))>> RTC_TR_MNU_Pos);
}


static uint32_t rtc_time_get_hour(void)
{
  return (uint32_t)((READ_BIT(RTC->TR, (RTC_TR_HT | RTC_TR_HU))) >> RTC_TR_HU_Pos);
}


static uint32_t rtc_date_get_DoW(void)
{
	return (uint32_t)((READ_BIT(RTC->DR, (RTC_DR_WDU ))) >> RTC_DR_WDU_Pos);

}

void RTC_Get(ts *ts)
{
	/*Time */
	ts->sec		= rtc_convert_bcd2bin(rtc_time_get_second());
	ts->min		= rtc_convert_bcd2bin(rtc_time_get_minute());
	ts->hour	= rtc_convert_bcd2bin(rtc_time_get_hour());
	/*Date*/
	ts->mday	= rtc_convert_bcd2bin(rtc_date_get_day());
	ts->mon		= rtc_convert_bcd2bin(rtc_date_get_month());
	ts->year	= rtc_convert_bcd2bin(rtc_date_get_year());
	ts->wday	= rtc_convert_bcd2bin(rtc_date_get_DoW());
}

Within main:

#include "LiquidCrystal_PCF8574.h"
#include "delay.h"
#include "stdio.h"
#include "rtc.h"

char lcd_data[20];



ts time;

int main(void)
{
	lcd_init();
	RTC_Init();

	time.hour=17;
	time.min=15;
	time.sec=30;

	time.wday=Saturday;
	time.mday=11;
	time.mon=3;
	time.year_s=23;

	//rtc_update(&time);


	while(1)
	{
		RTC_Get(&time);
		setCursor(0,0);
		sprintf(lcd_data,"%d:%d:%d    ",time.hour,time.min,time.sec);
		lcd_send_string(lcd_data);
		sprintf(lcd_data,"%d/%d/%d     ",time.mday,time.mon,time.year_s);
		setCursor(0,1);
		lcd_send_string(lcd_data);
		setCursor(0,2);
		switch (time.wday)
		{
			case Monday		: sprintf(lcd_data,"Monday  "); break;

			case Tuesday	: sprintf(lcd_data,"Tuesday  "); break;

			case Wednesday	: sprintf(lcd_data,"Wednesday  "); break;

			case Thursday	: sprintf(lcd_data,"Thursday  "); break;

			case Friday		: sprintf(lcd_data,"Friday    "); break;

			case Saturday	: sprintf(lcd_data,"Saturday   "); break;

			case Sunday		: sprintf(lcd_data,"Sunday  "); break;

			default			: break;

		}

		lcd_send_string(lcd_data);


	}

}

8. Code:

You can download the source code from here:

9. Results:

Add Comment

Your email address will not be published. Required fields are marked *