STM32RTC外设详解

一.RTC 实时时钟简介

实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变

STM32 的 RTC 外设,实质是一个掉电后还继续运行的定时器。所以 RTC 外设的复杂之处并不在于它的定时功能,而在于它掉电还继续运行的特性,而所谓掉电是指主电源 VDD 断开的情况,为了 RTC 外设掉电继续运行,必须接上锂电池给 STM32 的 RTC、备份发卡通过 VBAT 引脚供电。当主电源 VDD 有效时,由VDD给 RTC 外设供电;而当 VDD掉电后,由 VBAT给RTC 外设供电。

在这里插入图片描述
开发板中提供了一个钮扣电池插槽,可以接入型号为 CR1220 的钮扣电池,该型号的钮扣电池电压为 3.2V,图中的 BAT54C 双向二极管可切换输入到 STM32 备份域电源引脚 VBAT 的供电,当主电源正常供电时,由稳压器输出的 3.3V 供电,当主电源掉电时,由钮扣电池供电。

在这里插入图片描述

1.RTC时钟来源

对时钟不熟的请看《STM32系统时钟超详解》
在这里插入图片描述
从 RTC 的定时器特性来说,它是一个 32 位的计数器,只能向上计数。它使用的时钟源有三种:
1.高速外部时钟的 128 分频(HSE/128)
2.低速内部时钟 LSI
3.低速外部时钟 LSE;

使 HSE 分频时钟或 LSI 的话,在主电源 VDD掉电的情况下,这两个时钟来源都会受到影响,因此没法保证 RTC 正常工作。

因此 RTC 一般使用低速外部时钟 LSE,在设计中,频率通常为实时时钟模块中常用的 32.768KHz,这是因为 32768 = 2的15次方,分频容易实现,所以它被广泛应用到 RTC 模块。在主电源 VDD有效的情况下(待机),RTC 还可以配置闹钟事件使 STM32 退出待机模式

2.RTC主要特性

● 可编程的预分频系数:分频系数最高为2的20次方。
● 32位的可编程计数器,可用于较长时间段的测量。
● 2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟频率的四分之一以上)。
● 可以选择以下三种RTC的时钟源:
─ HSE时钟除以128;
─ LSE振荡器时钟;
─ LSI振荡器时钟。
● 2个独立的复位类型:
─ APB1接口由系统复位;
RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位(详见6.1.3节)
● 3个专门的可屏蔽中断:
─ 闹钟中断,用来产生一个软件可编程的闹钟中断。
─ 秒中断,用来产生一个可编程的周期性中断信号(最长可达1秒)。
─ 溢出中断,指示内部可编程计数器溢出并回转为0的状态

二.RTC 外设功能框图

在这里插入图片描述
框图中浅灰色的部分都是属于备份域的,在 VDD 掉电时可在 VBAT 的驱动下继续运行。这部分仅包括 RTC 的分频器,计数器,和闹钟控制器。

若 VDD 电源有效,RTC 可以触发RTC_Second(秒中断)、RTC_Overflow(溢出事件)和 RTC_Alarm(闹钟中断)。

从结构图可以分析到,其中的定时器溢出事件无法被配置为中断。若 STM32 原本处于待机状态,可由闹钟事件或 WKUP 事件(外部唤醒事件,属于 EXTI 模块,不属于 RTC)使它退出待机模式。闹钟事件是在计数器 RTC_CNT 的值等于闹钟寄存器 RTC_ALR 的值时触发的

1.RTC功能框图剖析

RTC有两个部分组成
第一部分:
RTC由两个主要部分组成(参见下图)。第一部分(APB1接口)用来和APB1总线相连。此单元还包含一组16位寄存器,可通过APB1总线对其进行读写操作(就是STM32对RTC的寄存器进行读写操作)。APB1接口由APB1总线时钟驱动,用来与APB1总线接口。

在这里插入图片描述
第二部分(RTC核心)由一组可编程计数器组成,分成两个主要模块。

第一个模块:
RTC的预分频模块,它可编程产生最长为1秒的RTC时间基准TR_CLK。RTC的预分频模块包含了一个20位的可编程分频器(RTC预分频器)。如果在RTC_CR寄存器中设置了相应的允许位,则在每个TR_CLK周期中RTC产生一个中断(秒中断)。

认真看完下图,就知道各个寄存器的作用了
在这里插入图片描述

  • 预分频装载寄存器 RTC_PRL

在这里插入图片描述

  • RTC分频器余数寄存器

在这里插入图片描述
该寄存器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新装载。这两个寄存器和 RTC 预分频装载寄存器位数是一样的。也就是说,如果预分频装载寄存器的值为32767,那么余数寄存器就会在每一次秒更新时由硬件重新装载为32767,然后向下计数,计数到0表示一秒,也即1000ms。
获取毫秒时间:
先用下面库函数获取RTC_DIV的值
在这里插入图片描述
毫秒时间:( 32767-RTC_GetDivider() )/32767*1000;
计算方式就是每个计数32767为1000ms也就是1秒钟,32767-RTC_DIV的值就等于计数了多少次。

第二个模块:
一个32位的可编程计数器,可被初始化为当前的系统时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比较,如果RTC_CR控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断。

它的计数器RTC_CNT 的 32 位由 RTC_CNTL 和 RTC_CNTH 两个寄存器组成,分别保存定时计数值的低 16 位和高 16 位。在配置 RTC 模块的时钟时,通常把输入的 32768Hz 的 RTCCLK 进行32768 分频得到实际驱动计数器的时钟 TR_CLK = RTCCLK/32768= 1 Hz,计时周期为 1 秒,计时器在 TR_CLK 的驱动下计数,即每秒计数器 RTC_CNT 的值加 1。

  • RTC计数器寄存器RTC_CNT

在这里插入图片描述
一个 32 位的时钟计数器,按秒钟计算,可以记录 4294967296 秒,
约合 136 年左右,作为一般应用,这已经是足够了。

  • RTC闹钟寄存器RTC_ALR

在这里插入图片描述

  • RTC中断

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

2.使能对后备寄存器和RTC的访问

RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。
系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作将使能对后备寄存器和RTC的访问:

● 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
在这里插入图片描述

● 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问
这就是为什么要使能电源接口时钟,因为要配置它的寄存器
在这里插入图片描述

3.复位过程

除了RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器外,所有的系统寄存器都由系统复位或电源复位进行异步复位。
RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器仅能通过备份域复位信号复位。

备份域复位
备份区域拥有两个专门的复位,它们只影响备份区域(见图4)。
当以下事件中之一发生时,产生备份区域复位。

  1. 软件复位,备份区域复位可由设置备份域控制寄存器 (RCC_BDCR)(见6.3.9节)中的BDRST位产生。

  2. 在VDD和VBAT两者掉电的前提下(主电源与备份电源(纽扣电池)都掉电),VDD或VBAT上电将引发备份区域复位
    在这里插入图片描述

4.读RTC寄存器

RTC核完全独立于RTC APB1接口。软件通过APB1接口访问RTC的预分频值、计数器值和闹钟值但是相关的可读寄存器只在与RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。RTC标志也是如此的。这意味着,如果APB1接口曾经被关闭,而读操作又是在刚刚重新开启APB1之后,则在第一次的内部寄存器更新之前,从APB1上读出的RTC寄存器数值可能被破坏了(通常读到0)。

意思是在第一次通过 APB1 接口访问 RTC 时,因为时钟频率的差异(APB1的时钟频率比RTC的时钟频率高的多),所以必须等待 APB1 与 RTC 外设同步,确保被读取出来的 RTC 寄存器值是正确的。若在同步之后,一直没有关闭 APB1 的 RTC 外设接口,就不需要再次同步了。

下述几种情况下能够发生这种情形:
● 发生系统复位或电源复位
● 系统刚从待机模式唤醒
● 系统刚从停机模式唤醒(参见第4.3节:低功耗模式)。
所有以上情况中,APB1接口被禁止时(复位、无时钟或断电)RTC核仍保持运行状态。
因此,若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置’1’
在这里插入图片描述

5.配置RTC寄存器

1.必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器。

2.对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。
可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是’1’时,才可以写入RTC寄存器。

配置过程:

  1. 查询RTOFF位,直到RTOFF的值变为’1’
    在这里插入图片描述

  2. 置CNF值为1,进入配置模式
    在这里插入图片描述

  3. 对一个或多个RTC寄存器进行写操作

  4. 清除CNF标志位,退出配置模式

  5. 查询RTOFF,直至RTOFF位变为’1’以确认写操作已经完成。
    仅当CNF标志位被清除时(要退出配置模式写操作才开始),写操作才能进行,这个过程至少需要3个RTCCLK周期

但是写RTC_PRL、RTC_CNT、RTC_ALR库函数自动带了进入配置模式写完退出配置模式,所以我们写下一个寄存器之前一定要等待上一次写操作完成
在这里插入图片描述

三.实现一个简易时钟

1.实验目的

在屏幕上显示当前时间日期(年月日),和时分秒。

2.实验原理

1.UNIX 时间戳

大多数操作系统都是利用时间戳和计时元年来计算当前时间的,而这个时间戳和计时元年大家都取了同一个标准——UNIX 时间戳和 UNIX 计时元年。

定时器被置 0 的这个时间被称为计时元年,相对计时元年经过的秒数称为时间戳,也就是计数器中的值。UNIX 计时元年被设置为格林威治时间 1970 年 1 月 1 日 0 时 0 分 0 秒,大概是为了纪念 UNIX 的诞生的时代吧,而UNIX 时间戳即为当前时间相对于 UNIX 计时元年经过的秒数。

通俗点讲:在1970年 1 月 1 日 0 时 0 分 0 秒,计数器寄存器RTC_CNT的值为0,我们需要先将当前时间与1970年 1 月 1 日 0 时 0 分 0 秒的差值算出经过了多少秒钟,将这个值写入计数器寄存器中,当使能RTC时钟的时候则则计数器从当前时间开始计时,计数器的值会每隔一秒加1,我们只需一直读取计数器寄存器中的值将它秒钟转化为实时时间(从1970年1月1日0时0分0秒开始计算)。再将转化的时间显示到屏幕上这样就实现了一个简易的时钟。

主要的任务就是将计数器的秒钟数换算成当前时间(主要考虑闰年与非闰年)
相当于1970 1 月 1 日 0 时 0 分 0 秒 ,计数器寄存器中的值为0,然后当前去读取计数器寄存器的值,就能获得一共经历了多少秒,然后换算成当前时间。

RCT外设配置过程

1.使能PWR和BKP时钟:RCC_APB1PeriphClockCmd();
2.使能后备寄存器访问: PWR_BackupAccessCmd();
1、2步使能对后备寄存器和RTC的访问。
3.配置RTC时钟源,使能RTC时钟:
1)开启LSE时钟
RCC_LSEConfig(RCC_LSE_ON);
2)等待LSE使能完成
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) ==RESET)
3)LSE作为RTC的时钟源
RCC_RTCCLKConfig();
4)使能RTC时钟
RCC_RTCCLKCmd();

在这里插入图片描述

4.设置RTC预分频系数:RTC_SetPrescaler();
5. 设置时间:RTC_SetCounter();
6.开启相关中断(如果需要):RTC_ITConfig();
7.编写中断服务函数:RTC_IRQHandler();
8.部分操作要等待写操作完成和同步。
RTC_WaitForLastTask();//等待最近一次对RTC寄存器
的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步

3.实验源码

rtc.c

#include "rtc.h"

_calendar_obj calendar;//时钟结构体 
 
static void RTC_NVIC_Config(void)
{
    
    	
  NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;		//RTC全局中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;		//使能该通道中断
	NVIC_Init(&NVIC_InitStructure);		
}

void RTC_Init(void)
{
    
    
	//使能电源和后备接口时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP,ENABLE);
	//使能对后备寄存器和RTC的访问
	PWR_BackupAccessCmd(ENABLE);
	if( BKP_ReadBackupRegister(BKP_DR1) !=0x5050 )
	{
    
    
		//复位备份区域
		BKP_DeInit();
		//开启LSE时钟
		RCC_LSEConfig(RCC_LSE_ON);
		//等待就绪
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) ==RESET)
		//LSE作为RTC的时钟源	
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
		//使能RTC时钟
    RCC_RTCCLKCmd(ENABLE);
		//等待时钟同步
		RTC_WaitForSynchro();
		//等待上一次写操作完成
		RTC_WaitForLastTask();	
		//使能RTC秒中断
		RTC_ITConfig(RTC_IT_SEC, ENABLE);	
		//等待最近一次对RTC寄存器的写操作完成
		RTC_WaitForLastTask();	
		//进入配置模式
		RTC_EnterConfigMode();
		//设置分频
		RTC_SetPrescaler(32767);
		//等待上一次写操作完成
		RTC_WaitForLastTask();
		//配置时间
    RTC_Set(2022,8,14,15,00,59);
		//退出配置模式
		RTC_ExitConfigMode();
		
		BKP_WriteBackupRegister(BKP_DR1,0x5050 );
	}
	else
	{
    
    
		RTC_WaitForSynchro();	//等待最近一次对RTC寄存器的写操作完成
		RTC_ITConfig(RTC_IT_SEC, ENABLE);	//使能RTC秒中断
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
	}
	RTC_NVIC_Config();
	RTC_Get();//更新时间	
}

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


//判断是否是闰年函数
//月份   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寄存器的写操作完成  	
	RTC_Get();
	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);
}			  

rtc.h

#ifndef __RTC_H

#define __RTC_H

#include "stm32f10x.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;	//日历结构体
extern u8 const mon_table[12];	//月份日期数据表
void 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 /* __RTC_H */


main.c

#include "stm32f10x.h"
#include "./usart/bsp_usart.h"	
#include "./lcd/bsp_ili9341_lcd.h"
#include "./flash/bsp_spi_flash.h"
#include "rtc.h"

static void Delay ( __IO uint32_t nCount );



int main(void)
{
    
    	
	//LCD 初始化
	ILI9341_Init ();         

	/* USART config */
	USART_Config();  
	RTC_Init();

 //其中0、3、5、6 模式适合从左至右显示文字,
 //不推荐使用其它模式显示文字	其它模式显示文字会有镜像效果			
 //其中 6 模式为大部分液晶例程的默认显示方向  
	ILI9341_GramScan ( 6 );
	LCD_SetFont(&Font8x16);
	LCD_SetColors(RED,BLACK);
	ILI9341_Clear(0,0,LCD_X_LENGTH,LCD_Y_LENGTH);	/* 清屏,显示全黑 */
	while ( 1 )
	{
    
    
		char dispBuff_T1[100];
		char dispBuff_T2[100];
//				ILI9341_Clear(0,0,LCD_X_LENGTH,LCD_Y_LENGTH);	/* 清屏,显示全黑 */
		sprintf(dispBuff_T1,"%0.2d-%0.2d-%0.2d",calendar.w_year,calendar.w_month,calendar.w_date);
//  ILI9341_Clear(8*5,LINE(8),LCD_X_LENGTH-8*5,HEIGHT_CH_CHAR);	
        //显示年月日
		ILI9341_DispString_EN_CH(8*5,LINE(7),dispBuff_T1);
		

		sprintf(dispBuff_T2,"%0.2d-%0.2d-%0.2d",calendar.hour,calendar.min,calendar.sec);
//  ILI9341_Clear(8*5,LINE(8),LCD_X_LENGTH-8*5,HEIGHT_CH_CHAR);	
        //显示时分秒
		ILI9341_DispString_EN_CH(8*5,LINE(8),dispBuff_T2);
		
		switch(calendar.week)
		{
    
    
			case 0:
			ILI9341_DispString_EN_CH(8*5,LINE(9),"星期日");
			break;
			case 1:
			ILI9341_DispString_EN_CH(8*5,LINE(9),"星期一");
			break;
			case 2:
			ILI9341_DispString_EN_CH(8*5,LINE(9),"星期二");
			break;
			case 3:
			ILI9341_DispString_EN_CH(8*5,LINE(9),"星期三");
			break;
			case 4:
			ILI9341_DispString_EN_CH(8*5,LINE(9),"星期四");
			break;
			case 5:
			ILI9341_DispString_EN_CH(8*5,LINE(9),"星期五");
			break;
			case 6:
			ILI9341_DispString_EN_CH(8*5,LINE(9),"星期六");
			break;

			default:
				break;
		}
	}
	
	
}

主要是将秒钟数换算成时间,时间换算成秒钟数,其实就是注意一下闰年与非闰年,看一遍程序应该懂了。

以下来自百度:

闰年产生原因:
最根本的原因是:地球绕太阳运行的周期为365天5小时48分46秒(合365.24219天),即一回归年(tropical year)。公历的平年只有365天,比回归年短约0.2422天,所余下的时间约为每四年累积一天,故在第四年的2月末加1天,使当年的时间长度变为366天,这一年就是闰年。现行公历中每400年有97个闰年。按照每四年一个闰年计算,平均每年就要多算出0.0078天,这样,每128年就会多算出1天,经过400年就会多算出3天多。因此,每400年中要减少3个闰年。所以公历规定:年份是整百数时,必须是400的倍数才是闰年;不是400的倍数的世纪年,即使是4的倍数也不是闰年。
这就是通常说的:四年一闰,百年不闰,四百年再闰。例如:2000年是闰年,2100年则是平年。

普通闰年:公历年份是4的倍数,且不是100的倍数的,为闰年(如2004年、2020年等就是闰年)。
世纪闰年:公历年份是整百数的,必须是400的倍数才是闰年(如1900年不是闰年,2000年是闰年)

闰年首先要是4的倍数,但不能是100的倍数
如果是100的倍数,必须要是400的倍数才是闰年

公历只分闰年和平年,平年有365天,闰年有366天(2月中多一天:29天)


//判断是否是闰年函数
//月份   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;	
}	 	

4.效果演示

请添加图片描述

猜你喜欢

转载自blog.csdn.net/k666499436/article/details/126328247