二十八、RTC实时时钟实验
WARNING
📌 注意:高低配版都没有贴电池座,需要自行焊接电池座并加入电池才能做掉电实验。
RTC的配置流程
- 解锁备份域的写保护 。我们RTC的核心寄存器是在备份域里的,要对备份域进行操作需要解除写保护。所以上电复位后的第一步是解锁备份域的写保护。
- 设置时钟来源 。本案例的RTC时钟来源是使用外部低速时钟晶振32.768Khz提供的,所以我们在配置RTC的时钟时,需要选择开启外部低速时钟使能,选择它成为RTC的时钟来源。
- 开启RTC外设 。就是开启RTC的时钟,等待它配置完成。
- 配置日历时间 。包括年月日周时分秒。
1. 宏定义
c
// 核查码
#define BACKUP_DATA 0x77
// 备份寄存器地址范围: 96~127
#define RTC_BACKUP_REG_START (96U)
#define RTC_BACKUP_DATA_SIZE (32U)
// 备份寄存器读取的地址
#define RTC_BACKUP_CHECK_ADDR RTC_BACKUP_REG_START
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
BACKUP_DATA
: 定义了一个核查码,其数值为0x77
。核查码用于检查RTC是否已经进行过配置。RTC_BACKUP_REG_START
: 定义了备份寄存器的起始地址,其数值为96U
。RTC的备份寄存器通常用于存储一些用户自定义的配置信息。RTC_BACKUP_DATA_SIZE
: 定义了备份寄存器的数据大小,其数值为32U
。这个值指示了备份寄存器可存储的数据的字节数。RTC_BACKUP_CHECK_ADDR
: 定义了用于检查核查码的备份寄存器地址,其值与RTC_BACKUP_REG_START
相同,即96U
。这个地址用于存储核查码,以便在初始化RTC时检查是否已经进行过配置。
2. 解除写保护
RTC的核心寄存器是在备份域里的,而备份域是归属于PWC电源管理时钟的控制下的。
c
// 关闭所有外设的写保护
LL_PERIPH_WE(LL_PERIPH_ALL);
// 检查备份寄存器中的配置是否已存在
uint8_t ret = rtc_check_backupReg();
if(ret == 0)
{
// 如果已经存在配置,则直接返回
return;
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 使用LL_PERIPH_WE(LL_PERIPH_ALL)关闭了所有外设的写保护,这允许对外设进行配置和写入操作。
- 调用rtc_check_backupReg()函数检查备份寄存器中的配置是否已存在。该函数的返回值存储在变量ret中。
- 如果ret等于0,表示已经存在配置,则直接返回,不再执行后续的RTC初始化过程。
c
> rtc_check_backupReg具体实现:
// 检查备份寄存器中的核查码
uint8_t rtc_check_backupReg(void)
{
uint8_t Check_Data = PWC_BKR_Read(RTC_BACKUP_CHECK_ADDR);
if(Check_Data == BACKUP_DATA)
{
printf("RTC 已经配置过!\r\n");
return 0;
}
return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
3. 设置时钟来源
RTC的时钟来源有两种,内部IRC32K低速时钟和外部低速时钟32.768KHz。
- 如果使用内部时钟,则在系统VDD断电的情况下,RTC的时钟也跟着停止,导致RTC无法跑时;
- 如果使用外部低速时钟,则可以满足断电跑时,并且功耗较低; 本案例的时钟来源选择使用外部低速时钟晶振32.768Khz提供,我们立创·梁山派天空星开发板上也板载了外部低速时钟32.768Khz(自己贴的)。
WARNING
📌 注意:青春版没有贴外部低速晶振,如果是青春版使用外部低速晶振会直接卡死,所以青春版需要更换时钟源。
c
/_ 停止 RTC _/
RTC_Cmd(DISABLE);
// 初始化 RTC 结构体
stc_rtc_init_t stcRtcInit;
(void)RTC_StructInit(&stcRtcInit);
// 配置 RTC 结构体
stcRtcInit.u8ClockSrc = RTC_CLK_SRC_LRC; // 时钟源为内部LRC
stcRtcInit.u8HourFormat = RTC_HOUR_FMT_24H; // 24 小时格式
stcRtcInit.u8IntPeriod = RTC_INT_PERIOD_PER_SEC; // 定时周期为每秒
(void)RTC_Init(&stcRtcInit);
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
配置之前要将RTC停止
- 使用RTC_Cmd(DISABLE)停止RTC,确保在重新初始化之前先停止RTC的运行。
- 声明了一个stc_rtc_init_t类型的结构体变量stcRtcInit,并使用RTC_StructInit(&stcRtcInit)对其进行初始化,将结构体的所有成员都设置为默认值。
- 对stcRtcInit结构体进行了配置,设置了RTC的各项参数:
- 时钟源选择为低速晶振(RTC_CLK_SRC_LRC)。
- 小时格式选择为24小时制(RTC_HOUR_FMT_24H)。
- 定时周期设置为每秒(RTC_INT_PERIOD_PER_SEC)。
- 使用RTC_Init(&stcRtcInit)对RTC进行初始化,将配置好的结构体参数应用到RTC中,使得RTC按照设定的参数进行工作。
4. 配置日历时间
结构体 stc_rtc_date_t和 stc_rtc_time_t 可以一步到位的配置RTC外设的日历时间,相关参数如下:
u8Year
: 指定RTC的年份,取值范围为0到99。u8Month
: 指定RTC的月份,使用十进制表示,取值范围为1到12。u8Day
: 指定RTC的日期,取值范围为1到31。u8Weekday
: 指定RTC的星期几,取值范围为0到6,分别代表星期日到星期六。
u8Hour
: 指定RTC的小时数。如果选择了12小时制(RTC_HOUR_FMT_12H),取值范围为1到12;如果选择了24小时制(RTC_HOUR_FMT_24H),取值范围为0到23。u8Minute
: 指定RTC的分钟数,取值范围为0到59。u8Second
: 指定RTC的秒数,取值范围为0到59。u8AmPm
: 指定RTC的上午/下午时间(仅在12小时制模式下有效)。该参数可以是 RTC_Hour12_AM或RTC_Hour12_PM ,表示上午或下午。
配置完成之后,通过调用 RTC_SetDate和 RTC_SetTime函数将配置的参数设置到RTC外设寄存器中。
c
// RTC 日期配置函数
void rtc_date_config(void)
{
stc_rtc_date_t stcRtcDate;
stc_rtc_time_t stcRtcTime;
// 往日期结构体内写入日期信息
stcRtcDate.u8Year = 24U; // 2024 年
stcRtcDate.u8Month = RTC_MONTH_APRIL; // 四月
stcRtcDate.u8Day = 11U; // 11 日
stcRtcDate.u8Weekday = RTC_WEEKDAY_THURSDAY; // 星期四
// 往时间结构体内写入时间信息
stcRtcTime.u8Hour = 21U; // 21 点
stcRtcTime.u8Minute = 28U; // 28 分
stcRtcTime.u8Second = 1U; // 1 秒
stcRtcTime.u8AmPm = RTC_HOUR_24H; // 24 小时格式
// 使用十进制格式更新日期
if(LL_OK != RTC_SetDate(RTC_DATA_FMT_DEC, &stcRtcDate))
{
DDL_Printf("设置日期失败!\r\n");
}
// 使用十进制格式更新时间
if(LL_OK != RTC_SetTime(RTC_DATA_FMT_DEC, &stcRtcTime))
{
DDL_Printf("设置时间失败!\r\n");
}
}
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
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
5. 开启RTC外设
c
/_ 启动 RTC 计数 _/
RTC_Cmd(ENABLE);
/_ 写入核查码 _/
rtc_write_backupReg();
1
2
3
4
5
2
3
4
5
做掉电实验,则要调用rtc_write_backupReg函数,将校验码写入指定地址,每次上电之后读取该地址内的数据,如果数据是指定的校验码,则说明已经配置过了,不需要在配置RTC了。
rtc_write_backupReg代码的具体实现是:
c
// 写入核查码到备份寄存器
void rtc_write_backupReg(void)
{
printf("开始写入核查码!\r\n");
PWC_BKR_Write(RTC_BACKUP_CHECK_ADDR, BACKUP_DATA);
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
6. 读取RTC时间
通过函数 RTC_GetTime 和 RTC_GetDate 获取RTC寄存器中的时间信息。
c
uint8_t WeekDay[][] = {"星期日","星期一","星期二","星期三","星期四","星期五","星期六","错误!"};
// 根据传入的星期值返回对应的星期字符串
uint8_t \*RTC_DisplayWeekday(uint8_t u8Weekday)
{
switch (u8Weekday)
{
case RTC_WEEKDAY_SUNDAY:
return &(WeekDay[][]);
case RTC_WEEKDAY_MONDAY:
return &(WeekDay[][]);
case RTC_WEEKDAY_TUESDAY:
return &(WeekDay[][]);
case RTC_WEEKDAY_WEDNESDAY:
return &(WeekDay[][]);
case RTC_WEEKDAY_THURSDAY:
return &(WeekDay[][]);
case RTC_WEEKDAY_FRIDAY:
return &(WeekDay[][]);
case RTC_WEEKDAY_SATURDAY:
return &(WeekDay[][]);
default:
return &(WeekDay[][]);
}
}
// 显示当前日期和时间
void RTC_ShowDateTime(void)
{
stc_rtc_date_t stcCurrentDate; // 日期结构体
stc_rtc_time_t stcCurrentTime; // 时间结构体
// 获取当前日期
RTC_GetDate(RTC_DATA_FMT_DEC, &stcCurrentDate);
// 获取当前时间
RTC_GetTime(RTC_DATA_FMT_DEC, &stcCurrentTime);
printf("20%02d年%02d月%02d日 【%02d:%02d:%02d】 %s\r\n\n",
stcCurrentDate.u8Year,
stcCurrentDate.u8Month,
stcCurrentDate.u8Day,
stcCurrentTime.u8Hour,
stcCurrentTime.u8Minute,
stcCurrentTime.u8Second,
RTC_DisplayWeekday(stcCurrentDate.u8Weekday));
}
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
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
7. RTC完整代码
rtc.c
c
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-04-11 LCKFB-LP first version
*/
#include "rtc.h"
#include "stdio.h"
uint8_t WeekDay[8][9] = {"星期日","星期一","星期二","星期三","星期四","星期五","星期六","错误!"};
// RTC 初始化函数
void rtc_init(void)
{
// 关闭所有外设的写保护
LL_PERIPH_WE(LL_PERIPH_ALL);
// 检查备份寄存器中的配置是否已存在
uint8_t ret = rtc_check_backupReg();
if(ret == 0)
{
// 如果已经存在配置,则直接返回
return;
}
// 重启 VBAT
PWC_VBAT_Reset();
// 尝试重置 RTC 计数器
if(LL_ERR_TIMEOUT == RTC_DeInit())
{
printf("重置 RTC 失败!\r\n");
return;
}
/* 停止 RTC */
RTC_Cmd(DISABLE);
// 初始化 RTC 结构体
stc_rtc_init_t stcRtcInit;
(void)RTC_StructInit(&stcRtcInit);
// 配置 RTC 结构体
stcRtcInit.u8ClockSrc = RTC_CLK_SRC_LRC; // 时钟源为内部LRC
stcRtcInit.u8HourFormat = RTC_HOUR_FMT_24H; // 24 小时格式
stcRtcInit.u8IntPeriod = RTC_INT_PERIOD_PER_SEC; // 定时周期为每秒
(void)RTC_Init(&stcRtcInit);
/* 更新日期和时间 */
rtc_date_config();
/* 启动 RTC 计数 */
RTC_Cmd(ENABLE);
/* 写入核查码 */
rtc_write_backupReg();
}
// RTC 日期配置函数
void rtc_date_config(void)
{
stc_rtc_date_t stcRtcDate;
stc_rtc_time_t stcRtcTime;
// 往日期结构体内写入日期信息
stcRtcDate.u8Year = 24U; // 2024 年
stcRtcDate.u8Month = RTC_MONTH_APRIL; // 四月
stcRtcDate.u8Day = 11U; // 11 日
stcRtcDate.u8Weekday = RTC_WEEKDAY_THURSDAY; // 星期四
// 往时间结构体内写入时间信息
stcRtcTime.u8Hour = 21U; // 21 点
stcRtcTime.u8Minute = 28U; // 28 分
stcRtcTime.u8Second = 1U; // 1 秒
stcRtcTime.u8AmPm = RTC_HOUR_24H; // 24 小时格式
// 使用十进制格式更新日期
if (LL_OK != RTC_SetDate(RTC_DATA_FMT_DEC, &stcRtcDate))
{
DDL_Printf("设置日期失败!\r\n");
}
// 使用十进制格式更新时间
if (LL_OK != RTC_SetTime(RTC_DATA_FMT_DEC, &stcRtcTime))
{
DDL_Printf("设置时间失败!\r\n");
}
}
// 写入核查码到备份寄存器
void rtc_write_backupReg(void)
{
printf("开始写入核查码!\r\n");
PWC_BKR_Write(RTC_BACKUP_CHECK_ADDR, BACKUP_DATA);
}
// 检查备份寄存器中的核查码
uint8_t rtc_check_backupReg(void)
{
uint8_t Check_Data = PWC_BKR_Read(RTC_BACKUP_CHECK_ADDR);
if(Check_Data == BACKUP_DATA)
{
printf("RTC 已经配置过!\r\n");
return 0;
}
return 1;
}
// 根据传入的星期值返回对应的星期字符串
uint8_t *RTC_DisplayWeekday(uint8_t u8Weekday)
{
switch (u8Weekday)
{
case RTC_WEEKDAY_SUNDAY:
return &(WeekDay[0][0]);
case RTC_WEEKDAY_MONDAY:
return &(WeekDay[1][0]);
case RTC_WEEKDAY_TUESDAY:
return &(WeekDay[2][0]);
case RTC_WEEKDAY_WEDNESDAY:
return &(WeekDay[3][0]);
case RTC_WEEKDAY_THURSDAY:
return &(WeekDay[4][0]);
case RTC_WEEKDAY_FRIDAY:
return &(WeekDay[5][0]);
case RTC_WEEKDAY_SATURDAY:
return &(WeekDay[6][0]);
default:
return &(WeekDay[7][0]);
}
}
// 显示当前日期和时间
void RTC_ShowDateTime(void)
{
stc_rtc_date_t stcCurrentDate; // 日期结构体
stc_rtc_time_t stcCurrentTime; // 时间结构体
// 获取当前日期
RTC_GetDate(RTC_DATA_FMT_DEC, &stcCurrentDate);
// 获取当前时间
RTC_GetTime(RTC_DATA_FMT_DEC, &stcCurrentTime);
printf("20%02d年%02d月%02d日 【%02d:%02d:%02d】 %s\r\n\n",
stcCurrentDate.u8Year,
stcCurrentDate.u8Month,
stcCurrentDate.u8Day,
stcCurrentTime.u8Hour,
stcCurrentTime.u8Minute,
stcCurrentTime.u8Second,
RTC_DisplayWeekday(stcCurrentDate.u8Weekday));
}
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
rtc.h
c
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-04-11 LCKFB-LP first version
*/
#ifndef __RTC_H__
#define __RTC_H__
#include "hc32_ll.h"
// 核查码
#define BACKUP_DATA 0x77
// 备份寄存器地址范围: 96~127
#define RTC_BACKUP_REG_START (96U)
#define RTC_BACKUP_DATA_SIZE (32U)
// 备份寄存器读取的地址
#define RTC_BACKUP_CHECK_ADDR RTC_BACKUP_REG_START
void rtc_init(void);
void rtc_date_config(void);
void rtc_write_backupReg(void);
uint8_t rtc_check_backupReg(void);
uint8_t *RTC_DisplayWeekday(uint8_t u8Weekday);
void RTC_ShowDateTime(void);
#endif
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
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
8. 实验验证
在main.c中编写如下:
c
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-04-11 LCKFB-LP first version
*/
#include "board.h"
#include "bsp_uart.h"
#include "stdio.h"
#include "rtc.h"
int32_t main(void)
{
board_init();
uart1_init(115200U);
rtc_init();
while(1)
{
RTC_ShowDateTime();
delay_ms(1000);
}
}
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
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
实验现象:
进行了12秒的断电实验,发现拔掉USB之后RTC依靠电池继续进行计时。
注意:进行掉电实验要在开发板背面焊接纽扣电池底座,然后选择合适的纽扣电池安装。
代码例程下载
📌 关于这一章节的代码,在 【HC32F4A0PITB版本】百度网盘链接入门手册资料下载->第03章软件资料
->代码例程
->015RTC时钟实验(可做掉电实验)
。