一:STM32RTC定时器知识:
RTC是个独立的定时器。RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能。修改计数器的值可以重新设置当前时间和日期。
RTC模块和时钟配置系统(RCC_BDCR寄存器)是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护。
二:RTC的特征如下:
可编程的预分频系数:分频系数最高为2
32位的可编程计数器,可用于较长时间段的测量。
2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟频率的四分之一以上)。
可以选择以下三种RTC的时钟源:
HSE时钟除以128:
LSE振荡器时钟;
LSI振荡器时钟(详见6.2.8节RTC时钟)。
2个独立的复位类型:
APB1接口由系统复位;
RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位(详见6.1.3节)。
3个专门的可屏蔽中断:
闹钟中断,用来产生一个软件可编程的闹钟中断。
秒中断,用来产生一个可编程的周期性中断信号(最长可达1秒)。
溢出中断,指示内部可编程计数器溢出并回转为0的状态。
三:RTC的组成有两部分如下所示:
APB1接口:用来和APB1总线相连。通过APB1接口可以访问RTC的相关寄存器(预分频值,计数器值,闹钟值)。
RTC核心:由一组可编程计数器组成。分两个主要模块。
第一个是RTC预分频模块,它可以编程产生最长1秒的RTC时间基TR_CLK。如果设置了秒中断允许位,可以产生秒中断。
第二个是32位的可编程计数器,可被初始化为当前时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比,当匹配时候如果设置了闹钟中断允许位,可以产生闹钟中断。
四:RTC的配置一般步骤如下:
使能PWR和BKP时钟:RCC_APB1PeriphClockCmd();
② 使能后备寄存器访问: PWR_BackupAccessCmd();
③ 配置RTC时钟源,使能RTC时钟:
RCC_RTCCLKConfig();
RCC_RTCCLKCmd();
如果使用LSE,要打开LSE:RCC_LSEConfig(RCC_LSE_ON);
④ 设置RTC预分频系数:RTC_SetPrescaler();
⑤ 设置时间:RTC_SetCounter();
⑥开启相关中断(如果需要):RTC_ITConfig();
⑦编写中断服务函数:RTC_IRQHandler();
⑧部分操作要等待写操作完成和同步。
RTC_WaitForLastTask();//等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro();//等待RTC寄存器同步
软件代码如下:
void RTC_Set(u16 Tmp_YY,u8 Tmp_MM,u8 Tmp_DD,u8 Tmp_hh,u8 Tmp_mm,u8 Tmp_ss);
u8 Is_Leap_Year(u16 year);
/*******************************************************************************
* Function Name : RTC_Configuration
* Description : Configures the RTC.
* Input : None
* Output : None
* Return : None
*******************************************************************************/
void RTC_Configuration(void)
{
int delay;
/* Enable PWR and BKP clocks */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/* Allow access to BKP Domain */
PWR_BackupAccessCmd(ENABLE);
/* Reset Backup Domain */
BKP_DeInit();
RCC_LSEConfig(RCC_LSE_ON);
do
{
/* delay about 10ms */
for(delay = 0;delay < 200000;delay++);
/* Enable LSE */
}while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); /* Wait till LSE is ready */
/* Select LSE as RTC Clock Source */
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
/* Enable RTC Clock */
RCC_RTCCLKCmd(ENABLE);
/* Wait for RTC registers synchronization */
RTC_WaitForSynchro();
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
/* Enable the RTC Second */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
/* Set RTC prescaler: set RTC period to 1sec */
RTC_SetPrescaler(32767); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) */
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
/*******************************************************************************
RTC_Init
实时时钟初始化
描述:通过读取备份寄存器的值,判定实时时钟是否进行已经初始化,否则进行初始化并置时间初值
本程序在主程序之前进行初始化
*******************************************************************************/
void RTC_Init(void)
{
if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
{
/* Backup data register value is not correct or not yet programmed (when
the first time the program is executed) */
/* RTC Configuration */
RTC_Configuration();
/* Adjust time by values entred by the user on the hyperterminal */
RTC_Set(2011,1,1,0,0,0);
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
}
else
{
/* Check if the Power On Reset flag is set */
if(RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
{
}
/* Check if the Pin Reset flag is set */
else if(RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
{
}
RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
/* Wait for RTC registers synchronization */
RTC_WaitForSynchro();
/* Enable the RTC Second */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
RCC_ClearFlag(); //清标志
}
/*******************************************************************************
Is_Leap_yesr
描述:判断输入的年份是闰年么
输入参数:year
返回参数:1闰年 0非闰年
月份天数-年份对应表
月份 1 2 3 4 5 6 7 8 9 10 11 12
闰年 31 29 31 30 31 30 31 31 30 31 30 31
非闰年 31 28 31 30 31 30 31 31 30 31 30 31
********************************************************************************/
u8 Is_Leap_Year(u16 year)
{
if(year%4 == 0) //闰年必须被4整除
{
if(year%100 == 0)
{
if(year%400 == 0) //如果以00结尾,还必须被400整除
return 1;
else
return 0;
}
else
return 1;
}
else return 0;
}
/*******************************************************************************
* Function Name : Time_Regulate
* Description : 把用户输入的日历时钟转换成秒值,写入RTC寄存器
* Input : hour min second
* Output : None
* Return : None
附加描述:
以时间1970.1.1为基准时间
以1970~2099为合法年份
*******************************************************************************/
const u8 mon_table[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; //平年的月份日期表
void RTC_Set(u16 Tmp_YY,u8 Tmp_MM,u8 Tmp_DD,u8 Tmp_hh,u8 Tmp_mm,u8 Tmp_ss)
{
u16 t;
u32 seccount =0;
if((Tmp_YY < 2000) || (Tmp_YY > 2099))
{
Tmp_YY = 2000; //年份的合法性判断
}
if((Tmp_MM > 12) || (Tmp_MM < 1))
{
Tmp_MM = 1; //月份的合法性判断
}
if((Tmp_DD > 31) || (Tmp_DD < 1))
{
Tmp_DD = 1; //日期的合法性判断
}
if(Tmp_hh > 23)
{
Tmp_hh = 0; //小时的合法性判断
}
if(Tmp_mm > 59)
{
Tmp_mm = 0; //分钟的合法性判断
}
if(Tmp_ss > 59)
{
Tmp_ss = 0; //秒的合法性判断
}
//1.计算从基准年份1970到目前设定值年份的秒钟数
for(t=1970;t<Tmp_YY;t++)
{
if(Is_Leap_Year(t))
seccount += 31622400; //闰年的秒数
else
seccount += 31536000; //平年的秒数
}
//2.把前面的月份的秒数相加
Tmp_MM-=1;
for(t=0;t<Tmp_MM;t++)
{
seccount += (u32)mon_table[t]*86400; //月份秒钟数相加
if((Is_Leap_Year(Tmp_YY)) && (t ==1))
seccount += 86400;
}
//3.把日期,小时,分,秒的秒钟数加上去
seccount += (u32)(Tmp_DD-1)*86400;
seccount += (u32)Tmp_hh*3600;
seccount += (u32)Tmp_mm*60;
seccount += (u32)Tmp_ss;
//4.初始化 RTC 寄存器
RCC->APB1ENR |= 1<<28; //使能电源时钟
RCC->APB1ENR |= 1<<27; //使能备份时钟
PWR->CR |= 1<<8; //取消备份区写保护
PWR_BackupAccessCmd(ENABLE);
RTC_WaitForLastTask();
RTC_SetCounter(seccount);
RTC_WaitForLastTask();
}
/**********************************************************************
获取实时时钟日历值
输入参数:
返回值:将日历写入Calendar的起始地址,共6个字节
将时间写入CurrTime的起始地址,共6个字节
**********************************************************************/
void RTC_Get(u16 *Calendar,u16 *CurrTime)
{
u16 year,month,day,hour,min,second;
volatile static u16 daycnt;
u32 timecount = 0;
u32 temp = 0;
u32 temp1 = 0;
timecount = RTC_GetCounter(); //读取实时时钟
temp = timecount / 86400;
if(daycnt != temp) //天数发生了变化,则重新计算日历
{
daycnt = temp;
temp1 = 1970; //起始基准年份
while(temp >= 365)
{
if(Is_Leap_Year(temp1))
{
if(temp >= 366)
temp -= 366;
else
{
// temp1 ++; //该处代码需要去掉,否到导致闰年的最后一天显示错误
break;
}
}
else
temp -= 365;
temp1++;
}
year = temp1; //得到年份
temp1 = 0;
while(temp >= 28)
{
if((Is_Leap_Year(year)) && (temp1==1))
{
if(temp >= 29)
temp -= 29;
else
break;
}
else
{
if(temp >= mon_table[temp1])
temp -= mon_table[temp1];
else
break;
}
temp1 ++;
}
month = temp1+1; //得到月份
day = temp+1; //得到日期
*Calendar = year;Calendar++;
*Calendar = month;Calendar++;
*Calendar = day;
}
temp = timecount%86400; //得到秒钟数
hour = temp/3600; //得到小时
temp1 = temp%3600;
min = temp1/60; //得到分钟
second = temp1%60; //得到秒钟
*CurrTime = hour;CurrTime++;
*CurrTime = min;CurrTime++;
*CurrTime = second;
}如上述代码所示,该RTC代码在很多例程中都使用该部分代码;该读取RTC的代码计算有个问题,导致闰年的最后一天显示错误哦,
while(temp >= 365)
{
if(Is_Leap_Year(temp1))
{
if(temp >= 366)
temp -= 366;
else
{
// temp1 ++;
break;
}如上述所示,需要将temp1代码屏蔽掉,否则在闰年的最后一天显示错误,例如:2024-12-31 会显示成2025-13-1 ;
我要赚赏金
