STM32 F103 RTC实验

实验目的

本实验旨在掌握STM32的实时时钟RTC的使用,利用其测量日期时间,数据手册请参看16章。

实验简介

STM32的实时时钟(RTC)是一个独立的定时器。STM32的RTC模块拥有一组连续计数的计数器,在相应软件配置下,可以提供时钟日历的功能,修改计数器的值可以重新设置系统当前的时间和日期。

RTC模块和时钟配置系统(RCC_BDCR寄存器)是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变

系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前,先要取消备份区域(BKP)写保护,

STM32的RTC具有掉电继续运行的特性,在主电源VDD断开时,为了RTC外设掉电继续运行,必须给STM32芯片通过VBAT引脚接上锂电池,当主电源VDD有效时,由VDD给RTC外设供电。当VDD掉电后,由VBAT给RTC外设供电,但无论由什么电源供电,RTC中的数据都保存在属于RTC的备份区域中,若主电源VDD和VBAT都掉电,那么备份域中保存的所有数据将丢失。备份域除了RTC模块的寄存器,还有42个16位的寄存器可以在VDD掉电的情况下保存用户程序的数据,系统复位或电源复位时,这些数据也不会复位。

从RTC的定时器特性来说,它是一个32位的计数器,只能向上计数。它使用的时钟源有三种,分别为高速外部时钟的128分频:HSE/128;低速内部时钟LSI;使用HSE分频时钟或LSI的话,在主电源VDD掉电的情况下,这两个时钟源都会受到影响,因此没法保证RTC正常工作。因此RTC一般使用低速外部时钟LSE,频率为实时时钟模块中常用的32.768KHz,这是因为32768=2的15次方,分频容易实现,所以它被广泛应用到RTC模块。在主电源VDD有效的情况下(待机),RTC还可以配置闹钟事件使STM32退出待机模式
在这里插入图片描述

HAL库代码

main.c

#include "MyIncludes.h"

u16 sys_cnt = 0;
void systick_isr(void)
{
  if(sys_cnt <100 )
  sys_cnt++;
  else
  {
    sys_cnt = 0;
    HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_4|GPIO_PIN_5);
  }
}

void rtc_disp(void)
{
  char date[15];
  //用来存储日期
  char time[15];
  //用来存储时间
  sprintf((char*)date,"20%d%d/%d%d/%d%d ",Calendar.RTC_Year%10,Calendar.RTC_Year%10,Calendar.RTC_Mon/10,Calendar.RTC_Mon%10,Calendar.RTC_Mday/10,Calendar.RTC_Mday%10);
  //sprintf函数打印到字符串中(将数值转换成对应字符串形式,就是变换成ASCALL码)

  sprintf((char*)time,"%d%d:%d%d:%d%d\n",Calendar.RTC_Hour/10,Calendar.RTC_Hour%10,Calendar.RTC_Min/10,Calendar.RTC_Min%10,Calendar.RTC_Sec/10,Calendar.RTC_Sec%10);

printf(date);
//发送日期
printf(time);
//发送时间

  
}

int main()
{
  System_Init();
  //系统初始化
  LED_Init();
  //LED初始化
  SysTick_Init(systick_isr);
  //SysTick初始化
  USART1_Init(115200,NULL,NULL);
  //串口1初始化  波特率:115200
  //发送完成回调为空   接收回调为空
  RTC_Init(rtc_disp);
  //实时时钟初始化
  while(1)
  {
    RTC_Process();
    //RTC处理函数
  }
}

rtc.h

#ifndef __RTC_H
#define __RTC_H

#include "stm32f1xx.h"
#include "stm32_types.h"
#include "stm32f1xx_hal.h"

typedef struct
{
   u8 RTC_Sec;
   //秒
   u8 RTC_Min;
   //分
   u8 RTC_Hour;
   //时
   u8 RTC_Mday;
   //日
   u8 RTC_Mon;
   //月
   u8 RTC_Year;
   //年
   u8 RTC_Wday;
   //星期
}_Calendar_obj;
//日历结构体

extern _Calendar_obj Calendar;
//结构体声明

typedef struct
{
  void (*Operation)(void);
  //函数指针
}_RTC_Fun;

void RTC_SetDateTime(_Calendar_obj datatime);
//设置RTC的日期时间函数

_Calendar_obj RTC_GetDateTime(void);
//获取RTC日期时间函数

void RTC_Init(void(*rtc)(void));
//RTC初始化函数

void RTC_Process(void);
//RTC处理

#endif

rtc.c

#include "rtc.h"
_Calendar_obj Calendar;
//日历时钟结构体变量声明
RTC_HandleTypeDef RTCHandle;
//时间句柄结构体变量声明
_RTC_Fun rtc_fun;
//用户函数结构体变量声明

u8 const table_week[12] = {0,3,3,6,1,4,6,2,5,0,3,5};
//月修正表数据
/*假设1月1日是星期一,那么2月1日是星期四,(4-1=3),故是3
3月1日是星期四,(4-1=3),4月1日是星期日(7-1=6),依次类推,
前提这一年是平年
*/

u8 RTC_Get_Week(u16 year,u8 month,u8 day)
//作用算出周几
{
  u16 temp2;
  u8 yearH,yearL;

  yearH = year/100;
  yearL = year%100;
  if(yearH >19 )
  yearL+=100;
  //这几行算出本年距离1900年的差
  temp2 = yearL + yearL/4;
  //算出除了一年中除正周外累计多出来的天数     365%7=1   闰年的个数:366-365=1
  temp2 = temp2%7;
  //除去整周的天数
  temp2 = temp2+day+table_week[month-1];
  //现在temp2是除去整周的天数,加上本日的天数 在加上月修正表数据
  if(yearL%4 == 0 && month <3 )
  temp2--;
  //如果是闰年1月2月的话要减去1,因为yearl多出来的一天加上去了,也就是
  //2月29日,多出来的一天再三月份以后才能加
  return (temp2%7);
  //这个就是本周星期几
  /*在这里我们举个栗子吧:假设时间是2020年2月29日,那么首先yearH=20,
  yearl=20,因为yearH = 20 >19 那么yeal+=100  那么yeal = 120天  
  temp2 = yearL + yearL/4;   120+30 = 150 天, 150%7 = 3,  
  3+29+3 = 35  35-1=34  34%7=6  也就是星期六,我们看下日历确实
  是星期六*/
}

void RTC_SetDateTime(_Calendar_obj datetime)
//先看void RTC_Init(void (*rtc)(void))函数
//设置RTC的日期和时间
{
  RTC_DateTypeDef SetDate;
  //设置日期结构体声明
  RTC_TimeTypeDef SetTime;
  //设置时间结构体声明
  
  SetDate.Date = datetime.RTC_Mday;
  SetDate.Month = datetime.RTC_Mon;
  SetDate.Year = datetime.RTC_Year;
  //0~99
  SetDate.WeekDay = datetime.RTC_Wday;
  SetTime.Hours = datetime.RTC_Hour;
  SetTime.Minutes = datetime.RTC_Min;
  SetTime.Seconds = datetime.RTC_Sec;
  
  HAL_RTC_SetDate(&RTCHandle,&SetDate,RTC_FORMAT_BIN);
  //设置日期
  HAL_RTC_SetTime(&RTCHandle,&SetTime,RTC_FORMAT_BIN);
  //设置时间
  return ;
}

_Calendar_obj RTC_GetDateTime(void)
//先看void RTC_Process(void)
//获取RTC日期时间
{
   RTC_DateTypeDef SetDate;
   RTC_TimeTypeDef SetTime;
   _Calendar_obj datetime;

   HAL_RTC_GetTime(&RTCHandle,&SetTime,RTC_FORMAT_BIN);
   //获取时间
   HAL_RTC_GetDate(&RTCHandle,&SetDate,RTC_FORMAT_BIN);
   //获取日期
   datetime.RTC_Sec = SetTime.Seconds;
   datetime.RTC_Min = SetTime.Minutes;
   datetime.RTC_Hour = SetTime.Hours;
   datetime.RTC_Mday = SetDate.Date;
   datetime.RTC_Mon = SetDate.Month;
   datetime.RTC_Year = SetDate.Year;
   datetime.RTC_Wday = SetDate.WeekDay;

   return datetime;
}

void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
//在HAL_RTC_Init中调用
{
  RCC_OscInitTypeDef RCC_OscInitStruct;
  //RCC内部/外部振荡器(HSE、HSI、LSE和LSI)配置结构变量声明
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
  //RCC扩展时钟结构变量声明
  __HAL_RCC_PWR_CLK_ENABLE();
  //备份电源使能

  HAL_PWR_EnableBkUpAccess();
  //电源使能唤醒


  __HAL_RCC_BKP_CLK_ENABLE();
    //使能BKP时钟
   //配置时钟源
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
  //要配置的振荡器   
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  //PLL的状态   未配置
  RCC_OscInitStruct.LSEState = RCC_LSE_ON;
  //振荡器状态  振荡器开启
  HAL_RCC_OscConfig(&RCC_OscInitStruct);
  //RCC配置
 
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
  //要配置的拓展时钟  外部时钟 0x0000001
  PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
  //指定RTC时钟源  用作RTC时钟的LSE振荡器时钟
  HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
//配置拓展时钟
  __HAL_RCC_RTC_ENABLE();
 // 使能RTC
 }

void RTC_Init(void (*rtc)(void))
{
 _Calendar_obj DateTime;
 //日历结构体变量声明

 RTCHandle.Instance = RTC;
 //寄存器基址 RTC
 RTCHandle. Init.AsynchPrediv = RTC_AUTO_1_SECOND;
 //指定BIC  异分频系数    0xffffffff  自动获取1s时机基
 RTCHandle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
 //指定那个信号路由到RTC针   0x00000000
 //篡改针上没有输出
 HAL_RTC_Init(&RTCHandle);
 //初始化RTC参数
 
 DateTime.RTC_Hour = 23;
 DateTime.RTC_Min = 59;
 DateTime.RTC_Sec = 50;
 DateTime.RTC_Year = 16;
 DateTime.RTC_Mon = 8;
 DateTime.RTC_Mday = 21;
 DateTime.RTC_Wday = RTC_Get_Week(DateTime.RTC_Year+2000,DateTime.RTC_Mon,DateTime.RTC_Mday);
 //2016年8月21日23时59分50秒
 RTC_SetDateTime(DateTime);
 //设置RTC的时间和日期
 rtc_fun.Operation = rtc;

 return ;
}

u8 last_sec = 0;
void RTC_Process(void)
//RTC处理
{
  Calendar = RTC_GetDateTime();
  if(last_sec != Calendar.RTC_Sec)
  {
    last_sec = Calendar.RTC_Sec;
    if(rtc_fun.Operation != NULL)
    rtc_fun.Operation();
    //每一秒刷新一次
  }
}

寄存器代码

main.c

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "key.h"
#include "rtc.h"


int main(void)
{
	char t[20];
	Stm32_Clock_Init(9);	//ϵͳʱÖÓÉèÖÃ
	uart_init(72,115200);	//´®¿Ú³õʼ»¯Îª115200
	delay_init(72);	   	 	//ÑÓʱ³õʼ»¯ 
	LED_Init();		  		//³õʼ»¯ÓëLEDÁ¬½ÓµÄÓ²¼þ½Ó¿Ú
	while(RTC_Init())
	{
		
	}
	
	while(1)
	{
		
	
				LED2=!LED2;
		delay_ms(100);
		  sprintf((char*)t,"%d:%d:%d\r\n",calendar.w_year,calendar.w_month,calendar.w_date);
		  printf(t);
		
		
		
		
	}
}

RTC.h

#ifndef __RTC_H
#define __RTC_H	  
#include "sys.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 失败返回1 成功返回0       					
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);
//设置时间		
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
//设置闹钟		 
#endif

RTC.C

#include "delay.h"
#include "usart.h"
#include "rtc.h" 	
#include "LED.h"

	   
_calendar_obj calendar;
//时钟结构体
   

u8 RTC_Init(void)
{
	
	u8 temp=0;
	//检查是不是第一次配置时钟
	if(BKP->DR1!=0X5050)//第一次配置
	{	 
	  	RCC->APB1ENR|=1<<28;  
	  	//使用电源时钟  
		RCC->APB1ENR|=1<<27;   
		//使能备份时钟  
		PWR->CR|=1<<8;  
		//取消备份区写保护        
		RCC->BDCR|=1<<16;   
		//备份区域软复位      
		RCC->BDCR&=~(1<<16);  
		//备份区域软复位结束  	 
	    RCC->BDCR|=1<<0; 
	    //开启外部低速振荡器       
	    while((!(RCC->BDCR&0X02))&&temp<250)
	    //等待外部时钟就绪	 
		{
			temp++;
			delay_ms(10);
		};
		
	
		
		if(temp>=250)return 1;	
		//初始化时钟失败,晶振有问题   
		RCC->BDCR|=1<<8;
		//LSE作为RTC时钟
		RCC->BDCR|=1<<15;
		//RTC时钟使能
	  	while(!(RTC->CRL&(1<<5)));
	  	//等待RTC寄存器操作完成
    	while(!(RTC->CRL&(1<<3)));
    	//等待RTC寄存器同步
    	RTC->CRH|=0X01;
    	//允许秒中断  		
    	RTC->CRH|=0X02;
    	//允许闹钟中断  		  
    	while(!(RTC->CRL&(1<<5)));
    	//等待RTC寄存器操作完成
		RTC->CRL|=1<<4; 
		//允许配置        
		RTC->PRLH=0X0000;
		RTC->PRLL=32767;  
		//时钟周期设置    
		RTC_Set(2015,1,14,17,42,55); 
		//设置时间
		RTC->CRL&=~(1<<4); 
		//配置更新         
		while(!(RTC->CRL&(1<<5))); 
		//等待RTC寄存器操作完成 	 									  
		BKP->DR1=0X5050;  
	 	printf("FIRST TIME\n");
	}else//系统继续计时
	{
    	while(!(RTC->CRL&(1<<3)));
    	//等待RTC寄存器同步
    	RTC->CRH|=0X01;  
    	//允许秒中断		  
    	while(!(RTC->CRL&(1<<5)));
    	//等待RTC寄存器操作完成
		printf("OK\n");
	}		    				  
	MY_NVIC_Init(0,0,RTC_IRQn,2);
	//优先级设置   
	RTC_Get();
	//更新时间;
	return 0; 
}		 				    


//RTC时钟中断
//每秒触发一次	 
void RTC_IRQHandler(void)
{		 
	if(RTC->CRL&0x0001)	
	//秒中断		
	{							
		RTC_Get();
		//更新时间				
		
 	}
	if(RTC->CRL&0x0002)	
	//闹钟中断		
	{
		RTC->CRL&=~(0x0002);
		//清除闹钟中断	 
		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->CRL&=0X0FFA;
    //清除溢出,秒钟中断标志         	
	while(!(RTC->CRL&(1<<5)));
	//等待RTC寄存器操作完成		    						 	   	 
}


//判断是否是不是闰年 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;	
}	 			   
										 
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};
//平年得月份日期表

//RTC设置
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->APB1ENR|=1<<28;
    //使能电源时钟
    RCC->APB1ENR|=1<<27;
    //使能备份时钟
	PWR->CR|=1<<8;    
	//取消备份区写保护
	
	RTC->CRL|=1<<4;  
	//允许配置
	
	RTC->CNTL=seccount&0xffff;
	RTC->CNTH=seccount>>16;
	RTC->CRL&=~(1<<4);
	//配置更新
	while(!(RTC->CRL&(1<<5)));
	//等待RTC寄存器操作完成
	RTC_Get();
	//设置完之后更新一下数据
	return 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->APB1ENR|=1<<28;//使能电源时钟
    RCC->APB1ENR|=1<<27;//使能备份时钟
	PWR->CR|=1<<8;    //取消备份区写保护
	
	RTC->CRL|=1<<4;  //允许配置
	RTC->ALRL=seccount&0xffff;
	RTC->ALRH=seccount>>16;
	RTC->CRL&=~(1<<4);//配置更新
	while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成
	return 0;	    
}

//得到当前时间
u8 RTC_Get(void)
{
	static u16 daycnt=0;
	u32 timecount=0; 
	u32 temp=0;
	u16 temp1=0;	  
 	timecount=RTC->CNTH;
 	//得到计数器中的值(秒钟数)
	timecount<<=16;
	timecount+=RTC->CNTL;			 

 	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;
}	 

//获得现在是星期几																				 
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);
}			  

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值