Real-Time Clock (RTC) with STM32 : Displaying Time

by Marwen Maghrebi

In this article, we will explore how to integrate a Real-Time Clock (RTC) with an STM32 microcontroller.

Real-Time Clock (RTC) with STM32 project overview

Things used in this project

Software apps and online services:

1- STMicroelectronics STM32CubeMX

2- STMicroelectronics STM32CubeIDE

3- Proteus 8

Real-Time Clock (RTC) and Its Application in STM32

Understanding Real-Time Clocks (RTC):

A Real-Time Clock (RTC) is a critical component in embedded systems that maintains accurate time and date tracking even when the power is off. This capability allows RTCs to function independently of the main processor, making them essential for devices that require precise timing without continuous processor intervention. RTCs find applications across various domains, including clocks, watches, washing machines, medicine dispensers, and data loggers. Unlike general-purpose timers in microcontrollers that focus on PWM generation and waveform tasks, RTCs provide enhanced accuracy for timekeeping.

Traditional 8-bit microcontrollers, such as PICs and AVRs, often lack integrated RTC modules. This limitation necessitates the use of external RTC chips like the DS1302 or PCF8563. However, modern microcontrollers like the STM32 come equipped with built-in RTC functionality. This advancement eliminates the need for additional hardware, saving cost, space, and design complexity. As a result, developers can create smarter and more compact devices with ease.

Block diagram showing RTC integration in STM32 microcontrollers

The RTC Alarm Feature:

The RTC Alarm is a specialized feature that triggers an event at a predefined time or date. This functionality is crucial for applications requiring timed operations. You can configure it to wake the system from low-power modes, such as Standby or  Sleep mode, or to execute a specific task when the specified time or date arrives. The ability to operate independently from the main processor enhances the efficiency of embedded systems, allowing them to conserve power while remaining responsive.

Why Use RTC Alarms?:

  • Low-Power Management: Minimizing power consumption is a significant concern in embedded systems, especially for battery-operated devices. The RTC Alarm can wake the microcontroller from a low-power state at a specific time. This feature makes it ideal for systems that need to remain in a power-saving mode for extended periods, executing tasks only at designated intervals.
  • Scheduled Events: RTC Alarms facilitate the scheduling of tasks at predetermined times. For instance, a system can be programmed to send sensor data every hour, or an alarm can trigger notifications at specific times. This enhances the functionality of time-dependent applications.
  • Time-Sensitive Applications: Applications that require operations based on real-world time, such as alarms, clocks, or timed data logging, benefit from RTC alarms. These alarms ensure that time-sensitive operations execute reliably and accurately.
  • Autonomous Wake-Up: An RTC Alarm can autonomously wake the microcontroller to perform critical tasks, even when the main processor is in a sleep or standby state. This capability reduces the need for manual intervention and simplifies power management in embedded systems.

To kickstart this project, we will implement a Real-Time Clock (RTC) using an STM32 microcontroller. Our primary goal is to display the current time and date on an ST7735 TFT display while allowing users to set these values through UART (Universal Asynchronous Receiver-Transmitter). Specifically, we will configure the RTC module to keep accurate time, establish UART communication for user input, and utilize the ST7735 display for visual output.

STM32CubeMX Configuration:

  • Open CubeMX & Create New Project Choose The Target MCU STM32F103C6 & Double-Click Its Name
  • Go To The Clock Configuration & Set The System Clock To 72MHz

Configuration for the RTC Mode:

  • In the Categories tab, (Activate Clock source calendar)
  • In Parameter Settings Enbale auto Predivider Calculation & Asynchronous Predevider value 

Configuration for the GPIO Mode:

  • Configure The GPIO Pins [PB12 .. PB14] as Input Pins

Configuration for the SPIMode:

  • In the Categories tab, select the SPI1 & Transmit Only Master
  • In the Parameter settings tab, set the Prescaler (for baud Rate) to 4

Configuration for the UART Mode:

  • Enable USART1 Module (Asynchronous Mode)
  • Set the USART1 communication parameters (baud rate = 115200, parity=NON, stop bits =1, and word length = 8bits)
  • Generate The Initialization Code & Open The Project In CubeIDE
 

STM32CubeIDE Configuration:

Includes and Initialization

This section includes the core logic for validating the date and time formats received via UART. We ensure the inputs are correct before setting them on the RTC. Functions like validateDate, validateTime, and checkInputFormat check the input validity. The Set_RTC_TimeAndDate function solicits, parses, and validates user inputs. If the inputs are valid, the RTC updates with the new time and date values. This section emphasizes user interaction and error handling for setting up the RTC.

  1. /* Includes ------------------------------------------------------------------*/
  2. #include "main.h"
  3.  
  4. /* Private includes ----------------------------------------------------------*/
  5. /* USER CODE BEGIN Includes */
  6. #include "fonts.h"
  7. #include "ST7735.h"
  8. #include <stdio.h>
  9. #include <string.h>
  10. /* USER CODE END Includes */
  11.  
  12. /* Private variables ---------------------------------------------------------*/
  13. RTC_HandleTypeDef hrtc;
  14. SPI_HandleTypeDef hspi1;
  15. UART_HandleTypeDef huart1;
  16.  
  17. /* USER CODE BEGIN PV */
  18. char time[30];
  19. char date[30];
  20. char uart_buf[30];
  21. RTC_TimeTypeDef sTime;
  22. RTC_DateTypeDef sDate;
  23. /* USER CODE END PV */
  24.  
  25. /* Private function prototypes -----------------------------------------------*/
  26. void SystemClock_Config(void);
  27. static void MX_GPIO_Init(void);
  28. static void MX_SPI1_Init(void);
  29. static void MX_RTC_Init(void);
  30. static void MX_USART1_UART_Init(void);
  31. /* USER CODE BEGIN PFP */
  32.  
  33. /* USER CODE END PFP */
  34.  
  35. /* Private user code ---------------------------------------------------------*/
  36. /* USER CODE BEGIN 0 */
  37.  
  38. // State variable to check if the configuration is done
  39. uint8_t configComplete = 0; // Configuration status

RTC Configuration and Input Handling

This part includes the core logic for validating the date and time formats received via UART, as well as ensuring the inputs are correct before setting them on the RTC. Functions like validateDate, validateTime, and checkInputFormat check the input validity. The Set_RTC_TimeAndDate function is where user inputs are solicited, parsed, and validated. If inputs are valid, the RTC is updated with the new time and date values. This section emphasizes user interaction and error handling for setting up the RTC.

  1. // Function to validate the date
  2. bool validateDate(RTC_DateTypeDef *date) {
  3. // Array with the number of days in each month
  4. uint8_t daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  5.  
  6. // Validate month
  7. if (date->Month < 1 || date->Month > 12) {
  8. return false;
  9. }
  10.  
  11. // Validate day
  12. if (date->Date < 1 || date->Date > daysInMonth[date->Month - 1]) {
  13. return false;
  14. }
  15.  
  16. // The date is valid
  17. return true;
  18. }
  19.  
  20. // Function to validate the time
  21. bool validateTime(RTC_TimeTypeDef *time) {
  22. // Validate hours, minutes, and seconds
  23. if (time->Hours > 23 || time->Minutes > 59 || time->Seconds > 59) {
  24. return false;
  25. }
  26.  
  27. // The time is valid
  28. return true;
  29. }
  30.  
  31. // Function to check format of the input strings
  32. bool checkInputFormat(const char *input, const char *format) {
  33. // Check for the correct length
  34. if (strlen(input) != strlen(format)) {
  35. return false;
  36. }
  37.  
  38. for (size_t i = 0; i < strlen(format); i++) {
  39. if (format[i] == ':') {
  40. if (input[i] != ':') return false; // Expecting ':' in time
  41. } else if (format[i] == '/') {
  42. if (input[i] != '/') return false; // Expecting '/' in date
  43. } else if (!isdigit(input[i])) {
  44. return false; // Expecting a digit
  45. }
  46. }
  47. return true; // Format is correct
  48. }
  49.  
  50. void Set_RTC_TimeAndDate(void) {
  51. RTC_TimeTypeDef sTime;
  52. RTC_DateTypeDef sDate;
  53. char uart_buf[50]; // Ensure this is appropriately sized for your messages
  54.  
  55. while (1) { // Loop until valid date and time are set
  56. // Get the time from UART
  57. const char *timePrompt = "Enter Time (HH:MM:SS): ";
  58. HAL_UART_Transmit(&huart1, (uint8_t *)timePrompt, strlen(timePrompt), HAL_MAX_DELAY);
  59. UART_ReceiveString(uart_buf, sizeof(uart_buf)); // Assuming uart_buf is defined
  60.  
  61. // Validate the format of the input
  62. if (!checkInputFormat(uart_buf, "HH:MM:SS")) {
  63. const char *errorMsg = "Invalid time format. Please use HH:MM:SS\n\r";
  64. HAL_UART_Transmit(&huart1, (uint8_t *)errorMsg, strlen(errorMsg), HAL_MAX_DELAY);
  65. continue; // Prompt for input again
  66. }
  67.  
  68. // Parse the received time from uart_buf
  69. sTime.Hours = (uart_buf[0] - '0') * 10 + (uart_buf[1] - '0');
  70. sTime.Minutes = (uart_buf[3] - '0') * 10 + (uart_buf[4] - '0');
  71. sTime.Seconds = (uart_buf[6] - '0') * 10 + (uart_buf[7] - '0');
  72.  
  73. // Prompt for date input
  74. const char *datePrompt = "Enter Date (DD/MM/YY): ";
  75. HAL_UART_Transmit(&huart1, (uint8_t *)datePrompt, strlen(datePrompt), HAL_MAX_DELAY);
  76. UART_ReceiveString(uart_buf, sizeof(uart_buf)); // Assuming uart_buf is defined
  77.  
  78. // Validate the format of the input
  79. if (!checkInputFormat(uart_buf, "DD/MM/YY")) {
  80. const char *errorMsg = "Invalid date format. Please use DD/MM/YY\n\r";
  81. HAL_UART_Transmit(&huart1, (uint8_t *)errorMsg, strlen(errorMsg), HAL_MAX_DELAY);
  82. continue; // Prompt for input again
  83. }
  84.  
  85. // Parse the received date from uart_buf
  86. sDate.Date = (uart_buf[0] - '0') * 10 + (uart_buf[1] - '0');
  87. sDate.Month = (uart_buf[3] - '0') * 10 + (uart_buf[4] - '0');
  88. sDate.Year = (uart_buf[6] - '0') * 10 + (uart_buf[7] - '0'); // Two-digit year
  89.  
  90. // Validate date and time
  91. bool dateValid = validateDate(&sDate);
  92. bool timeValid = validateTime(&sTime);
  93.  
  94. if (!dateValid || !timeValid) {
  95. const char *errorMsg = "Error in time or in date. Please try again.\n\r";
  96. HAL_UART_Transmit(&huart1, (uint8_t *)errorMsg, strlen(errorMsg), HAL_MAX_DELAY);
  97. continue; // Prompt for input again
  98. }
  99.  
  100. // If valid, set the time and date
  101. HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
  102. HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
  103.  
  104. const char *successMsg = "RTC set successfully\n\r";
  105. HAL_UART_Transmit(&huart1, (uint8_t *)successMsg, strlen(successMsg), HAL_MAX_DELAY);
  106. break; // Exit the loop since time and date are set successfully
  107. }
  108. }
  109.  
  110. void UART_ReceiveString(char *buffer, uint16_t bufferSize) {
  111. uint16_t i = 0;
  112. char received;
  113.  
  114. // Receive each character until '\r' is received or buffer is full
  115. while (i < bufferSize - 1) {
  116. HAL_UART_Receive(&huart1, (uint8_t *)&received, 1, HAL_MAX_DELAY);
  117. if (received == '\r') { // End of input
  118. break;
  119. }
  120. buffer[i++] = received;
  121. }
  122. buffer[i] = '\0'; // Null-terminate the string
  123. }

Main Loop and Display Update

In this final section, the main function initializes the STM32 system, including GPIO, SPI, RTC, and UART peripherals, as well as the ST7735 TFT display. We set the display’s orientation, fill the screen with a background color, and draw borders. In the infinite loop, we fetch the current time and date from the RTC, format it into strings, and display it on the TFT screen. We also send the same information via UART for debugging or logging. The display updates every second, providing a continuous real-time clock interface.

  1. int main(void)
  2. {
  3. /* MCU Configuration--------------------------------------------------------*/
  4.  
  5. /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  6. HAL_Init();
  7.  
  8. /* Configure the system clock */
  9. SystemClock_Config();
  10.  
  11. /* Initialize all configured peripherals */
  12. MX_GPIO_Init();
  13. MX_SPI1_Init();
  14. MX_RTC_Init();
  15. MX_USART1_UART_Init();
  16.  
  17. /* USER CODE BEGIN 2 */
  18. ST7735_Init(); // Initialize the TFT display
  19. ST7735_Backlight_On(); // Turn on the display backlight
  20.  
  21. // Set the display orientation
  22. uint8_t rotation = 1; // 1 for portrait or 0 for landscape, based on preference
  23. ST7735_SetRotation(rotation);
  24.  
  25. // Fill the screen with a background color
  26. ST7735_FillRoundRect(1, 1, 160, 128, 2, ST7735_BLUE); // Blue background
  27.  
  28. // Draw screen borders
  29. for (int x = 0; x < ST7735_GetWidth(); x++) {
  30. ST7735_DrawPixel(x, 0, ST7735_WHITE); // Top border
  31. ST7735_DrawPixel(x, ST7735_GetHeight() - 1, ST7735_WHITE); // Bottom border
  32. }
  33. for (int y = 0; y < ST7735_GetHeight(); y++) {
  34. ST7735_DrawPixel(0, y, ST7735_WHITE); // Left border
  35. ST7735_DrawPixel(ST7735_GetWidth() - 1, y, ST7735_WHITE); // Right border
  36. }
  37.  
  38. /* USER CODE END 2 */
  39.  
  40. /* Infinite loop */
  41. while (1)
  42. {
  43. // Get the current time and date from the RTC
  44. HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
  45. HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
  46.  
  47. // Format the time and date into strings for display
  48. snprintf(time, sizeof(time), "Time: %02d:%02d:%02d", sTime.Hours, sTime.Minutes, sTime.Seconds);
  49. snprintf(date, sizeof(date), "Date: %02d/%02d/20%02d", sDate.Date, sDate.Month, sDate.Year);
  50.  
  51. // Clear previous text (black background) and display the updated time and date
  52. ST7735_WriteString(5, 5, time, Font_7x10, ST7735_WHITE, ST7735_BLACK); // Display time
  53. ST7735_WriteString(5, 20, date, Font_7x10, ST7735_WHITE, ST7735_BLACK); // Display date
  54.  
  55. // Send the time and date over UART for logging/debugging
  56. snprintf(uart_buf, sizeof(uart_buf), "%02d:%02d:%02d - %02d/%02d/20%02d\n\r",
  57. sTime.Hours, sTime.Minutes, sTime.Seconds, sDate.Date, sDate.Month, sDate.Year);
  58. HAL_UART_Transmit(&huart1, (uint8_t*)uart_buf, strlen(uart_buf), HAL_MAX_DELAY);
  59.  
  60. // Delay for 1 second before updating again
  61. HAL_Delay(1000);
  62. }
  63. }

Proteus Configuration :

  • Open Proteus & Create New Project and click next
  • Click on Pick Device
  • Search for STM32F103C8 & ST7735R 
  • Click on Virtual Instruments Mode then choose Terminal 
  • Click on Terminal Mode then choose (DYNAMIC & POWER &GROUND)
  • finally make the circuit below and start the simulation
Circuit design for Real-Time Clock (RTC) with STM32 in Proteus simulation

That’s all!

If you have any questions or suggestions don’t hesitate to leave a comment below.

You Might Also Like

2 comments

rania December 12, 2024 - 6:18 pm

thank you for the tuto , but i can’t find the libraries ?

Reply
Marwen Maghrebi December 13, 2024 - 12:27 pm

You’re welcome! 😊 You can find the required libraries in the project titled
“STM32 ST7735 TFT Display SPI: A Complete Guide to Interfacing.”
https://theembeddedthings.com/stmp32/interfacing-stm32-with-st7735-display-using-spi/
Feel free to check it out, and let me know if you have any other questions!

Reply

Leave a Comment


Are you sure want to unlock this post?
Unlock left : 0
Are you sure want to cancel subscription?
-
00:00
00:00
    -
    00:00
    00:00