欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

STM32之RTC(实时时钟)代码讲解

程序员文章站 2024-02-22 21:08:28
...

          STM32 的实时时钟(RTC)是一个独立的定时器。STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
        RTC 模块和时钟配置系统(RCC_BDCR 寄存器)是在后备区域,即在系统复位或从待机模式唤醒后 RTC 的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和 RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护

RTC 的简化框图:

STM32之RTC(实时时钟)代码讲解

       RTC 由两个主要部分组成(见上图),第一部分(APB1 接口)用来和 APB1 总线相连。此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。APB1 接口由 APB1 总线时钟驱动,用来与 APB1 总线连接。另一部分(RTC 核心)由一组可编程计数器组成,分成两个主要模块。第一个模块是 RTC 的预分频模块,它可编程产生 1 秒的 RTC 时间基准 TR_CLK。RTC 的预分频模块包含了一个 20位的可编程分频器(RTC 预分频器)。如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个TR_CLK 周期中 RTC 产生一个中断(秒中断)。第二个模块是一个 32 位的可编程计数器RTC_CNT),可被初始化为当前的系统时间,一个 32 位的时钟计数器,按秒钟计算,可以记录 4294967296 秒,约合 136 年左右,作为一般应用,这已经是足够了的。RTC 还有一个闹钟寄存器 RTC_ALR,用于产生闹钟。系统时间按 TR_CLK 周期累加并与

         136年的计算,取32位的最大值:

          ffffffff(十六进制) /   3600 (十进制,一小时的秒数)  /     24 (一天24小时)   /     365 (一年按照365天计算)   =  136 年

         ffffffff(十六进制)   =    4294967295(十进制)

STM32之RTC(实时时钟)代码讲解

    

回归到本次文章的重点吧,代码讲解,寄存器部分,各位自己去了解吧,开始使用的时候,其实可以直接套模板用着先,有时间了自己在深入了解寄存器就行(只讲初始化部分,代码是原子的):

下面的代码是RTC时钟的初始化部分,以及中断部分:

我想讲解的是,如何去实现时间计算部分,在下面:

//RTC时钟中断
//每秒触发一次  	 
void RTC_IRQHandler(void)
{		 
	if(RTC->CRL&0x0001)//秒钟中断
	{							
		RTC_Get();//更新时间   
		//printf("sec:%d\r\n",calendar.sec);
 	}
	if(RTC->CRL&0x0002)//闹钟中断
	{
		RTC->CRL&=~(0x0002);		//清闹钟中断	  
  		//printf("Alarm!\n");		   
  	} 				  								 
    RTC->CRL&=0X0FFA;         //清除溢出,秒钟中断标志
	while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成	  	    						 	   	 
}


//实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常
//BKP->DR1用于保存是否第一次配置的设置
//返回0:正常
//其他:错误代码
u8 RTC_Init(void)
{
	//检查是不是第一次配置时钟
	u8 temp=0;
	if(BKP->DR1!=0X5050)//第一次配置
	{	 
	  	RCC->APB1ENR|=1<<28;     //使能电源时钟	    
		RCC->APB1ENR|=1<<27;     //使能备份时钟	    
		PWR->CR|=1<<8;           //取消备份区写保护
		RCC->BDCR|=1<<16;        //备份区域软复位	   
		RCC->BDCR&=~(1<<16);     //备份区域软复位结束	  	 
	    RCC->BDCR|=1<<0;         //开启外部低速振荡器 
	    while((!(RCC->BDCR&0X02))&&temp<250)//等待外部时钟就绪	 
		{
			temp++;
			delay_ms(10);
		};
		if(temp>=250)return 1;//初始化时钟失败,晶振有问题	   
		RCC->BDCR|=1<<8; //LSI作为RTC时钟 	    
		RCC->BDCR|=1<<15;//RTC时钟使能	  
	  	while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成	 
    	while(!(RTC->CRL&(1<<3)));//等待RTC寄存器同步  
    	RTC->CRH|=0X01;  		  //允许秒中断
    	while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成	 
		RTC->CRL|=1<<4;           //允许配置	 
		RTC->PRLH=0X0000;
		RTC->PRLL=32767;          //时钟周期设置(有待观察,看是否跑慢了?)理论值:32767	
		RTC_Set(2014,3,8,22,10,55);  //设置时间	  
		RTC->CRL&=~(1<<4);           //配置更新
		while(!(RTC->CRL&(1<<5)));   //等待RTC寄存器操作完成		 									  
		BKP->DR1=0X5050;  
	 	printf("FIRST TIME\n");
	}else//系统继续计时
	{
    	while(!(RTC->CRL&(1<<3)));//等待RTC寄存器同步  
    	RTC->CRH|=0X01;  		  //允许秒中断
    	while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成
		printf("OK\n");
	}		    				  
	MY_NVIC_Init(0,0,RTC_IRQn,2);//优先级设置    
	RTC_Get();//更新时间 
	return 0; //ok
}

实现时间计算部分代码:

说一下除以和求余的区别:

除运算得到的是整数倍   除数的整数;

求余预算得到的是拿了整数倍   除数,,剩余的整数;

例如   100  /  3  =  33..........3       33是除运算得到的,1是求余的得到的

先说一下实现计算时间的实现思路(主要抓住中断函数就行,中断是每1s中断一次):

1、中断,跳转至我们的中断服务函数,取出计数器计算到的秒数;

2、对秒数进行计算,求出过了多少天;                                                                          

     加入现在的秒数是 8000000s,

     那现在就是  8000000 / 3600 / 24 /  =  92.59 天

    这里计算出来的天数(整数 加1 )要和上一次的天数比较,如果多一天的话,就继续执行,要是后365天了,就要判断下一年是否是闰      年(接下来计算的2月要使用),计算闰年,就考验大家的算法了

   将闰年的和平年每个月的天数   保存在数组里面,将计算出的天数和数组比较机也可以得出现在是几月

   例如:今年是平年 92.59 - 31(1月)-28(2月)-31(3月)= 2.59天 

              2.59天   <   30天(4月)  那就可以判断现在是4月

              2.59天 取整数 再加1,就是哪天,2.59取整就是2,再加1就是3,那今天就是4月3号

3、时间计算:

      1)小时:计算的秒数对一天的秒数求余数得到不够一天的秒数之后,在除以一小时的秒数;       

           8000000 % (3600 *24)  /     3600        =      14  ,那现在的时钟就是下午的2时

     2)分钟:计算的秒数对一天的秒数求余数得到不够一天的秒数之后,

                      再对一小时的秒数求余,得到不够一小时的秒数;

                      最后在除以一分钟的秒数得到现在是  几分钟

     3)秒数:   计算的秒数对一天的秒数求余数得到不够一天的秒数之后,

                      再对一小时的秒数求余,得到不够一小时的秒数;

                      最后再一分钟的秒数求余,得到现在是  几秒

4)星期几,这个就自己看吧,也不难,代码应该能看懂的了

//判断是否是闰年函数
//月份   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
//year:年份
//返回值:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year)
{			  
	if(year%4==0) //必须能被4整除
	{ 
		if(year%100==0) 
		{ 
			if(year%400==0)return 1;//如果以00结尾,还要能被400整除 	   
			else return 0;   
		}else return 1;   
	}else return 0;	
}	 			   
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//返回值:0,成功;其他:错误代码.
//月份数据表											 
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表	  
//平年的月份日期表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
//syear,smon,sday,hour,min,sec:年月日时分秒
//返回值:设置结果。0,成功;1,失败。
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
	u16 t;
	u32 seccount=0;
	if(syear<1970||syear>2099)return 1;	   
	for(t=1970;t<syear;t++)	//把所有年份的秒钟相加
	{
		if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
		else seccount+=31536000;			  //平年的秒钟数
	}
	smon-=1;
	for(t=0;t<smon;t++)	   //把前面月份的秒钟数相加
	{
		seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
		if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数	   
	}
	seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加 
	seccount+=(u32)hour*3600;//小时秒钟数
    seccount+=(u32)min*60;	 //分钟秒钟数
	seccount+=sec;//最后的秒钟加上去
													    
	//设置时钟
    RCC->APB1ENR|=1<<28;//使能电源时钟
    RCC->APB1ENR|=1<<27;//使能备份时钟
	PWR->CR|=1<<8;    //取消备份区写保护
	//上面三步是必须的!
	RTC->CRL|=1<<4;   //允许配置 
	RTC->CNTL=seccount&0xffff;
	RTC->CNTH=seccount>>16;
	RTC->CRL&=~(1<<4);//配置更新
	while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 
	RTC_Get();//设置完之后更新一下数据 	
	return 0;	    
}
//得到当前的时间,结果保存在calendar结构体里面
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
	static u16 daycnt=0;
	u32 timecount=0; 
	u32 temp=0;
	u16 temp1=0;	  
 	timecount=RTC->CNTH;//得到计数器中的值(秒钟数)
	timecount<<=16;
	timecount+=RTC->CNTL;			 

 	temp=timecount/86400;   //得到天数(一天的秒数)
	if(daycnt!=temp)//超过一天了
	{	  
		daycnt=temp;
		temp1=1970;	//从1970年开始
		while(temp>=365)
		{				 
			if(Is_Leap_Year(temp1))//是闰年
			{
				if(temp>=366)temp-=366;//闰年的秒钟数
				else break;  
			}
			else temp-=365;	  //平年 
			temp1++;  
		}   
		calendar.w_year=temp1;//得到年份
		temp1=0;
		while(temp>=28)//超过了一个月
		{
			if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
			{
				if(temp>=29)temp-=29;//闰年的秒钟数
				else break; 
			}
			else 
			{
				if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
				else break;
			}
			temp1++;  
		}
		calendar.w_month=temp1+1;	//得到月份
		calendar.w_date=temp+1;  	//得到日期 
	}
	temp=timecount%86400;     		//对计算到的秒数对一天的秒数求余,得到不够一天的秒数
	calendar.hour=temp/3600;     	//小时,除以一小时的秒数得到现在的小时
	calendar.min=(temp%3600)/60; 	//分钟,对一小时的秒数求余,再除以一小时的分数得到现在的分钟	
	calendar.sec=(temp%3600)%60; 	//秒钟,对一小时的秒数求余,再求余一分钟的秒数得到现在的秒数
	calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期   
	return 0;
}	 
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//year,month,day:公历年月日 
//返回值:星期号																						 
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{	
	u16 temp2;
	u8 yearH,yearL;
	
	yearH=year/100;	yearL=year%100; 
	// 如果为21世纪,年份数加100  
	if (yearH>19)yearL+=100;
	// 所过闰年数只算1900年之后的  
	temp2=yearL+yearL/4;
	temp2=temp2%7; 
	temp2=temp2+day+table_week[month-1];
	if (yearL%4==0&&month<3)temp2--;
	return(temp2%7);
}