这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【换取逻辑分析仪】在STM32F103硬件平台解决RTC读取闰年最后一天时间错误

共3条 1/1 1 跳转至

【换取逻辑分析仪】在STM32F103硬件平台解决RTC读取闰年最后一天时间错误的问题

助工
2025-01-01 14:26:01     打赏

一: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 ;




关键词: STM32     RTC    

专家
2025-01-01 22:07:47     打赏
2楼

发现这个很厉害!谢谢分享!


工程师
2025-01-02 13:41:11     打赏
3楼

666666


共3条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]