Internal RTC in STM32
Most of the STM32 devices have RTC (Real Time Clock) built-in, which can keep the track of the current time and date. RTC can be used for chronometers, alarm clocks, watches, small electronic agendas, and many other devices. Today in this tutorial we will see how to access internal RTC in STM32.
We will see how to set up the time, get the time and display it on the LCD. We will also use the alarm feature of the RTC.
CubeMX Configuration
Clock Configuration
Below are the images showing the clock configuration.
I am using the Nucleo F446RE board and it has both, LSE and HSE crystals on board. The LSE is 32.768Khz and HSE is of 8MHz. The LSE crystal is used to provide the clock to the RTC whereas the HSE will be used to clock the rest of the system.
In the RCC configuration, enable the crystal oscillator for both LSE and HSE. The second image shows how the clock is configured such that the RTC gets the 32.768KHz, while the rest of the system runs at 180MHz.
If your controller board does not have this dedicated LSE crystal of 32.768KHz, you can use the Internal RC oscillator (LSI RC) too.
RTC Configuration
Below is the image showing the RTC configuration.
Activate the Clock Source, Activate Calendar and enable at least one of the Alarm.
In the parameter configuration, I have set the 24Hr Time format and Data format is set to binary format. You can set any random values for the time, date and alarm. We will write functions to set up the values for these parameters in the code itself.
The Asynchronous and Synchronous Predivider values are used to calculate the value of the ck_spre. The values should be chosen in a way that the ck_spre value = 1. The formula to calculate ck_spre is shown below.
The RTCCLK is at 32768, so using the PREDIV_A value of 127 and PREDEV_S value of 255 in the above formula results in the ck_spre value =1.
We have also configured the Alarm, so make sure to enable the Alarm interrupt in the NVIC Tab. We will use the LED connected to pin PA5 for the Alarm signal, so let’s set the pin PA5 as output.
I2C Configuration
We are using the LCD1602 to display the current Time and Date. The LCD is connected using the PCF8574 I2C extender. Below is the image showing the I2C configuration.
I am using the I2C1 to connect the LCD. The I2C is configured in the standard mode with the clock speed set to 100KHz. The pins PB8 and PB9 are configured as the SCL and SDA pins.
We have already covered how to interface the LCD via I2C with STM32. You can check out the tutorial for more details.
NOTE that we will program the time to not reset with the board, but this is true as long as the board is connected to the power supply. When you disconnect the power supply, the time will reset and we can’t prevent this from happening. The only solution is to connect a 3V battery to the VBAT pin of the MCU. Doing this will keep the time running, and it does not matter if the MCU is connected to the power supply or not.
Some Insight into the CODE
We are going to set the time and date in the program itself. So first let’s write a function to set the time and date
set_time
void set_time (uint8_t hr, uint8_t min, uint8_t sec)
{
RTC_TimeTypeDef sTime = {0};
sTime.Hours = hr;
sTime.Minutes = min;
sTime.Seconds = sec;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
}
I copied this code from the pre-generated function MX_RTC_Init
and commented out the code inside the pre-generated function. This is to prevent the RTC from setting the same time after the MCU resets. The parameter of the function set_time
will be passed to their respective places and the function HAL_RTC_SetTime
will set the time to the RTC.
set_date
void set_date (uint8_t year, uint8_t month, uint8_t date, uint8_t day) // monday = 1
{
RTC_DateTypeDef sDate = {0};
sDate.WeekDay = day;
sDate.Month = month;
sDate.Date = date;
sDate.Year = year;
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x2345); // backup register
}
I copied this code again from the pre-generated function MX_RTC_Init
and commented out the code inside the pre-generated function. This is to prevent the RTC from setting the same date after the MCU resets.The parameter of the function set_
date will be passed to their respective places and the function HAL_RTC_Set
Date will set the date to the RTC.
Note here that the day starts with Monday, therefore Monday = 1 and Sunday = 7.
The function HAL_RTCEx_BKUPWrite
writes a random value to the backup Register RTC_BKP_DR1. The unique thing about this backup register is that it does not reset when the MCU resets, therefore the value (0x2345 in this case) will always be stored inside this Register, until we change it to some other value.
In the main function, we will read this backup Register. If it has the value we stored (0x2345), we will not update the time again. This prevents our code from writing the same time/date into the RTC every time the MCU resets.
This function HAL_RTCEx_BKUPWrite
is being called in the set_date
function, so you must call the set_date
function in the main function. Otherwise, even the time will not be able to keep up after the reset.
Get Time & Date
Below is the function to get the time and date from the RTC.
void get_time_date(char *time, char *date)
{
RTC_DateTypeDef gDate;
RTC_TimeTypeDef gTime;
/* Get the RTC current Time */
HAL_RTC_GetTime(&hrtc, &gTime, RTC_FORMAT_BIN);
/* Get the RTC current Date */
HAL_RTC_GetDate(&hrtc, &gDate, RTC_FORMAT_BIN);
/* Display time Format: hh:mm:ss */
sprintf((char*)time,"%02d:%02d:%02d",gTime.Hours, gTime.Minutes, gTime.Seconds);
/* Display date Format: dd-mm-yyyy */
sprintf((char*)date,"%02d-%02d-%2d",gDate.Date, gDate.Month, 2000 + gDate.Year);
}
The parameters of this function are:
- @time is the pointer to the character array, where the time data will be stored in the character form.
- @date is the pointer to the character array, where the date data will be stored in the character form.
Inside this function, we will first call the functions HAL_RTC_GetTime
and HAL_RTC_GetDate
to get the time and date in the binary format. The respective data will be stored in the RTC time and date structures.
If you want the data in number form, you can just define these structures globally, and then utilise the time and date data. But here I want to display them on the LCD, so I need to convert them to the character format.
The function sprintf will convert these numeric data in the character format and store them in the respective arrays.
set_alarm
The following function is used to set the ALARM.
void set_alarm (uint8_t hr, uint8_t min, uint8_t sec, uint8_t date)
{
RTC_AlarmTypeDef sAlarm = {0};
sAlarm.AlarmTime.Hours = hr;
sAlarm.AlarmTime.Minutes = min;
sAlarm.AlarmTime.Seconds = sec;
sAlarm.AlarmTime.SubSeconds = 0;
sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
sAlarm.AlarmDateWeekDay = date;
sAlarm.Alarm = RTC_ALARM_A;
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
}
The parameters of the function are the date and time when we want the Alarm to trigger. This function is again copied from the pre-generated function MX_RTC_Init
and I commented out this code inside the pre-generated function. The parameter of the function set_
alarm will be passed to their respective places and the function HAL_RTC_Set
Alarm will configure the Alarm in the RTC.
ALARM Event Callback
When the conditions for the Alarm in the RTC are met, an interrupt will trigger and the HAL_RTC_AlarmAEventCallback
will be called. We will write our code inside this function.
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
HAL_GPIO_WritePin (GPIOA, GPIO_PIN_5, 1); // turn on the LED
}
Inside this function we will set the pin PA5, which is connected to the LED on board. So the LED will turn on indicating the Alarm.
The main function
int main ()
{
....
lcd_init();
if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0x2345)
{
set_time(15, 54, 00);
set_date(24, 8, 11, 7);
}
set_alarm(15, 55, 0, 11);
Inside the main function we will first initialise the LCD. Then we will read the RTC backup Register. If it has the value we stored (0x2345), we will not update the time again. This prevents our code from writing the same time/date into the RTC every time the MCU resets.
But if the value stored in the Backup Register is not equal to 0x2345, that would mean that this is the first time the code is running and hence we need to set the time and date in the RTC. Here I am setting the current time and date. Also set the alarm by calling the set_alarm function.
We will read the time in the while loop as shown below.
while (1)
{
get_time_date(timeData, dateData);
lcd_put_cur(0,0);
lcd_send_string(timeData);
lcd_put_cur(1,0);
lcd_send_string(dateData);
HAL_Delay(500);
}
}
Here we will first read time and date data. The data is stored in the arrays, timeData and dateData, in the character form. We will print these arrays on the LCD.
Result
Below is the gif showing the result of the above code.
As you can see above, the time is updating every second on the LCD. Also note that as soon as the alarm is triggered, the LED on board tuns ON. This indicates that the Alarm was triggered successfully.