RTC 简介

RTC(Real Time Clock),即实时时钟,类似于钟表一般,能够持续记录时间,为程序提供精确的日期和时间信息,即使在断电期间也能确保准确运行。

原理和特点

  • 在STM32中,存在两个时钟源:高速时钟(8 MHz)和低速时钟(32.768 kHz)。高速时钟用于驱动CPU、外设和定时器等核心组件,而低速时钟则负责管理看门狗和RTC等功能。
  • RTC依赖低速时钟运行。
  • RTC模块内部包含了一个独立的32位寄存器来保存当前的时间戳信息。
  • 低速时钟以极低的功耗运行,即使在断电情况下,通过备用电源(如纽扣电池),RTC也能持续运行以确保时间准确性。

RTC的一般使用方法

  • 在CubeMX中找到Timers -> RTC,勾选Activate Clock Source,即可激活RTC时钟功能。
  • 即使学习板断电,RTC依然能够持续记录时间。
  • HAL库的RTC驱动未实现日期的断电走时功能,即断电后时间可以继续走时,但日期会重置。
  • keysking提供了RTC库,可以实现断电走时功能,具体代码见下文。
  • 需要获取当前日期和时间时,只需调用相应函数即可实现。

RTC实时时钟实现

工程配置

  • **开启外部晶振:**在Pinout&Configuration -> System Core -> RCC 页面,将 High Speed Clock (HSE) 以及 Low Speed Clock (LSE) 都配置为 Crystal/Ceramic Resonator

配置时钟源

  • **配置主时钟频率:**在Clock Configuration 页面,将PLL Source 选择为 HSE,将System Clock Mux 选择为 PLLCLK,然后在HCLK (MHz) 输入72并回车,将HCLK频率配置为 72 MHz

时钟配置

  • **配置RTC时钟频率:**在Clock Configuration 页面,将RTC时钟源选择为 LSE

配置RTC时钟源

  • **激活RTC:**在Pinout&Configuration -> Timers -> RTC -> Mode,勾选 Activate Clock Source、Activate Calendar,以启用RTC时钟并激活日历功能。仅开启RTC时钟将仅记录时间,而不包括日期信息。

RTC配置

  • **打开串口2外设:**Pinout&Configuration -> Connectivity -> USART2,将Mode选择为Asynchronous

代码

在工程的Core/Inc文件夹上右键,选择New -> File,创建kk_rtc.h文件,将以下代码粘贴到kk_rtc.h文件中

      #ifndef INC_KK_RTC_H_
#define INC_KK_RTC_H_
#include "stm32f1xx_hal.h"
#include "rtc.h"
#include "time.h"

HAL_StatusTypeDef KK_RTC_SetTime(struct tm *time);
struct tm *KK_RTC_GetTime();
void KK_RTC_Init();

#endif /* INC_KK_RTC_H_ */
    

在工程的Core/Src文件夹上右键,选择New -> File,创建kk_rtc.c文件,将以下代码粘贴到kk_rtc.c文件中

      #include "kk_rtc.h"

// RTC已经被初始化的值 记录在RTC_BKP_DR1中
#define RTC_INIT_FLAG 0x2333

/**
 * @brief  进入RTC初始化模式
 * @param  hrtc  指向包含RTC配置信息的RTC_HandleTypeDef结构体的指针
 * @retval HAL status
 */
static HAL_StatusTypeDef RTC_EnterInitMode(RTC_HandleTypeDef *hrtc)
{
  uint32_t tickstart = 0U;

  tickstart = HAL_GetTick();
  /* 等待RTC处于INIT状态,如果到达Time out 则退出 */
  while ((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET)
  {
    if ((HAL_GetTick() - tickstart) >  RTC_TIMEOUT_VALUE)
    {
      return HAL_TIMEOUT;
    }
  }

  /* 禁用RTC寄存器的写保护 */
  __HAL_RTC_WRITEPROTECTION_DISABLE(hrtc);


  return HAL_OK;
}

/**
 * @brief  退出RTC初始化模式
 * @param  hrtc   指向包含RTC配置信息的RTC_HandleTypeDef结构体的指针
 * @retval HAL status
 */
static HAL_StatusTypeDef RTC_ExitInitMode(RTC_HandleTypeDef *hrtc)
{
  uint32_t tickstart = 0U;

  /* 禁用RTC寄存器的写保护。 */
  __HAL_RTC_WRITEPROTECTION_ENABLE(hrtc);

  tickstart = HAL_GetTick();
  /* 等到RTC处于INIT状态,如果到达Time out 则退出 */
  while ((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET)
  {
    if ((HAL_GetTick() - tickstart) >  RTC_TIMEOUT_VALUE)
    {
      return HAL_TIMEOUT;
    }
  }

  return HAL_OK;
}

/**
 * @brief  写入RTC_CNT寄存器中的时间计数器。
 * @param  hrtc   指向包含RTC配置信息的RTC_HandleTypeDef结构体的指针。
 * @param  TimeCounter: 写入RTC_CNT寄存器的计数器
 * @retval HAL status
 */
static HAL_StatusTypeDef RTC_WriteTimeCounter(RTC_HandleTypeDef *hrtc, uint32_t TimeCounter)
{
  HAL_StatusTypeDef status = HAL_OK;

  /* 进入RTC初始化模式 */
  if (RTC_EnterInitMode(hrtc) != HAL_OK)
  {
    status = HAL_ERROR;
  }
  else
  {
    /* 设置RTC计数器高位寄存器 */
    WRITE_REG(hrtc->Instance->CNTH, (TimeCounter >> 16U));
    /* 设置RTC计数器低位寄存器 */
    WRITE_REG(hrtc->Instance->CNTL, (TimeCounter & RTC_CNTL_RTC_CNT));

    /* 退出RTC初始化模式 */
    if (RTC_ExitInitMode(hrtc) != HAL_OK)
    {
      status = HAL_ERROR;
    }
  }

  return status;
}


/**
 * @brief  读取RTC_CNT寄存器中的时间计数器。
 * @param  hrtc   指向包含RTC配置信息的RTC_HandleTypeDef结构体的指针。
 * @retval 时间计数器
 */
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)
  {
    /* 当读取CNTL和CNTH寄存器期间计数器溢出时, 重新读取CNTL寄存器然后返回计数器值 */
    timecounter = (((uint32_t) high2 << 16U) | READ_REG(hrtc->Instance->CNTL & RTC_CNTL_RTC_CNT));
  }
  else
  {
    /* 当读取CNTL和CNTH寄存器期间没有计数器溢出, 计数器值等于第一次读取的CNTL和CNTH值 */
    timecounter = (((uint32_t) high1 << 16U) | low);
  }

  return timecounter;
}

/**
 * @brief 设置RTC时间
 * @param time 时间
 * @retval HAL status
 */
HAL_StatusTypeDef KK_RTC_SetTime(struct tm *time){
    uint32_t unixTime = mktime(time);
    return RTC_WriteTimeCounter(&hrtc, unixTime);
}

/**
 * @brief 获取RTC时间
 * @retval 时间
 */
struct tm *KK_RTC_GetTime() {
  time_t unixTime = RTC_ReadTimeCounter(&hrtc);
  return gmtime(&unixTime);
}

void KK_RTC_Init(){
    uint32_t initFlag = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
    if(initFlag == RTC_INIT_FLAG) return;
    if (HAL_RTC_Init(&hrtc) != HAL_OK){
        Error_Handler();
    }
    struct tm time = {
          .tm_year = 2025 - 1900,
          .tm_mon = 1 - 1,
          .tm_mday = 1,
          .tm_hour = 23,
          .tm_min = 59,
          .tm_sec = 55,
    };
    KK_RTC_SetTime(&time);
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, RTC_INIT_FLAG);

}
    

找到MX_RTC_Init的定义,在此文件中引用#include "kk_rtc.h",并在MX_RTC_Init函数的USER CODE RTC_Init 0注释对中调用KK_RTC_Init()函数 并且通过return 绕过MX_RTC_Init函数后面生成的代码

          hrtc.Instance = RTC;
    hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
    hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
    KK_RTC_Init();
    return;
    

在main函数的while循环中获取并通过串口输出当前时间

        now = KK_RTC_GetTime();
  sprintf(message, "%d-%d-%d %02d:%02d:%02d", now->tm_year + 1900,now->tm_mon + 1,now->tm_mday,now->tm_hour,now->tm_min,now->tm_sec);
  HAL_UART_Transmit(&huart2, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
  HAL_Delay(1000);
    

RTC主代码

声明

作者: liyao

版权:本博客所有文章除特别声明外,均采用CCBY-NC-SA4.O许可协议。转载请注明!

最后更新于 2026-02-18 18:15 history