第十五章 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_index<sizeof(month_list)/sizeof(month_list[0]); month_index++) { if (strcmp(buffer, month_list[month_index]) == 0) { compile_time->month = 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",我们就可以根据这两个字符串的格式提取所需数据,这里我们还需要用到另外一个函数,函数的原型如下: /** * @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 串口终端输出时间
|