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:**在Pinout&Configuration -> Timers -> RTC -> Mode,勾选 Activate Clock Source、Activate Calendar,以启用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
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
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
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
2
3
4
上次更新: 2025/02/18 14:46:10
- 01
- 模板生成工具 原创02-18
- 02
- keil调试 原创01-21
- 03
- GPIO概述与配置 原创01-20
- 04
- element-plus多文件手动上传 原创11-03
- 05
- TrueLicense 创建及安装证书 原创10-25