目录
1. RTC 实时时钟的应用场景
2. RTC 的配置与初始化
2.1 设置 RTC 时钟源
2.2 初始化 RTC 寄存器
2.3 中断配置
2.4 备份寄存器配置
2.5 校准 RTC
3. 实例演示代码
4. 总结
1. RTC 实时时钟的应用场景
实时时钟(RTC)在嵌入式系统中具有广泛的应用场景,特别是对于需要准确跟踪时间的应用。下面是一些常见的 RTC 应用场景:
时钟显示: RTC 可用于驱动液晶显示屏上的时钟模块,以在设备上显示当前时间。
日历功能: RTC 可用于记录日期,包括年、月、日等信息,从而实现日历功能。
定时任务: 在需要周期性执行任务的应用中,RTC 可以作为触发定时任务执行的时钟源。
事件记录: RTC 可用于记录系统中的事件发生时间,例如记录故障发生的时间戳。
定时器功能: RTC 还可以用作定时器,实现定时触发操作,例如定时启动警报等。
电源管理: RTC 可以在设备休眠时运行,唤醒时提供时间信息,从而实现高效的电源管理。
日志记录: RTC 可以用于记录系统运行状态或用户操作记录的时间信息,以便后续分析。
2. RTC 的配置与初始化
配置和初始化 RTC 模块是使用实时时钟功能的关键步骤。下面是配置和初始化 RTC 的一般步骤:
2.1 设置 RTC 时钟源
RTC 的时钟源通常有两种选择:外部晶体振荡器(LSE)和内部振荡器(LSI)。外部晶体振荡器(通常是32.768kHz)提供更高的精度和稳定性,而内部振荡器则更为节省成本,可以在LSE失效时使用。
2.2 初始化 RTC 寄存器
RTC 模块通常包含多个寄存器,用于存储时间、日期和控制信息。在初始化过程中,需要设置这些寄存器的初始值,以确保 RTC 模块能够正确地工作。
2.3 中断配置
如果需要使用 RTC 中断功能,例如定时器中断或闹钟中断,还需要配置相应的中断使能位和中断优先级。这样,当 RTC 模块产生中断时,系统可以及时响应并处理中断事件。
2.4 备份寄存器配置
RTC 模块通常还包含一些备份寄存器(BKP),用于存储关键信息,例如设备状态、配置参数等。在初始化过程中,可以根据需要对这些备份寄存器进行配置,以实现数据的备份和恢复功能。
2.5 校准 RTC
通过下列RTC简图可知,后备区域的时钟与APB1总线的时钟是不相同的,为了确保 RTC 模块提供的时间信息准确可靠,需要对 RTC 进行周期性的校准。校准过程包括校正 RTC 时钟源的偏差,以及对 RTC 寄存器的时间值进行校准,以确保与外部时间标准的一致性。
3. 实例演示代码
本节我们将利用RTC实现一个时钟显示的功能,通过读取时间戳来获取当前时间(主供电断开依然可以由备用电池供电,故主供电断开依然可以记时),本程序可以通过动态传参的方法来配置当前的初始时间。
首先是等待RTC同步函数
void rtc_wait(void)
{
RTC_WaitForSynchro(); //等待同步
RTC_WaitForLastTask(); //等待上一次操作完成
}
接着是RTC的初始化
void rtc_Init(uint16_t year,uint16_t mouth,uint16_t day,uint16_t hour,uint16_t min,uint16_t sec)
{
time_t time_cnt;
struct tm time_date;
BKP_Init();//备份寄存器初始化,同时使能PWR的时钟
if (R_Save_data(BKP_DR10) != 0x2024) //判断是否是第一次设置备份寄存器
{
RCC_LSEConfig(RCC_LSE_ON); //LSE时钟,通常是32.768kHz
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET); //LSE准备就绪
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //配置LSE来源
RCC_RTCCLKCmd(ENABLE); //RTC时钟使能
rtc_wait();//等待同步(主时钟和低速时钟的同步)
RTC_SetPrescaler(32768 - 1); //把32.768kHz分频到1Hz
RTC_WaitForLastTask(); //等待上一次操作完成
time_date.tm_year = year - 1900; //time库中,年是从1900年算起的
time_date.tm_mon = mouth - 1; //time库中,月是从1月算起的
time_date.tm_mday = day;
time_date.tm_hour = hour;
time_date.tm_min = min;
time_date.tm_sec = sec;
time_cnt = mktime(&time_date) - 8 * 60 * 60; //将日期转换为秒计数器,并减8个时区(东8)
RTC_SetCounter(time_cnt); //更新到计数器
RTC_WaitForLastTask(); //等待上一次操作完成
BKP_WriteBackupRegister(BKP_DR10, 0x2024); //第一次设置完成,写入标志位
}
else rtc_wait();//第一次已经配置好了,后期主上电只需等待时钟同步
}
接着是RTC读取时间的函数
void Read_RTC(uint16_t *year,uint16_t *mouth,uint16_t *day,uint16_t *hour,uint16_t *min,uint16_t *sec)
{
time_t time_cnt;
struct tm time_date;
time_cnt = RTC_GetCounter() + 8 * 60 * 60; //获取当前的秒计数器
time_date = *localtime(&time_cnt); //将秒计数器转换为日期
*year = time_date.tm_year + 1900; //将日期时间结构体赋值给数组的时间
*mouth = time_date.tm_mon + 1;
*day = time_date.tm_mday;
*hour = time_date.tm_hour;
*min = time_date.tm_min;
*sec = time_date.tm_sec;
}
在这里我们要注意,需要在文件的开头包含time.h文件,因为在函数中使用到了该头文件中包含的结构体和其他有关时间戳的函数,通过该头文件我们可以避免自己编写闰年等等的程序逻辑
#include <time.h>
在获取函数之前,我们还需要定义全局变量,一定要全局变量哈,不要写在main函数或者其他功能函数中了
uint16_t year,mouth,day,hour,min,sec;
接着,我们只需要在初始化时将当前时间作为参数传入初始化函数,就可以在获取函数中通过全局变量实时获取到当前的时间了
rtc_Init(2024,2,19,4,27,55); //RTC初始化,当前时间2024年2月19日4点27分55秒
Read_RTC(&year,&mouth,&day,&hour,&min,&sec);//获取函数,将日期信息分别赋值到全局变量中
最后程序中如果出现函数未定义的,可能是在BKP篇中封装好的函数,下附BKP篇的.c文件
#include "bkp.h"
//备份寄存器初始化
void BKP_Init(void)
{
//备份寄存器通常需要在Vbat电压下工作(Vbat是用于保持备份寄存器内容的电源),而PWR模块负责监测Vbat电压
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //开启PWR的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); //开启BKP的时钟
PWR_BackupAccessCmd(ENABLE); //用PWR使能备份寄存器
}
/*在f103c8t6中,有20个字节的后备数据空间,对应10个16位的备份寄存器
BKP_DR对应BKP_DR1 - BKP_DR10
*/
uint16_t W_Save_data(uint16_t BKP_DR, uint16_t data)
{
BKP_WriteBackupRegister(BKP_DR, data); //将需要掉电保存的数据写入备份寄存器
if(data == BKP_ReadBackupRegister(BKP_DR)) //读取写入的备份寄存器的数据
{
return 1; //如果正确读取到备份寄存器的数据,返回1
}
else return 0;
}
uint16_t R_Save_data(uint16_t BKP_DR)
{
return BKP_ReadBackupRegister(BKP_DR);
}
4. 总结
RTC(实时时钟)模块在嵌入式系统中扮演着重要角色,通过提供精确时间和日期信息,帮助系统实现定时任务、时间戳记录、闹钟等功能。我们学习了RTC的原理、配置和应用场景。在配置和初始化RTC时,需要注意时钟源选择、寄存器初始化和异常处理,以确保模块的稳定可靠运行。通过掌握RTC知识,我们能够提升系统性能和稳定性。