一、RTC简介
RTC(Real Time Clock),即实时时钟,类似于钟表一般,能够持续记录时间,为程序提供精确的日期和时间信息,即使在断电期间也能确保准确运行。
二、 原理和特点
- 在STM32中,存在两个时钟源:高速时钟(8 MHz)和低速时钟(32.768 kHz)。高速时钟用于驱动CPU、外设和定时器等核心组件,而低速时钟则负责管理看门狗和RTC等功能。
- RTC依赖低速时钟运行。
- RTC模块内部包含了一个独立的32位寄存器来保存当前的时间戳信息。
- 低速时钟以极低的功耗运行,即使在断电情况下,通过备用电源(如纽扣电池),RTC也能持续运行以确保时间准确性。
三、cubeMx相关配置
- 开启外部晶振
低速时钟选用外部晶振

- 配置RTC时钟频率
选用外部低速晶振(LSE)

- 激活RTC

四、重写RTC库
myRTC.c
#include "myRTC.h" /** * @brief Enters the RTC Initialization mode. * @param hrtc pointer to a RTC_HandleTypeDef structure that contains * the configuration information for RTC. * @retval HAL status */ static HAL_StatusTypeDef RTC_EnterInitMode(RTC_HandleTypeDef *hrtc) { uint32_t tickstart = 0U; tickstart = HAL_GetTick(); /* Wait till RTC is in INIT state and if Time out is reached exit */ while ((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET) { if ((HAL_GetTick() - tickstart) > RTC_TIMEOUT_VALUE) { return HAL_TIMEOUT; } } /* Disable the write protection for RTC registers */ __HAL_RTC_WRITEPROTECTION_DISABLE(hrtc); return HAL_OK; } /** * @brief Exit the RTC Initialization mode. * @param hrtc pointer to a RTC_HandleTypeDef structure that contains * the configuration information for RTC. * @retval HAL status */ static HAL_StatusTypeDef RTC_ExitInitMode(RTC_HandleTypeDef *hrtc) { uint32_t tickstart = 0U; /* Disable the write protection for RTC registers */ __HAL_RTC_WRITEPROTECTION_ENABLE(hrtc); tickstart = HAL_GetTick(); /* Wait till RTC is in INIT state and if Time out is reached exit */ while ((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET) { if ((HAL_GetTick() - tickstart) > RTC_TIMEOUT_VALUE) { return HAL_TIMEOUT; } } return HAL_OK; } /** * @brief Read the time counter available in RTC_CNT registers. * @param hrtc pointer to a RTC_HandleTypeDef structure that contains * the configuration information for RTC. * @retval Time counter */ static uint32_t RTC_ReadTimeCounter(RTC_HandleTypeDef *hrtc) { uint16_t high1 = 0U, high2 = 0U, low = 0U; uint32_t timecounter = 0U; high1 = READ_REG(hrtc->Instance->CNTH & RTC_CNTH_RTC_CNT); low = READ_REG(hrtc->Instance->CNTL & RTC_CNTL_RTC_CNT); high2 = READ_REG(hrtc->Instance->CNTH & RTC_CNTH_RTC_CNT); if (high1 != high2) { /* In this case the counter roll over during reading of CNTL and CNTH registers, read again CNTL register then return the counter value */ timecounter = (((uint32_t) high2 << 16U) | READ_REG(hrtc->Instance->CNTL & RTC_CNTL_RTC_CNT)); } else { /* No counter roll over during reading of CNTL and CNTH registers, counter value is equal to first value of CNTL and CNTH */ timecounter = (((uint32_t) high1 << 16U) | low); } return timecounter; } /** * @brief Write the time counter in RTC_CNT registers. * @param hrtc pointer to a RTC_HandleTypeDef structure that contains * the configuration information for RTC. * @param TimeCounter: Counter to write in RTC_CNT registers * @retval HAL status */ static HAL_StatusTypeDef RTC_WriteTimeCounter(RTC_HandleTypeDef *hrtc, uint32_t TimeCounter) { HAL_StatusTypeDef status = HAL_OK; /* Set Initialization mode */ if (RTC_EnterInitMode(hrtc) != HAL_OK) { status = HAL_ERROR; } else { /* Set RTC COUNTER MSB word */ WRITE_REG(hrtc->Instance->CNTH, (TimeCounter >> 16U)); /* Set RTC COUNTER LSB word */ WRITE_REG(hrtc->Instance->CNTL, (TimeCounter & RTC_CNTL_RTC_CNT)); /* Wait for synchro */ if (RTC_ExitInitMode(hrtc) != HAL_OK) { status = HAL_ERROR; } } return status; } //设置RTC时间 HAL_StatusTypeDef myRTC_SetTime(struct tm *time) { uint32_t unixTime = mktime(time); // 将实际时间转换为时间戳 return RTC_WriteTimeCounter(&hrtc, unixTime); } //获取RTC时间 struct tm *myRTC_GetTime(void) { time_t unixTime = RTC_ReadTimeCounter(&hrtc); //将时间戳转换为实际时间 return gmtime(&unixTime); } void myRTC_Init(void) { // 先获取初始化标志位 uint32_t initFlag = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1); // 如果初始化标志位是我们设定的值,说明已经初始化过一次了,则直接跳出初始化 if (initFlag == RTC_INIT_FLAG) return; // cubeMx生成的RTC初始化代码 if (HAL_RTC_Init(&hrtc) != HAL_OK) { Error_Handler(); } // 如果第一次初始化则给时间赋初始值, 这里使用结构体, 跟time.h一样 struct tm time = { .tm_year = 2025 - 1900, // 注意这里的年是当前年与1900的差值 .tm_mon = 11 - 1, // 这里的月是0-11,所以实际月份要减1 .tm_mday = 7, .tm_hour = 16, .tm_min = 53, .tm_sec = 55, }; //将时间存入RTC myRTC_SetTime(&time); //运行到这里说明是第一次初始化,所以要把设定好的初始化标志位存进BKP, 防止下次时间被重新赋值 HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, RTC_INIT_FLAG); }
myRTC.h
// // Created by Mood on 2025/11/7. // #ifndef RTC_DEMO_MYRTC_H #define RTC_DEMO_MYRTC_H #include "stm32f1xx_hal.h" #include "rtc.h" #include <time.h> #define RTC_INIT_FLAG 0x2333 HAL_StatusTypeDef myRTC_SetTime(struct tm *time); struct tm *myRTC_GetTime(void); void myRTC_Init(void); #endif //RTC_DEMO_MYRTC_H
注:每次复位时代码均要进行一次初始化,cubeMx生成的RTC初始化代码中有一部分每次都会消耗一定时间,导致实时时间会偏慢,所以我们只在第一次初始化,后面复位均跳过此初始化。
void MX_RTC_Init(void)
{
/* USER CODE BEGIN RTC_Init 0 */
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
myRTC_Init();
return;
/* USER CODE END RTC_Init 0 */
/* USER CODE BEGIN RTC_Init 1 */
/* USER CODE END RTC_Init 1 */
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
// 该初始化代码,每次复位时均会消耗一段时间,导致时间偏慢,所以我们在自己的初始化函数中,将其设置为只有第一次初始化才会调用此函数,后面每次复位
// 均会跳过
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN RTC_Init 2 */
/* USER CODE END RTC_Init 2 */
}
五、示例程序
该代码实现了实时时钟,并且具有掉电不丢失功能,当然初始时间需自己在myRTC_Init()函数中设置。
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "rtc.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "oled.h"
#include <stdio.h>
#include "myRTC.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C2_Init();
MX_RTC_Init();
/* USER CODE BEGIN 2 */
HAL_Delay(20);
OLED_Init();
char message[50];
struct tm *now;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
OLED_NewFrame();
now = myRTC_GetTime();
sprintf(message, "%d-%d-%d %d:%d:%d", now->tm_year, now->tm_mon, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec);
OLED_PrintASCIIString(0, 0, message, &afont12x6, OLED_COLOR_NORMAL);
OLED_ShowFrame();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
注:上述相关资料均来自ksysking