第十五章RTC实验
本章将介绍Kendryte K210的RTC外设使用。通过本章的学习,读者将学习到SDK编程技术使用Kendryte K210的RTC模块。
本章分为如下几个小节:
15.1 RTC介绍
15.2 硬件设计
15.3 程序设计
15.4 运行验证
15.1 RTC介绍
Kendryte K210的RTC是用来计时的单元,在设置时间后具备计时功能:
1. 可使用外部高频晶振进行计时
2. 可配置外部晶振频率与分频
3. 支持万年历配置,可配置的项目包含世纪、年、月、日、时、分、秒与星期
4. 可按秒进行计时,并查询当前时刻
5. 支持设置一组闹钟,可配置的项目包含年、月、日、时、分、秒,闹钟到达时触发中断
6. 中断可配置,支持每日、每时、每分、每秒触发中断
7. 可读出小于1秒的计数器计数值,最小刻度单位为外部晶振的单个周期
8. 上电/复位后数据清零
实时时钟(RTC)能为系统提供一个准确的时间,Kendryte K210的RTC模块功能强大,支持万年历配置、闹钟、时间中断,能够适用于多种场景。通过本章的学习,读者将学习到RTC的使用。
Kendryte K210官方SDK提供了多个操作RTC模块的函数,这里我们只讲述本实验用到的函数,这些函数介绍如下:
1, rtc_init函数
该函数主要用于RTC初始化,如下代码所示:
int rtc_init(void)
{
/* Reset RTC */
sysctl_reset(SYSCTL_RESET_RTC);
/* Enable RTC */
sysctl_clock_enable(SYSCTL_CLOCK_RTC);
/* Unprotect RTC */
rtc_protect_set(0);
/* Set RTC clock frequency */
rtc_timer_set_clock_frequency(
sysctl_clock_get_freq(SYSCTL_CLOCK_IN0));
rtc_timer_set_clock_count_value(1);
/* Set RTC mode to timer running mode */
rtc_timer_set_mode(RTC_TIMER_RUNNING);
return 0;
}
首先我们要重新复位RTC,然后使能系统RTC时钟,撤销RTC的保护,让RTC能重新配置,为了保证RTC计时准确,我们需要根据系统时钟重新配置RTC时钟,最后配置RTC进入计时模式,RTC配置成功后该函数返回0,反之返回1。这里需要注意的是:RTC模块需要使能PLL0,并且CPU频率要大于30MHz。
2,rtc_timer_set函数
该函数用于设置RTC的日期和时间,如下代码所示:
int rtc_timer_set(int year, int month, int day, int hour, int minute, int second);
Kendryte K210的RTC模块在上电/复位后数据会清零,所以我们每次启动RTC后要重新设置日期、时间数据,函数共有6个参数,参数分别对应年、月、日、小时、分钟和秒,设置成功函数返回0。
3,rtc_timer_get函数
该函数用来读取当前RTC的日期、时间数据,该函数原型及参数描述如下所示:
int rtc_timer_get(int *year, int *month, int *day, int *hour, int *minute, int *second);
函数共有6个参数,参数分别对应年、月、日、小时、分钟和秒,读取成功函数返回0,返回其他表示读取失败。
15.2 硬件设计
15.2.1 例程功能
1.程序编译时将当前系统的日期、时间设置为RTC的日期、时间,之后每秒打印输出RTC的时间数据。
15.2.2 硬件资源
1.UARTHS(ISP)
UARTHS_TX – IO5
UARTHS_RX – IO4
15.2.3 原理图
本章实验内容,主要讲解RTC模块的使用,无需关注原理图。
15.3 程序设计
15.3.1 RTC获取系统时间驱动代码
RTC获取系统时间源码包括两个文件:rtcdate.c和rtcdate.h, rtcdate.h文件只包含了函数的声明,我们这里介绍下函数参数的结构体rtc_date_time_t。
typedef struct _rtc_date_time
{
uint32_t sec : 6;
uint32_t min : 6;
uint32_t hour : 5;
uint32_t week : 3;
uint32_t day : 5;
uint32_t month : 4;
uint16_t year;
} rtc_date_time_t;
可以看到,这个结构体是用于存放RTC的时间和日期的,我们设置RTC时间的时候可以直接使用该结构体的参数,下面的们介绍rtcdate.c文件的内容。
void get_compile_time(rtc_date_time_t *compile_time)
{
const char date[12] = {__DATE__};
const char time[9] = {__TIME__};
uint8_t length;
const char *ptr;
char buffer[100];
uint8_t month_index;
const char *month_list[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
if (compile_time == NULL)
{
return;
}
/* Set to default */
compile_time->sec = 0;
compile_time->min = 0;
compile_time->hour = 0;
compile_time->week = 6;
compile_time->day = 1;
compile_time->month = 1;
compile_time->year = 2000;
/* Month */
ptr = find_field(date, ' ', 0);
length = (uint8_t)(strchr(ptr, ' ') - ptr);
strncpy(buffer, ptr, length);
buffer[length] = '\0';
for (month_index=0; month_indexmonth = month_index + 1;
}
}
/* Day */
ptr = find_field(date, ' ', 1);
length = (uint8_t)(strchr(ptr, ' ') - ptr);
strncpy(buffer, ptr, length);
buffer[length] = '\0';
compile_time->day = atoi(buffer);
/* Year */
ptr = find_field(date, ' ', 2);
length = (uint8_t)(strchr(ptr, ' ') - ptr);
strncpy(buffer, ptr, length);
buffer[length] = '\0';
compile_time->year = atoi(buffer);
/* Hour */
ptr = find_field(time, ':', 0);
length = (uint8_t)(strchr(ptr, ':') - ptr);
strncpy(buffer, ptr, length);
buffer[length] = '\0';
compile_time->hour = atoi(buffer);
/* Minute */
ptr = find_field(time, ':', 1);
length = (uint8_t)(strchr(ptr, ':') - ptr);
strncpy(buffer, ptr, length);
buffer[length] = '\0';
compile_time->min = atoi(buffer);
/* Second */
ptr = find_field(time, ':', 2);
length = (uint8_t)(strchr(ptr, '\0') - ptr);
strncpy(buffer, ptr, length);
buffer[length] = '\0';
compile_time->sec = atoi(buffer);
/* Week */
compile_time->week = rtc_get_wday(compile_time->year, compile_time->month, compile_time->day);
}
首先我们介绍get_compile_time函数,函数主要功能就是获取系统时间,通过代码__DATE__和__TIME__就能读取到系统的日期和时间,这两个数据是以字符串的形式保存在数组中,我们不能直接使用,这时候就需要数据提取处理,接着我们对RTC的日期和时间结构体变量赋予一个默认值,完成后我们就对上面提取到的日期和时间数据处理了,在VSCode我们可以看到__DATE__和__TIME__这两个数据的宏定义分别为#define __DATE__ "Feb 21 2024"和#define __TIME__ "09:49:35",我们就可以根据这两个字符串的格式提取所需数据,这里我们还需要用到另外一个函数,函数的原型如下:
/**
* @brief 查找字符串中第n个分隔符后的地址
* @param string :需要查找的字符串
* @param interval :分隔符
* @param index :第n个分割符
* @retval 返回第n个分隔符后的地址
*/
static const char *find_field(const char *string, const char interval, uint8_t index)
{
uint8_t index_loop = 0;
const char *field = string;
while (index_loop < index)
{
while (field[0] != interval)
{
field++;
}
while (field[0] == interval)
{
field++; /* 偏移到分隔符地址后 */
}
index_loop++;
}
return field;
}
这个函数是根据我们日期和时间的数据格式编写的,比如日期数据中两个数据之间是以空格符号分隔,时间是以‘:’符号分隔,那我们就可以以这两个分隔符提取数据处理了,这里我们通过三个参数便可读取到字符串中第n个分隔符后的数据地址了,如日期第一个数据是月份,后面是空格符号,通过find_field(date, ' ', 0)这段代码是直接读取到日期数据的第一个字符的首地址(第三个参数为0地址未偏移直接跳出),接着我们用strchr函数读取下个空格符的位置,就能够确定月份这个数据的所占的字节数,进而提取到到月份的字符串数据,我们可以再稍微处理下再保存到RTC存储日期、时间数据的结构体变量中,其他几个数据读取方法都相似,这里不再叙述。
15.3.2 main.c代码
main.c中的代码如下所示:
int main(void)
{
rtc_date_time_t time;
int year;
int month;
int day;
int hour;
int minute;
int second;
int second_prev = 0;
sysctl_pll_set_freq(SYSCTL_PLL0, 800000000); /* RTC模块需要开启PLL0,并且CPU频率要大于30MHz */
get_compile_time(&time); /* 获取系统时间 */
rtc_init();
rtc_timer_set(time.year, time.month, time.day, time.hour, time.min, time.sec);
while (1)
{
msleep(100);
rtc_timer_get(&year, &month, &day, &hour, &minute, &second);
if (second_prev != second)
{
second_prev = second;
printf("%4d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second);
}
}
}
我们先定义6个日期、时间数据变量,然后开启PLL0时钟。RTC模块要求开启PLL0时钟,并且CPU频率大于30MHz,通过get_compile_time函数读取系统时钟存放在结构体变量中,然后初始化RTC,设置RTC时间。
最后在一个循环中每隔100毫秒读取一次RTC的实时时间,当秒钟变量second发生变化时,就通过串口打印输出RTC的日期、时间数据。
15.4 运行验证
将DNK210开发板连接到电脑主机,通过VSCode将固件烧录到开发板中,此时,我们打开“串口终端”查看输出的数据,如下图所示:。

图15.4.1 串口终端输出时间