编程随笔 编程随笔
  • 前端
  • 后端
  • 嵌入式
  • 星球项目
  • 开源项目
  • 海康AGV
  • 四向车
  • 工具类
  • 项目仓库

    • 部署仓库 (opens new window)
    • 代码仓库 (opens new window)
  • vuepress插件

    • 自动生成导航栏与侧边栏 (opens new window)
    • 评论系统 (opens new window)
    • 全文搜索 (opens new window)
    • 选项卡 (opens new window)
    • 自动生成sitemap (opens new window)
  • 自主开发插件

    • 批量操作frontmatter (opens new window)
    • 链接美化 (opens new window)
    • 折叠代码块 (opens new window)
    • 复制代码块 (opens new window)

liyao52033

走运时,要想到倒霉,不要得意得过了头;倒霉时,要想到走运,不必垂头丧气。心态始终保持平衡,情绪始终保持稳定,此亦长寿之道
  • 前端
  • 后端
  • 嵌入式
  • 星球项目
  • 开源项目
  • 海康AGV
  • 四向车
  • 工具类
  • 项目仓库

    • 部署仓库 (opens new window)
    • 代码仓库 (opens new window)
  • vuepress插件

    • 自动生成导航栏与侧边栏 (opens new window)
    • 评论系统 (opens new window)
    • 全文搜索 (opens new window)
    • 选项卡 (opens new window)
    • 自动生成sitemap (opens new window)
  • 自主开发插件

    • 批量操作frontmatter (opens new window)
    • 链接美化 (opens new window)
    • 折叠代码块 (opens new window)
    • 复制代码块 (opens new window)
  • 基础知识

    • GPIO概述与配置
    • keil调试
    • RTC实时时钟
      • RTC 简介
      • RTC实时时钟实现
  • 嵌入式
  • 基础知识
华总
2025-02-12
0
0
目录

RTC实时时钟原创

# 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实时时钟实现

# 1、工程配置

  • **开启外部晶振:**在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

# 2、代码

在工程的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_ */
1
2
3
4
5
6
7
8
9
10
11

在工程的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);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

找到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;
1
2
3
4
5

在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);
1
2
3
4

RTC主代码

上次更新: 2025/02/18 14:46:10
keil调试

← keil调试

最近更新
01
jFlash使用 原创
03-24
02
中央仓库上传指南 原创
03-23
03
模板生成工具 原创
02-18
04
keil调试 原创
01-21
05
GPIO概述与配置 原创
01-20
更多文章>
Copyright © 2023-2025 liyao52033  All Rights Reserved
备案号:鄂ICP备2023023964号-1    
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式