STM32RTC外设详解

news/2024/12/21 16:24:20/

目录

  • 一.RTC 实时时钟简介
    • 1.RTC时钟来源
    • 2.RTC主要特性
  • 二.RTC 外设功能框图
    • 1.RTC功能框图剖析
    • 2.使能对后备寄存器和RTC的访问
    • 3.复位过程
    • 4.读RTC寄存器
    • 5.配置RTC寄存器
  • 三.实现一个简易时钟
    • 1.实验目的
    • 2.实验原理
    • 3.实验源码
    • 4.效果演示

一.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.效果演示

请添加图片描述


http://www.ppmy.cn/news/11727.html

相关文章

C语言-自定义类型-枚举和联合(11.3)

目录 思维导图&#xff1a; 1.枚举 1.1 枚举类型的定义 1.2 枚举的优点 1.3 枚举的使用 2. 联合&#xff08;共用体&#xff09; 2.1 联合类型的定义 2.2 联合的特点 2.3 联合大小的计算 写在最后&#xff1a; 思维导图&#xff1a; 1.枚举 1.1 枚举类型的定义 例&…

蓝桥杯省赛习题练习(二)

题目来源&#xff1a;2020年真题题集&#xff08;B组&#xff09; 注&#xff1a;代码都是自己写的&#xff0c;不是参考答案&#xff01; 目录1. 门牌制作运行结果2. 既约分数运行结果3. 蛇形填数运行结果4. 跑步锻炼运行结果5. 7段码6. 成绩统计运行结果7. 回文日期运行结果1…

【OpenAI】基于 Gym-CarRacing 的自动驾驶项目 | 车道检测功能的实现 | 边缘检测与分配 | 样条拟合

限时开放&#xff0c;猛戳订阅&#xff01; &#x1f449; 《一起玩蛇》&#x1f40d; &#x1f4ad; 写在前面&#xff1a; 本篇是关于多伦多大学自动驾驶专业项目的博客。GYM-Box2D CarRacing 是一种在 OpenAI Gym 平台上开发和比较强化学习算法的模拟环境。它是流行的 Box2D…

干货 | 涉疫数据的安全应用方案

以下内容整理自清华大学《数智安全与标准化》课程大作业期末报告同学的汇报内容。第一部分&#xff1a;涉疫数据分类及问题剖析一、涉疫数据分类我们以新冠肺炎疫情为例&#xff0c;构建数据图谱&#xff0c;将涉疫数据分为三个大类&#xff0c;八个小类&#xff0c;共分为50项…

Linux实用命令

进程相关当程序运行在系统上时&#xff0c;我们称之为进程&#xff08;process&#xff09;。想监测这些进程&#xff0c;需要熟悉 ps/top 等命令的用法。ps 命令好比工具中的瑞士军刀&#xff0c;它能输出运行在系统上的所有程序的许多信息。而 top 可以监控当前各个进程的运行…

vue报错汇总

项目场景&#xff1a; 使用vue报错汇总。 1、项目启动不报错也不成功 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 项目启动时&#xff0c;一直启动不成功&#xff0c;末句提示 98% emitting Copyplugin… 原因分析&#xff1a; 最有可能是因为require或者import了…

JAVA01_11学习总结(MyBatis-优化)

今日内容 1. MyBatis日志 MyBatis日志日志是我们纠察改错的有力工具1)导入日志的jar包--使用log4j2)配置日志的核心配置文件3)将日志输出并查看 2. 不适用MyBatis自带的连接池,使用德鲁伊 MyBatis的连接池来源-PooledDataSourceFactory是MyBatis自带的数据源工厂-无参构造,…

Tomcat 性能优化建议

修改Tomcat Connector运行模式为apr Tomcat Connector有三种运行模式&#xff1a; bio&#xff1a;阻塞IO bio是三种运行模式中性能最低第一种。 nio&#xff1a;是一个基于缓冲区&#xff0c;并能提供非阻塞I/O操作的JAVA API 因此NIO也成为非阻塞I/O&#xff0c;比bio拥有更…