RTC实时时钟源码分析

news2024/11/17 17:36:18

1.先来看一下RTC的配置过程

 2.RTC源码讲解

我们的工程中加入了 rtc.c 源文件和 rtc.h头文件,同时,引入了 stm32f10x_rtc.c 和 stm32f10x_bkp.c 库文件。

说明,首先是 RTC_Init,其代码如下:

//实时时钟配置
//初始化 RTC 时钟,同时检测时钟是否工作正常
//BKP->DR1 用于保存是否第一次配置的设置
//返回 0:正常
//其他:错误代码
u8 RTC_Init(void)
{
u8 temp=0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR |
RCC_APB1Periph_BKP, ENABLE); //使能 PWR 和 BKP 外设时钟。这个使能了时钟和后备区域寄存器
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
//检查是不是第一次配置RTC,如果不是的话就要进行复位等操作
if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //从指定的后备寄存器中
//读出数据:读出了与写入的指定数据不相等
{ 
BKP_DeInit(); //③复位备份区域
RCC_LSEConfig(RCC_LSE_ON);  //设置外部低速晶振(LSE)
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) //检查指定的
//RCC 标志位设置与否,等待低速晶振就绪
{       //在这里的这个for循环主要是为了防止LSE外部时钟没有配置成功,得多次进行检测才行
temp++;
delay_ms(10);
}
if(temp>=250)return 1;//初始化时钟失败,晶振有问题 
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置 RTC 时钟
//(RTCCLK),选择 LSE 作为 RTC 时钟
RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟
RTC_WaitForLastTask();  //等待最近一次对 RTC 寄存器的写操作完成
RTC_WaitForSynchro(); //等待 RTC 寄存器同步
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断
RTC_WaitForLastTask();  //等待最近一次对 RTC 寄存器的写操作完成
RTC_EnterConfigMode(); // 允许配置,只有执行了这个函数才能对RTC里面的寄存器进行操作
RTC_SetPrescaler(32767); //设置 RTC 预分频的值
RTC_WaitForLastTask();  //等待最近一次对 RTC 寄存器的写操作完成
RTC_Set(2009,12,2,10,0,55); //设置时间
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后备寄存器中
//写入用户程序数据 0x5050,相当于恢复了原始的状态
}
else//系统继续计时
{
RTC_WaitForSynchro(); //等待最近一次对 RTC 寄存器的写操作完成
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断
RTC_WaitForLastTask();  //等待最近一次对 RTC 寄存器的写操作完成
}
RTC_NVIC_Config(); //RCT 中断分组设置 
RTC_Get(); //更新时间 
return 0; //ok
}

该函数用来初始化 RTC 时钟,但是只在第一次的时候设置时间,以后如果重新上电/复位都不会再进行时间设置了(前提是备份电池有电),在第一次配置的时候,我们是按照上面介绍的 RTC 初始化步骤来做的,这里就不在多说了,这里我们设置时间是通过时间设置函数RTC_Set(2014,3,8,22,10,55);来实现的,这里我们默认将时间设置为 2014 年 3 月 8 日 22 点 10 分55 秒。在设置好时间之后,我们通过语句 BKP_WriteBackupRegister(BKP_DR1, 0X5050);向
BKP->DR1 写入标志字 0X5050,用于标记时间已经被设置了。这样,再次发生复位的时候,该函数通过语句 if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050)判断 BKP->DR1 的值,来决定是不是需要重新设置时间,如果不需要设置,则跳过时间设置,仅仅使能秒钟中断一下,就进行中断分组,然后返回了。这样不会重复设置时间,使得我们设置的时间不会因复位或者断电而丢失。
该函数还有返回值,返回值代表此次操作的成功与否,如果返回 0,则代表初始化 RTC 成功,如果返回值非零则代表错误代码了。

//设置时钟
//把输入的时钟转换为秒钟
//以 1970 年 1 月 1 日为基准
//1970~2099 年为合法年份
//返回值:0,成功;其他:错误代码.
//平年的月份日期表
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_APB1PeriphClockCmd(RCC_APB1Periph_PWR |
RCC_APB1Periph_BKP, ENABLE); //使能 PWR 和 BKP 外设时钟
PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问
RTC_SetCounter(seccount); //设置 RTC 计数器的值
RTC_WaitForLastTask();  //等待最近一次对 RTC 寄存器的写操作完成
return 0;
}

该函数用于设置时间,把我们输入的时间,转换为以 1970 年 1 月 1 日 0 时 0 分 0 秒当做起始时间的秒钟信号,后续的计算都以这个时间为基准的,由于 STM32 的秒钟计数器可以保存136 年的秒钟数据,这样我们可以计时到 2106 年。
接着,我们介绍一下 RTC_Get 函数,该函数用于获取时间和日期等数据,其代码如上:
 

//得到当前的时间,结果保存在 calendar 结构体里面
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
static u16 daycnt=0;
u32 timecount=0; u32 temp=0; u16 temp1=0; 
timecount=RTC_GetCounter(); 
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;
}

函数其实就是将存储在秒钟寄存器 RTC->CNTH 和 RTC->CNTL 中的秒钟数据转换为真正的时间和日期。该代码还用到了一个 calendar 的结构体,calendar 是我们在 rtc.h 里面将要定义的一个时间结构体,用来存放时钟的年月日时分秒等信息。因为 STM32 的 RTC 只有秒钟计数器,而年月日,时分秒这些需要我们自己软件计算。我们把计算好的值保存在 calendar 里面,方便其他程序调用。

最后介绍一下秒钟中断服务函数,该函数代码如下:
 

//RTC 时钟中断
//每秒触发一次
void RTC_IRQHandler(void)
{ 
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
{ 
RTC_Get();//更新时间
}
if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
{
RTC_ClearITPendingBit(RTC_IT_ALR);  //清闹钟中断
}
RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);  //清闹钟中断
RTC_WaitForLastTask(); 
}

此部分代码比较简单,我们通过 RTC_GetITStatus 函数来判断发生的是何种中断,如果是秒钟中断,则执行一次时间的计算,获得最新时间。从而,我们可以在 calendar 里面读到时间、日期等信息。

接下来看看 rtc.h 内容如下:

#ifndef __RTC_H
#define __RTC_H
//时间结构体
typedef struct
{
vu8 hour;
vu8 min;
vu8 sec;
//公历日月年周
vu16 w_year;
vu8 w_month;
vu8 w_date;
vu8 week; 
}_calendar_obj; 
extern _calendar_obj calendar;  //日历结构体
void Disp_Time(u8 x,u8 y,u8 size);  //在制定位置开始显示时间
void Disp_Week(u8 x,u8 y,u8 size,u8 lang); //在指定位置显示星期
u8 RTC_Init(void); //初始化 RTC,返回 0,失败;1,成功;
u8 Is_Leap_Year(u16 year); //平年,闰年判断
u8 RTC_Get(void); //更新时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day);
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间 
#endif

从上面代码可以看到_calendar_obj 结构体所包含的东西,是一个完整的公历信息,包括年、月、日、周、时、分、秒等 7 个元素。我们以后要知道当前时间,只需要通过 RTC_Get 函数,执行时钟转换,然后就可以从 calendar 里面读出当前的公历时间了。

最后来看看主函数代码:
 

int main(void)
{ 
u8 t;
delay_init(); //延时函数初始化 
uart_init(9600);  //串口初始化为 9600
LED_Init();  //初始化与 LED 连接的硬件接口
LCD_Init();  //初始化 LCD
usmart_dev.init(72);  //初始化 USMART
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"Mini STM32");
LCD_ShowString(60,70,200,16,16,"RTC TEST"); 
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2014/3/8");
while(RTC_Init()) //RTC 初始化 ,一定要初始化成功
{
LCD_ShowString(60,130,200,16,16,"RTC ERROR! "); delay_ms(800);
LCD_ShowString(60,130,200,16,16,"RTC Trying...");
} 
//显示时间
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,130,200,16,16," - - "); 
LCD_ShowString(60,162,200,16,16," : : ");
while(1)
{ 
if(t!=calendar.sec)
{
t=calendar.sec;
LCD_ShowNum(60,130,calendar.w_year,4,16);
LCD_ShowNum(100,130,calendar.w_month,2,16); 
LCD_ShowNum(124,130,calendar.w_date,2,16);
switch(calendar.week)
{
case 0: LCD_ShowString(60,148,200,16,16,"Sunday "); break; 
case 1: LCD_ShowString(60,148,200,16,16,"Monday "); break; 
case 2: LCD_ShowString(60,148,200,16,16,"Tuesday "); break; 
case 3: LCD_ShowString(60,148,200,16,16,"Wednesday");break;
case 4: LCD_ShowString(60,148,200,16,16,"Thursday ");break;
case 5: LCD_ShowString(60,148,200,16,16,"Friday ");break;
case 6: LCD_ShowString(60,148,200,16,16,"Saturday ");break;
}
LCD_ShowNum(60,162,calendar.hour,2,16); 
LCD_ShowNum(84,162,calendar.min,2,16);
LCD_ShowNum(108,162,calendar.sec,2,16);
LED0=!LED0;
} 
delay_ms(10);
};
}

这部分代码就不再需要详细解释了,在包含了 rtc.h 之后,通过判断 calendar.sec 是否改变来决定要不要更新时间显示。同时我们设置 LED0 每 2 秒钟闪烁一次,用来提示程序已经开始跑了。
为了方便设置时间,我们在 usmart_config.c 里面,修改 usmart_nametab 如下:
 

struct _m_usmart_nametab usmart_nametab[]=
{
#if USMART_USE_WRFUNS==1 //如果使能了读写操作
(void*)read_addr,"u32 read_addr(u32 addr)",
(void*)write_addr,"void write_addr(u32 addr,u32 val)", 
#endif 
(void*)delay_ms,"void delay_ms(u16 nms)",
(void*)delay_us,"void delay_us(u32 nus)",
(void*)RTC_Set,"u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)",
};

将 RTC_Set 加入了 usmart,同时去掉了上一章的一些函数(减少代码量),这样通过串口就可以直接设置 RTC 时间了。
至此,RTC 实时时钟的软件设计就完成了,接下来就让我们来检验一下,我们的程序是否正确了。

整体函数代码:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "rtc.h" 		    
// 精英 STM32开发板
//RTC实时时钟 驱动代码			 
//正点原子@ALIENTEK
//2010/6/6
	   
_calendar_obj calendar;//时钟结构体 
 
static void RTC_NVIC_Config(void)
{	
  NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;		//RTC全局中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	//先占优先级1位,从优先级3位
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	//先占优先级0位,从优先级4位
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;		//使能该通道中断
	NVIC_Init(&NVIC_InitStructure);		//根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}

//实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常
//BKP->DR1用于保存是否第一次配置的设置
//返回0:正常
//其他:错误代码

u8 RTC_Init(void)
{
	//检查是不是第一次配置时钟
	u8 temp=0;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);	//使能PWR和BKP外设时钟   
	PWR_BackupAccessCmd(ENABLE);	//使能后备寄存器访问  
	if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050)		//从指定的后备寄存器中读出数据:读出了与写入的指定数据不相同
		{	 			 
		BKP_DeInit();	//复位备份区域 	
		RCC_LSEConfig(RCC_LSE_ON);	//设置外部低速晶振(LSE),使用外设低速晶振
		while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250)	//检查指定的RCC标志位设置与否,等待低速晶振就绪
			{
			temp++;
			delay_ms(10);
			}
		if(temp>=250)return 1;//初始化时钟失败,晶振有问题	    
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);		//设置RTC时钟(RTCCLK),选择LSE作为RTC时钟    
		RCC_RTCCLKCmd(ENABLE);	//使能RTC时钟  
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		RTC_WaitForSynchro();		//等待RTC寄存器同步  
		RTC_ITConfig(RTC_IT_SEC, ENABLE);		//使能RTC秒中断
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		RTC_EnterConfigMode();/// 允许配置	
		RTC_SetPrescaler(32767); //设置RTC预分频的值
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		RTC_Set(2015,1,14,17,42,55);  //设置时间	
		RTC_ExitConfigMode(); //退出配置模式  
		BKP_WriteBackupRegister(BKP_DR1, 0X5050);	//向指定的后备寄存器中写入用户程序数据
		}
	else//系统继续计时
		{

		RTC_WaitForSynchro();	//等待最近一次对RTC寄存器的写操作完成
		RTC_ITConfig(RTC_IT_SEC, ENABLE);	//使能RTC秒中断
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		}
	RTC_NVIC_Config();//RCT中断分组设置		    				     
	RTC_Get();//更新时间	
	return 0; //ok

}		 				    
//RTC时钟中断
//每秒触发一次  
//extern u16 tcnt; 
void RTC_IRQHandler(void)
{		 
	if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//一秒钟中断
	{							
		RTC_Get();//更新时间   
 	}
	if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
	{
		RTC_ClearITPendingBit(RTC_IT_ALR);		//清闹钟中断	  	
	  RTC_Get();				//更新时间   
  	printf("Alarm Time:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间	
		
  	} 				  								 
	RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);		//清闹钟中断
	RTC_WaitForLastTask();	  	    						 	   	 
}
//判断是否是闰年函数
//月份   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
//输入:年份
//输出:该年份是不是闰年.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};
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_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);	//使能PWR和BKP外设时钟  
	PWR_BackupAccessCmd(ENABLE);	//使能RTC和后备寄存器访问 
	RTC_SetCounter(seccount);	//设置RTC计数器的值

	RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成  	
	return 0;	    
}

//初始化闹钟		  
//以1970年1月1日为基准
//1970~2099年为合法年份
//syear,smon,sday,hour,min,sec:闹钟的年月日时分秒   
//返回值:0,成功;其他:错误代码.
u8 RTC_Alarm_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_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);	//使能PWR和BKP外设时钟   
	PWR_BackupAccessCmd(ENABLE);	//使能后备寄存器访问  
	//上面三步是必须的!
	
	RTC_SetAlarm(seccount);
 
	RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成  	
	
	return 0;	    
}


//得到当前的时间
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
	static u16 daycnt=0;
	u32 timecount=0; 
	u32 temp=0;
	u16 temp1=0;	  
    timecount=RTC_GetCounter();	 
 	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 {temp1++;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年)
//输入参数:公历年月日 
//返回值:星期号																						 
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);
}			  
















本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/548568.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UI自动化测试遇弹窗怎么办?这个解决方法你get到了没?

目录 引言 弹窗的种类: APP内的业务弹窗 弹窗处理 watcher的使用 实战案例 软件测试技术的准备和建议 一、测试基础 二、Linux必备知识 三、Shell脚本 四、互联网程序原理 五、MySQL数据库 六、抓包工具 七、接口测试工具 八、Web自动化测试Java&Python 九…

关于C语言的杂记5

文章目录 引入正文内部函数与外部函数相关数组的知识点数组的初始化测试一维数组在内存中存储的地址&#xff1a;遍历二维数组的值测试二维数组的地址(观察内存情况)数组下标为0开始的由来 两个数交换位置的三种方法 引入 写在前面&#xff1a;关于C语言这部分内容&#xff0c;…

msys2 pacman 安装 ncurses-devel 解决 make menuconfig 问题

问题描述 windows 下使用 msys2 环境&#xff0c;可以开发编译一些 Linux 下的软件包&#xff0c;一般Linux 下的软件包&#xff0c;需要安装 PC 平台的gcc、gcc交叉编译工具链&#xff0c;如 arm的&#xff0c;以及一些常用的 软件包&#xff0c;如 python 等 有一个软件包&a…

【工具】Ubuntu 非root用户 安装openssl

文章目录 openssl 下载 & 解压指定安装路径编译安装配置环境变量验证是否安装成功 openssl 下载 & 解压 openssl 下载地址 或者 https://www.openssl.org/source/openssl-1.1.1l.tar.gz 上传到服务器 解压 tar -zxvf openssl-1.1.1l.tar.gz切换到openssl-1.1.1l目…

maven-resources-plugin详解

目录 一、什么场景会用到&#xff1f;二、他有什么作用&#xff1f;三、插件常用配置1、字符集编码2、resources相关的配置2.1、resources配置结构2.2、resources默认配置2.2.1、maven超级pom默认配置2.2.2、spring-boot对resources插件的默认配置2.2.3、resources最终生效的配…

2023年最新VMware 17+虚拟机详细配置安装【程序员使用指南】!!

文章目录 Vmware版本选择17Pro安装自定义安装填写对应的许可证正式安装虚拟机进行对应的配置配置镜像文件选择对应的语言&#xff1a;到这个界面&#xff0c;选择中文 安装结束连接对应的xshell Vmware版本选择17Pro安装 ● 最开始从这里出发 自定义安装 ● 记得自定义在自…

TikTok选品,什么产品在TikTok最受欢迎?

TikTok是当下的海外社交媒体推广新风口已经有目共睹&#xff0c;与此同时跨境商家们或许都面临着同样的问题&#xff0c;就是TikTok要怎么选品&#xff0c;什么产品才会比较受欢迎。俗话说&#xff1a;七分靠选品&#xff0c;三分靠运营。选品选对了&#xff0c;运营也就事半功…

chatgpt赋能Python-python3_decode函数

Python3 decode函数 - 解码你的数据 编程领域中&#xff0c;字符串是一个不可避免的概念&#xff0c;他们构成了我们生活中逻辑化和技术实现化的重要组成部分。不仅如此&#xff0c;字符串的加载和使用也对程序的性能产生了重大的影响。在这篇文章中&#xff0c;我们将重点讨论…

现在有什么基于vue3.0的优秀开源项目呢?

前言 以下是一些github上一些基于vue3.0的优秀开源项目&#xff0c;可以自己选择一些去练一下手&#xff0c;废话少说&#xff0c;直接进入正题---- 1、 vue-manage-system Star: 16k 是一个基于 Vue3 和 Element Plus 的后台管理系统解决方案&#xff0c;它提供了一系列的通…

电脑找不到msvcr110.dll怎么办?电脑msvcr110.dll丢失怎么解决?

在使用某些软件或游戏时&#xff0c;有时会出现找不到msvcr110.dll文件的错误提示&#xff0c;这会导致程序无法正常运行。这个问题通常是由于缺少Visual C msvcr110.dll引起的。下面是解决这个问题的几个方法&#xff1a; msvcr110.dll的解决方法一 直接使用dll修复程序进行修…

videojs扩展方法,自定义功能

8版本不支持extend&#xff0c;8支持es6扩展 videojsextend

DeepGPS: Deep Learning Enhanced GPS Positioning in Urban Canyons

DeepGPS: Deep Learning Enhanced GPS Positioning in Urban Canyons IEEE TMC&#xff5c;深度学习增强的城市峡谷GPS定位 https://github.com/bducgroup/DeepGPS 文章目录 DeepGPS: Deep Learning Enhanced GPS Positioning in Urban CanyonsAbstract1 INTRODUCTION2 BACKGRO…

【C++】如何克服红黑树的恐惧?看这篇文章足够了

红黑树的实现会比AVL简单-.- 文章目录 判断是否是AVL树一、红黑树二、红黑树的实现总结 判断是否是AVL树 上一篇文章我们详细介绍了AVL树并且实现了AVL树&#xff0c;这篇文章我们将在前言中引入判断是否是AVL树的方法&#xff0c;然后我们就进入红黑树的实现&#xff0c;如果…

从0到1精通性能测试,需求到实战压力测试实例(详情)你要的都有...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 性能测试需求分析…

David Silver Lecture 9:Exploration and Exploitation

1 Introduction 1.1 Outline 1.1.1 Exploration vs. Exploitation Dilemma 1.1.2 examples 1.1.3 principles Naive Exploration 在前面的章节主要使用的是naive exploration的方法Optimistic Initialisation 这种方法的思想是&#xff0c;我们对每个动作的奖励给出一个乐观的…

C语言深度解刨(一)——关键字

文章目录 前言关键字定义和声明&#xff0c;你懂了吗&#xff1f;最宽宏大量关键字——auto最快的关键字——register最名不副实的关键字——static修饰变量修饰函数 基本数据类型——short、int、long、char、flaot、double变量命名规则 最冤枉的关键字——sizeof常年被人误认…

Java开发 - 不知道算不算详细的分布式锁详解

前言 今天给大家带来一篇关于分布式锁的好文&#xff0c;关于分布式系统下的内容&#xff0c;博主已经写了好几篇了&#xff0c;也收获了大家的不少好评。分布式系统在目前的开发中所占的比重还是比较大的&#xff0c;如果你还没接触过分布式系统&#xff0c;那么欢迎你去学习…

操作文件 --Java EE

目录 一、文件的概念 二、文件的目录&#xff08;directory&#xff09; 三、文件路径&#xff08;Path&#xff09; 四、文件的分类 五、文件的操作 1.文件系统的操作 1.1创建文件--createNewFile( ) 1.2删除文件--delete( ) 和 deleteOnExit( ) 1.3文件改名--renam…

2023ICPC西安邀请赛

比赛完由于被旅游、赶ddl、上班等等各种事情影响&#xff0c;导致我现在才有时间可以写写小作文&#xff0c;这中间隔得时间有点长了&#xff0c;已经不知道从哪开始说起了&#xff0c;灵感也都流失了… before Friday 比赛前一个周&#xff0c;我的队友djk&#xff0c;也是我…

HNU-计算机系统-Challenge

Challenge 计科210X wolf 202108010XXX 本题是从属于第七次讨论课的个人题,听说做了有加分?我来试试。 下面是相关报告。 题目: C 语言的初学者第一个编写的 C 代码一般是如下所示的“ #include <stdio.h> int main() {printf("Hello, World!");