STM32RTC外设详解

news2024/11/24 9:19:15

目录

  • 一.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.coloradmin.cn/o/157577.html

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

相关文章

链表题目总结 -- 双指针技巧

文章目录一. 合并两个有序链表1. 思路简述2. 代码3. 总结二. 分隔链表1. 思路简述2. 代码3. 总结三. 合并K个升序链表1. 思路简述2. 代码3. 总结四. 单链表的倒数第 k 个节点1. 思路简述2. 代码3. 总结五. 链表的中间结点1. 思路简述2. 代码3. 总结六. 环形链表&#xff08;链表…

docker一个容器内部署多个服务

原因是&#xff0c;我有一个springBoot服务需要写入httpd的目录&#xff0c;然后httpd提供链接给别人下载。之前的方法是&#xff0c;httpd和springBoot各一个容器&#xff0c;但是我们将镜像是部署在腾讯云上的&#xff0c;腾讯云会自动对每个容器分离不同的虚拟机&#xff0c…

pytorch简单自定义Datasets

前言 本文记录一下如何简单自定义pytorch中Datasets&#xff0c;官方教程文件层级目录如下&#xff1a; images 1.jpg2.jpg…9.jpg annotations_file.csv 数据说明 image文件夹中有需要训练的图片&#xff0c;annotations_file.csv中有2列&#xff0c;分别为image_id和labe…

Python的23种设计模式(完整版带源码实例)

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; Python的23种设计模式 一 什么是设计模式 设计模式是面对各种问题进行提炼和抽象而形成的解决方案。这些设计方案是前人不断试验&…

【入门篇】1 # 复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?

说明 【数据结构与算法之美】专栏学习笔记。 什么是复杂度&#xff1f; 复杂度也叫渐进复杂度&#xff0c;包括时间复杂度和空间复杂度&#xff0c;用来分析算法执行效率与数据规模之间的增长关系&#xff0c;可以粗略地表示&#xff0c;越高阶复杂度的算法&#xff0c;执行…

时域脉冲通信采用高斯脉冲且使用PAM调制的Matlab简易演示仿真

时域脉冲通信采用高斯脉冲且使用PAM调制的Matlab简易演示仿真 环境 matlab 2016a 指标 1 将声音信号转为二进制码 2 PAM调制 3 采用高斯脉冲 流程 代码 [OriginVoice,fs]audioread(voice.m4a) ; OriginVoiceOriginVoice(:,2); Nlength(OriginVoice); % 计算信号x的长度 …

算法训练营 day15 二叉树 层序遍历 翻转二叉树 对称二叉树

算法训练营 day15 二叉树 层序遍历 翻转二叉树 对称二叉树 层序遍历 102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。…

标签设计打印软件:LabelJoy 6.23.0 Crack

LabelJoy 专业条码软件 生成25种条形码 从数据源导入条码 计算自动校验 商业条形码标签软件 兼容 Excel、Access、MySQL、Oracle 11.000 个预装的纸张布局 支持任何打印机 通过 3 个步骤创建和打印标签&#xff1a; 选择布局 创建您的标签 开始打印 最好的标签打印软件&#xf…

kafka-1

文章目录1.启动2.创建主题3.发送消息4.消费消息5.使用kafka connect将现有的数据导入到kafka中6.使用kafka streams处理kafka中的events6.终止服务集群配置要点创建主题要点主题分区变更主题副本可变更吗&#xff1f;创建生产者要点> tar -xzf kafka_2.12-3.3.1.tgz1.启动 …

Mac生成和查看ssh key

从 git 上拉取或者提交代码每次都需要输入账号密码&#xff0c;这样很麻烦。我们可以在电脑上生成一个 ssh key&#xff0c;然后把ssh key添加到 git 中&#xff0c;就可以不用每次去输账号密码了。下面就介绍一下怎么在自己的 Mac 中生成和查看 ssh key。一、Mac生成SSH Key打…

【环境】idea远程debug

工作中&#xff0c;遇到问题的时候&#xff0c;想知道上下文中对应的参数值是什么&#xff1f;这时候&#xff0c;1、我们可以全靠逻辑分析。费脑&#xff0c;不一定对。2、打印日志&#xff0c;打印的信息不一定全&#xff0c;换包&#xff0c;麻烦3、远程debug。 1、配置ide…

pytorch二维码识别

二维码图片的生成 利用captcha可以生成二维码图片 # -*- coding: UTF-8 -*- from captcha.image import ImageCaptcha # pip install captcha from PIL import Image import random import time import os # 验证码中的字符 # string.digits string.ascii_uppercase NUMBER…

整理了一周近万字讲解linux基础开发工具vim,gdb,gcc,yum等的使用

文章目录 前言一、yum的使用二、vim的使用三 . gcc/g的使用四 . gdb的使用总结前言 想用linux开发一些软件等必须要会的几种开发工具是必不可少的&#xff0c;在yum vim gcc gdb中指令繁杂的是vim和gdb这两个工具&#xff0c;至于yum和gcc的指令就比较简单了。 一、yum的使用…

【SpringMVC】拦截器

目录 一、概念 二、自定义拦截器的三个实现方法 三、自定义拦截器执行流程 四、使用 五、拦截器和过滤器 相关文章&#xff08;可以关注我的SpringMVC专栏&#xff09; SpingMVC专栏SpingMVC专栏一、概念在学习拦截器之前&#xff0c;我们得先了解一下它是个什么❓ SpringMVC…

SAP ABAP调用标准事务码

这里介绍常见的几种在开发中常用到的事务代码跳转功能。 1、最常用到的是“SET PARAMETER”语句赋值&#xff0c;然后再使用“CALL TRANSACTION”语句跳转屏幕。 比如采购订单、销售订单、交货单、采购发票、销售发票等事务代码&#xff0c;均可以利用给参数赋值来直接跳转&am…

零售及仓储数字化整理解决方案

价格管控 皮克价格管控方案可实现门店与企业信息管理平台的数据同步&#xff0c;强化零售企业对终端的控制。同时为企业销售决策提供支持&#xff0c;优化门店经营活动的效率和频率。陈列管理 皮克陈列管理方案通过电子价签产品使商品陈列得到固化。 同时实现了陈列可视化&am…

ArcGIS水文分析提取河网及流域

在进行某些研究或者一些论文插图显示的时候&#xff0c;有时我们会碰到在部分资料中找不到一些小的河流或者流域的数据的情况&#xff0c;这里讲述通过DEM数据生成河网及流域。 一、数据来源 四川省高程数据来源于中国科学院资源环境科学与数据中心&#xff08;中国科学院资源环…

Vue3学习之深度剖析CSS Modules和Scope

Css Modules 是通过对标签类名进行加装成一个独一无二的类名&#xff0c;比如.class 转换成.class_abc_123,类似于symbol&#xff0c;独一无二的键名 Css Scope 是通过为元素增加一个自定义属性&#xff0c;这个属性加上独一无二的编号&#xff0c;而实现作用域隔离。 原理 …

爬虫必备抓包工具——Fiddler【认识使用】

目录&#xff1a;1.fiddler &#xff08;抓包工具&#xff09;1.1 引入&#xff1a;HTTP/https代理&#xff08;正向代理&#xff09;1.2 拓展&#xff1a;反向代理&#xff1a;1.2 初识Fiddler①什么是抓包&#xff1f;抓包有什么用&#xff1f;②浅谈fiddler&#xff1a;③fi…

Unity_Skybox自定义插件可实现日夜更替Polyverse Skies | Low Poly

又又一个天空盒,不过这个做的还是比较完善的。。。不会出现买家秀和买家秀差别大问题 此Skybox插件特色提供: 可扩展,自定义很多的Skybox Shader预制体几个,虽然都是夜晚样式(缺白天)若干预设值</