32HAL——RTC时钟

一、RTC简介

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

二、 原理和特点

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

三、cubeMx相关配置

  1. 开启外部晶振

低速时钟选用外部晶振

  1. 配置RTC时钟频率

选用外部低速晶振(LSE)

  1. 激活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

相关推荐
知识分享小能手3 小时前
jQuery 入门学习教程,从入门到精通, jQuery在HTML5中的应用(16)
前端·javascript·学习·ui·jquery·html5·1024程序员节
吃个糖糖3 小时前
Pytorch 学习之Transforms
人工智能·pytorch·学习
常常不爱学习3 小时前
Vue3 + TypeScript学习
开发语言·css·学习·typescript·html
CandyU24 小时前
UE5 C++ 进阶学习 小知识点 —— 01 - 本地化语言
学习·ue5
武陵悭臾5 小时前
Python应用开发学习: Pygame 中实现数字水平靠右对齐和垂直靠底对齐
python·学习·程序人生·游戏·个人开发·学习方法·pygame
Tonya435 小时前
测开学习DAY26
学习
偶像你挑的噻5 小时前
Linux应用开发-17-套接字
linux·网络·stm32·嵌入式硬件
水月wwww5 小时前
vue学习之组件与标签
前端·javascript·vue.js·学习·vue
952365 小时前
数据结构-链表
java·数据结构·学习