Skip to main content

Command Palette

Search for a command to run...

Internal RTC

Updated
•8 min read•View as Markdown
Internal RTC
D

Hi, this blog is a way to keep all my projects in one place, while documenting the process along with all the problems that I encounter.

In my most recent project, which you can read Here. I interfaced my DS1307 real time clock with my STM 32 discovery board and displayed the date and time on LCD display using an I2C adaptor. While doing that project I realized that my STM32 F407VG has an inbuild RTC. The RTC is inbuild into the processor, so I don’t need to use I2C or anything, however this would be a lot harder as I have to deal with the internal registers. But today I am ready to take up the challenge. The internal RTC means that there are no hardware connections as the RTC is part of the chip.

RTC

First, I thought I could tell you some of the features available in the inbuild RTC. It has the time and data with automatic leap year correction like the DS1307. It does have additional feature which is absent in the DS1307 like: wake up, alarm, timestamp, daylight saving correction and tamper detection as well as a more precise oscillator.

HAL

Everyone who implements an RTC using the discovery board probably does it with the STM32 CubeMX or with HAL libraries. I have yet to do a project using the HAL library. HAL stands for hardware abstraction layer, what it does is, provide a layer between the programmer and the microcontroller, so instead of directly accessing the registers we can call the respective HAL function and it will set the registers accordingly. I have stayed away for HAL code as it isn’t that descriptive. I mean the reason I am doing these projects is so that I can learn that is happening at a fundamental level. In CubeMX you can tell the software what you want and it will generate code to do it. CubeMX isn’t hard and actually quite easy. I would like to stick with register level code as far as possible. Having said that I have acknowledge that this might be a little too hard to implement only in register level code so I might use a few snips of HAL.

Enabling Clock and RTC

We need to provide the RTC with a clock source, this is the clock that the RTC will use to count time. The more precise the clock source the more precise is our time. We can use the internal low speed oscillator (LSI), the internal high-speed oscillator (HSI) or the external high-speed oscillator (HSE). This part of the code would also involve configuring the RCC register. There is a lot of initializations required, so I have used the HAL library to implement this. Most of the HAL functions are intuitive and by reading the name you can understand the working. We also need to define a function called systick which handles the event of a clock pulse interrupt.

BCD

Similar to the DS1307 project the RTC also uses BCD format so I will implement two functions: one to convert BCD to decimal and one to convert decimal to BCD.

unsigned int  decimal2bcd(unsigned int num){
    return ((num/10 * 16) + (num % 10));
}

unsigned int bcd2decimal(unsigned int num){
    return ((num/16 * 10) + (num % 16));
}

Setting Time and Data

So, let’s get to codding that RTC. The most important resource for this would be the register datasheet for the board. If you read the datasheet carefully it will tell you what all steps to take. The page 803 of the datasheet tells us the procedure we must follow to set the time and date on the RTC.

image.png

Here there is actually an important concept to understand. “Register Write Protection” this is a feature that makes our lives a lot harder but is a very important feature of the RTC. This feature locks certain registers and protects it from being changed. This allows the time and data to be stored even if we reset the board. Luckily the datasheet tells us what we have to do remove this protection. “After system reset, the RTC registers are protected against parasitic write access with the DBP bit of the PWR power control register (PWR_CR). The DBP bit must be set to enable RTC registers write access. After backup domain reset, all the RTC registers are write-protected. Writing to the RTC registers is enabled by writing a key into the Write Protection register, RTC_WPR. The following steps are required to unlock the write protection on all the RTC registers except for RTC_ISR[13:8], RTC_TAFCR, and RTC_BKPxR.

  • Write ‘0xCA’ into the RTC_WPR register.
  • Write ‘0x53’ into the RTC_WPR register.

Writing a wrong key reactivates the write protection. The protection mechanism is not affected by system reset”

So the first step is to set the DBP (disable backup write protection) bit of the CR (control register) of the PWR (power controller).That is done by PWR->CR |= (0x1UL<<8);

The next step is to enter the access code which is done by RTC->WPR = (0xCA); and RTC->WPR = (0x53);

Now we have successfully removed the protection, now we can go ahead and enable the clock in the RCC register RCC->BDCR |= (0x1UL<<15);

We can also choose what oscillator we want to use for the RTC. We have 3 options the LSE (low speed external), LSI (low speed internal) and the HSE (high speed external) I am just going to use the LSI as I do not require much precision. RCC->BDCR |= (0x2UL<<8); Now we can go back to the datasheet and see the rest of the procedure to set the time.

void resetprotect(void){
    PWR->CR |= (0x1UL<<8);                                //write protection disabled
    RTC->WPR = (0xCA);                                        //access code 1
    RTC->WPR = (0x53);                                        //access code 2
}
void clockconfig(void){
    RCC->BDCR |= (0x2UL<<8);        //Select the RTC clock source as the LSI
    RCC->BDCR |= (0x1UL<<15);        //enabling the clock for RTC
}
void setting(void){
    RTC->ISR |= (0x1UL<<7);     //set initialization mode
    while (!((RTC->ISR) & (0x1UL<<6)));        //polling to see if enerter the initialization mode
    RTC->PRER |= (127<<16);                              //Asynchronous prescaler factor
    RTC->PRER |= (255<<0);                                 //Synchronous prescaler factor
    settime();
    setdate();
    RTC->CR |= ~(0x1UL<<6);                                //FMT BIT to 0 (24 hour format)
    RTC->CR |= ~(0x1UL<<5);                                //by pass shadow registers is not set
    RTC->ISR |= ~(0x1UL<<7);                          //leavining initialization mode
    set_flag = ((RTC->ISR) & (0x1UL<<4));    //to check if the clock is set
}

void settime(void){
    //setting time to 18:42:30
    RTC->TR &= ~(0x1UL<<22);                     //to make bit 22 0, to set at 24 hour format
    RTC->TR |= (decimal2bcd(1)<<20);  //to set the 10 digit of the hours (set to either 0,1,2) 
    RTC->TR |= (decimal2bcd(8)<<16);  //to set the units digits of the hours (set to [0,9])
    RTC->TR |= (decimal2bcd(4)<<12);  //to set the 10 digit of the minuits (set to [0,5])
    RTC->TR |= (decimal2bcd(2)<<8);   //to set the units digit of the minuites (set to [0,9])
    RTC->TR |= (decimal2bcd(3)<<4);   //to set the 10 digti of the seconds (set to [0,5])
    RTC->TR |= (decimal2bcd(0)<<0);   //to set the units digit of the seconds (set to [0,9])
}

void setdate(void){
    //setting date to 05-06-2021 (monday)
    RTC->DR |= (decimal2bcd(2)<<20);  //to set the 10 digit of the years (set to [0,9])
    RTC->DR |= (decimal2bcd(1)<<16);  //to set the units digit of the years (set to [0,9])
    RTC->DR |= (0x1UL<<13);                        //to set the day of week (monday =001,sunday =111) (set between 001 and 111)
    RTC->DR |= (decimal2bcd(0)<<12);  //to set the 10 digits of the month (set to 0 or 1)
    RTC->DR |= (decimal2bcd(6)<<8);     //to set the units digits of the month (set to [0.9])
    RTC->DR |= (decimal2bcd(0)<<4);        //to set the 10 digit of the date [set to 1,2,3]
    RTC->DR |= (decimal2bcd(5)<<0);        //to set the units of the date (set to [0,9]) 
}

However, I faced a lot of issues in setting the time and date because of the write protection. It is suggested to use HAL libraries for this task. So I will implement it in the following lines of code.

    // Set time 
    sTime.Hours = 22;
    sTime.Minutes = 59;
    sTime.Seconds = 45;
    HAL_RTC_SetTime(&myRtcHandle, &sTime, RTC_FORMAT_BIN);
    //Set date 
    sDate.Date = 29;
    sDate.Month = RTC_MONTH_DECEMBER;
    sDate.WeekDay = RTC_WEEKDAY_SUNDAY;
    sDate.Year = 21;
    HAL_RTC_SetDate(&myRtcHandle, &sDate, RTC_FORMAT_BIN);

Looking at the HAL code it does seem a lot easier. Maybe HAL isn’t that bad after all.

Reading Date and Time

Similar to wring the Date and Time we can read it from the TR and DT registers. I used bit extraction to get the concerned bits.

void readtime(void){
    time = RTC->TR;
    hour = bcd2decimal((time & (0x3FUL<<16)));
    min = bcd2decimal((time & (0x7FUL<<8)));
    sec = bcd2decimal((time & (0x7FUL<<0)));
}

void readdate(void){
    calender =RTC->DR;
    year = bcd2decimal((calender & (0xFFUL<<16)));
    day = bcd2decimal((calender & (0x7UL<<13)));
    month = bcd2decimal((calender & (0x1FUL<<8)));
    date = bcd2decimal((calender & (0x3FUL<<0)));
}

Final Code

#include "stm32f4xx_hal.h"

void SystemClock_Config(void);
void LED_Config(void);
void RTC_Config(void);

RTC_HandleTypeDef myRtcHandle;
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;

void readdate(void);
void readtime(void);
unsigned int bcd2decimal(unsigned int num);
unsigned int  decimal2bcd(unsigned int num);
unsigned long  sec,min,hour,day,date,month,year;
uint32_t time,calender;

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    RTC_Config();

    //Set time 
    sTime.Hours = 22;
    sTime.Minutes = 59;
    sTime.Seconds = 45;
    HAL_RTC_SetTime(&myRtcHandle, &sTime, RTC_FORMAT_BIN);
    //Set date 
    sDate.Date = 29;
    sDate.Month = RTC_MONTH_DECEMBER;
    sDate.WeekDay = RTC_WEEKDAY_SUNDAY;
    sDate.Year = 21;
    HAL_RTC_SetDate(&myRtcHandle, &sDate, RTC_FORMAT_BIN);



    while(1)
    {
        readtime();
        readdate();
    }
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = 16;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  HAL_RCC_OscConfig(&RCC_OscInitStruct);
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
  PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
  HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

void RTC_Config(void)
{
    __HAL_RCC_RTC_ENABLE();
    myRtcHandle.Instance = RTC;
    myRtcHandle.Init.HourFormat = RTC_HOURFORMAT_24;
    myRtcHandle.Init.AsynchPrediv = 127;
    myRtcHandle.Init.SynchPrediv = 255;
    myRtcHandle.Init.OutPut = RTC_OUTPUT_DISABLE;
    HAL_RTC_Init(&myRtcHandle);
}

void SysTick_Handler(void)
{
  HAL_IncTick();
  HAL_SYSTICK_IRQHandler();
}
void readtime(void){
    time = RTC->TR;
    hour = bcd2decimal((time & (0x3FUL<<16)));
    min = bcd2decimal((time & (0x7FUL<<8)));
    sec = bcd2decimal((time & (0x7FUL<<0)));
}
void readdate(void){
    calender =RTC->DR;
    year = bcd2decimal((calender & (0xFFUL<<16)));
    day = bcd2decimal((calender & (0x7UL<<13)));
    month = bcd2decimal((calender & (0x1FUL<<8)));
    date = bcd2decimal((calender & (0x3FUL<<0)));
}
unsigned int  decimal2bcd(unsigned int num){
    return ((num/10 * 16) + (num % 10));
}
unsigned int bcd2decimal(unsigned int num){
    return ((num/16 * 10) + (num % 16));
}

Output

image.png

This project was a lot harder than I expected. I did not expect to encounter such difficulties with the write protection. However, I was able to get the job done with the respective HAL functions. While I try to stay away for HAL, it is widely accepted in the community and considered a proper way of coding.

Github: https://github.com/daniboi16/STM32-F4/tree/main/009)%20RTC

32 views