STM32 F103C8T6学习笔记11:RTC实时时钟—OLED手表日历

之前在 学习笔记10文章 做了一个简易的,使用定时器计时的简单时钟,现在使用RTC实时时钟同步代替定时器来实现一下OLED手表日历,接着上个实验文章进行完善~~

文章提供源码、测试工程下载、测试效果图。

目录

 RTC实时时钟:

简介:

主要特性:

 RTC框图:

UNIX时间戳:

程序设计:

配置RTC初始化过程分为以下几步:

 RTC日历初始化相关代码:

主函数代码:

测试效果:

测试工程下载:


 RTC实时时钟:

简介:

 STM32F10x-中文参考手册 有关于RTC实时时钟的介绍是从 P308页开始的:

 RTC时钟与DS1302时钟芯片不同,DS1302时钟芯片是通过读取寄存器实现读取年月日等信息的

而RTC时钟是作为STM32F103单片机中的一个时钟定时器模块(其余系列不一定),主电源掉电后会继续使用   后备电池(由 Vbat 引脚接 电源 继续供电 )继续运行的模块,它本质是一个32位的向上计数器。

因此我们在STM32F103单片机中 读取RTC时 本质是得到一个计数值,对其进行处理。

主要特性:

 我们一般在LSE接一个 32.768k ( 2^15=32768 )的晶振作为RTC的时钟源,便于分频产生1HZ的时钟基准,

 RTC框图:

 我们从RTC的框图可以了解到,它的秒、闹钟都是有中断的,但溢出事件(计数到达最大值)时没有中断。

UNIX时间戳:

在设计到日历时,我们就需要注意这个时间戳:

程序设计:

首先注意一下这些头文件,都是需要用到的,别忘记了添加

#include "stm32f10x_rtc.h" //RTC相关库
#include "stm32f10x_pwr.h"  
#include "stm32f10x_bkp.h"

配置RTC初始化过程分为以下几步:

1.配置中断,配置中断优先级

2.检查寄存器BKP_DR1,根据其值确定是否为第一次上电,V BAT是否有后备电池,第一次上电就要初始化时间。(V BAT没电池 以及 V BAT有电池 但寄存器没被写入值都算第一次上电)(后备寄存器区由V BAT引脚供电,因此当V BAT引脚有电时主电源断不会使得后备寄存器区的寄存器BKP_DR1的值丢失 )

3.定义时间结构体,用来存放改变时间等.

4.复制编写RTC_Configuration()函数,配置相关时钟源,外部时钟还是内部,分频等。

5.编写Time_Adjust()函数,给RTC时钟附上初始值(通过将 小时、分钟和秒都转换成秒 加起来 来实现设置当前计数值)(小时是 24小时制)

此处需要注意一个小细节:

就是我的程序设计使用上没用到串口,因此没有初始化串口,但在移植官方代码时,他们使用串口打印测试,各阶段初始化情况,起初我保留了这些printf()语句,认为会跳过,但实际上程序会因为没有初始化串口而在printf那卡住~·

 RTC日历初始化相关代码:

#include "RTC.h"


void RTC_init(void)
{
   NVIC_InitTypeDef NVIC_Initstructure;
	
	/*1. NVIC 中断配置 */
  /*Configure one bit for preemption priority 中断分组*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
   /*Enable the RTC Interrupt */
		NVIC_Initstructure.NVIC_IRQChannel = RTC_IRQn;
		NVIC_Initstructure.NVIC_IRQChannelPreemptionPriority = 1;
		NVIC_Initstructure.NVIC_IRQChannelSubPriority = 0;
		NVIC_Initstructure.NVIC_IRQChannelCmd = ENABLE;
		NVIC_Init (&NVIC_Initstructure);	
	
		/*检查V BAT引脚是否为第一次上电(是否有后备电源)没有就要初始化时间*/
	   /*在启动时检查备份寄存器BKP_DR1,如果内容不是0xA5A5,
	  则需重新配置时间并询问用户调整时间*/
	if (BKP_ReadBackupRegister( BKP_DR1) != 0xA5A5)
	{
		//配置RTC与设置初值:
		RTC_Configuration();
		Time_Adjust(&time1);
		/*向BKP_DR1寄存器写入标志,说明RTC已在运行,只要后备有电,这个值就不会掉*/
		BKP_WriteBackupRegister( BKP_DR1, 0xA5A5);
	}
	
	else
	{
		/* 使能 PWR 和 Backup 时钟 */
	  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
		/* 允许访问 Backup 区域 */
	  PWR_BackupAccessCmd(ENABLE);		
	  /*LSE启动无需设置新时钟*/		
#ifdef RTC_CLOCK_SOURCE_LSI		
			/* 使能 LSI */
			RCC_LSICmd(ENABLE);
			/* 等待 LSI 准备好 */
			while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET)
			{}
#endif
		/*检查是否是系统掉电重启*/
		if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
		{ ;}
		/*检查是否Reset复位引脚引起的 复位*/
		else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
		{;}	
		/*等待寄存器同步*/
		RTC_WaitForSynchro();	
		/*允许RTC秒中断*/
		RTC_ITConfig(RTC_IT_SEC, ENABLE);	
		/*等待上次RTC寄存器写操作完成*/
		RTC_WaitForLastTask();
	}
	   /*定义了时钟输出宏,则配置校正时钟输出到PC13*/
	#ifdef RTCClockOutput_Enable
	/* 使能 PWR 和 Backup 时钟 */
	  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
	
	/* 允许访问 Backup 区域 */
	  PWR_BackupAccessCmd(ENABLE);
	
	  /* 禁止 Tamper 引脚 */
	  /* 要输出 RTCCLK/64 到 Tamper 引脚,  tamper 功能必须禁止 */	
	  BKP_TamperPinCmd(DISABLE); 
	
	  /* 使能 RTC 时钟输出到 Tamper 引脚 */
	  BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
	#endif
	  /* 清除复位标志 flags */
	  RCC_ClearFlag();
}


/*
 * 函数名:RTC_Configuration
 * 描述  :配置RTC
 * 输入  :无
 * 输出  :无
 * 调用  :外部调用
 */
void RTC_Configuration(void)
{
	/* 使能 PWR 和 Backup 时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
	/* 允许访问 Backup 区域 */
	PWR_BackupAccessCmd(ENABLE);	
	/* 复位 Backup 区域 */
	BKP_DeInit();	
//使用外部时钟还是内部时钟(在bsp_rtc.h文件定义)	
//使用外部时钟时,在有些情况下晶振不起振
//批量产品的时候,很容易出现外部晶振不起振的情况,不太可靠	
#ifdef 	RTC_CLOCK_SOURCE_LSE
	/* 使能 LSE */
	RCC_LSEConfig(RCC_LSE_ON);	
	/* 等待 LSE 准备好 */
	while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
	{}
	/* 选择 LSE 作为 RTC 时钟源 */
	RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);	
	/* 使能 RTC 时钟 */
	RCC_RTCCLKCmd(ENABLE);
	/* 等待 RTC 寄存器 同步
	 * 因为RTC时钟是低速的,内环时钟是高速的,所以要同步
	 */
	RTC_WaitForSynchro();	
	/* 确保上一次 RTC 的操作完成 */
	RTC_WaitForLastTask();	
	/* 使能 RTC 秒中断 */
	RTC_ITConfig(RTC_IT_SEC, ENABLE);
	/* 确保上一次 RTC 的操作完成 */
	RTC_WaitForLastTask();	
	/* 设置 RTC 分频: 使 RTC 周期为1s  */
	/* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) = 1HZ */
	RTC_SetPrescaler(32767); 	
	/* 确保上一次 RTC 的操作完成 */
	RTC_WaitForLastTask();	
#else
	/* 使能 LSI */
	RCC_LSICmd(ENABLE);
	/* 等待 LSI 准备好 */
	while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET){}	
	/* 选择 LSI 作为 RTC 时钟源 */
	RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);	
	/* 使能 RTC 时钟 */
	RCC_RTCCLKCmd(ENABLE);	
	/* 等待 RTC 寄存器 同步
	 * 因为RTC时钟是低速的,内环时钟是高速的,所以要同步
	 */
	RTC_WaitForSynchro();	
	/* 确保上一次 RTC 的操作完成 */
	RTC_WaitForLastTask();	
	/* 使能 RTC 秒中断 */
	RTC_ITConfig(RTC_IT_SEC, ENABLE);	
	/* 确保上一次 RTC 的操作完成 */
	RTC_WaitForLastTask();
	/* 设置 RTC 分频: 使 RTC 周期为1s ,LSI约为40KHz */
	/* RTC period = RTCCLK/RTC_PR = (40 KHz)/(40000-1+1) = 1HZ */	
	RTC_SetPrescaler(40000-1); 
	/* 确保上一次 RTC 的操作完成 */
	RTC_WaitForLastTask();
#endif
	
}
#ifndef _RTC_h_ 
#define _RTC_h_  

#include "headfire.h"

//使用LSE外部时钟 或 LSI内部时钟
//#define RTC_CLOCK_SOURCE_LSE      
#define RTC_CLOCK_SOURCE_LSI
//北京时间的时区秒数差
#define TIME_ZOOM						(8*60*60)

//初始化时间结构体
extern struct rtc_time time1;


void RTC_init(void);           //初始化 与 配置RTC
void RTC_Configuration(void);  //配置RTC


#endif

#include "RTC_day.h"

/*时间结构体,初始化默认时间 2023-08-21 17:55:55*/
struct rtc_time time1= {55,55,17,21,8,2023,1} ;//初始化时间结构体

uint16_t BMP_cnt,BMP_FLAG;

#define FEBRUARY		2
#define	STARTOFTIME		1970
#define SECDAY			86400L           /*  一天有多少s */
#define SECYR			(SECDAY * 365)
#define	leapyear(year)		((year) % 4 == 0)
#define	days_in_year(a) 	(leapyear(a) ? 366 : 365)
#define	days_in_month(a) 	(month_days[(a) - 1])

static int month_days[12] = {	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

/*
 * This only works for the Gregorian calendar - i.e. after 1752 (in the UK)
 */
 /*计算公历*/
void GregorianDay(struct rtc_time * tm)
{
	int leapsToDate;
	int lastYear;
	int day;
	int MonthOffset[] = { 0,31,59,90,120,151,181,212,243,273,304,334 };

	lastYear=tm->tm_year-1;

	/*计算从公元元年到计数的前一年之中一共经历了多少个闰年*/
	leapsToDate = lastYear/4 - lastYear/100 + lastYear/400;      

     /*如若计数的这一年为闰年,且计数的月份在2月之后,则日数加1,否则不加1*/
	if((tm->tm_year%4==0) &&
	   ((tm->tm_year%100!=0) || (tm->tm_year%400==0)) &&
	   (tm->tm_mon>2)) {
		/*
		 * We are past Feb. 29 in a leap year
		 */
		day=1;
	} else {
		day=0;
	}

	day += lastYear*365 + leapsToDate + MonthOffset[tm->tm_mon-1] + tm->tm_mday; /*计算从公元元年元旦到计数日期一共有多少天*/

	tm->tm_wday=day%7;
}

/* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
 * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
 * => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
 *
 * [For the Julian calendar (which was used in Russia before 1917,
 * Britain & colonies before 1752, anywhere else before 1582,
 * and is still in use by some communities) leave out the
 * -year/100+year/400 terms, and add 10.]
 *
 * This algorithm was first published by Gauss (I think).
 *
 * WARNING: this function will overflow on 2106-02-07 06:28:16 on
 * machines were long is 32-bit! (However, as time_t is signed, we
 * will already get problems at other places on 2038-01-19 03:14:08)
 *
 */
u32 mktimev(struct rtc_time *tm)
{
	if (0 >= (int) (tm->tm_mon -= 2)) {	/* 1..12 -> 11,12,1..10 */
		tm->tm_mon += 12;		/* Puts Feb last since it has leap day */
		tm->tm_year -= 1;
	}

	return (((
		(u32) (tm->tm_year/4 - tm->tm_year/100 + tm->tm_year/400 + 367*tm->tm_mon/12 + tm->tm_mday) +
			tm->tm_year*365 - 719499
	    )*24 + tm->tm_hour /* now have hours */
	  )*60 + tm->tm_min /* now have minutes */
	)*60 + tm->tm_sec; /* finally seconds */	 
}



void to_tm(u32 tim, struct rtc_time * tm)
{
	register u32    i;
	register long   hms, day;

	day = tim / SECDAY;			/* 有多少天 */
	hms = tim % SECDAY;			/* 今天的时间,单位s */

	/* Hours, minutes, seconds are easy */
	tm->tm_hour = hms / 3600;
	tm->tm_min = (hms % 3600) / 60;
	tm->tm_sec = (hms % 3600) % 60;

	/* Number of years in days */ /*算出当前年份,起始的计数年份为1970年*/
	for (i = STARTOFTIME; day >= days_in_year(i); i++) {
		day -= days_in_year(i);
	}
	tm->tm_year = i;

	/* Number of months in days left */ /*计算当前的月份*/
	if (leapyear(tm->tm_year)) {
		days_in_month(FEBRUARY) = 29;
	}
	for (i = 1; day >= days_in_month(i); i++) {
		day -= days_in_month(i);
	}
	days_in_month(FEBRUARY) = 28;
	tm->tm_mon = i;

	/* Days are what is left over (+1) from all that. *//*计算当前日期*/
	tm->tm_mday = day + 1;

	/*
	 * Determine the day of week
	 */
	GregorianDay(tm);
	
}

/*
 * 函数名:Time_Adjust
 * 描述  :时间调节
 * 输入  :用于读取RTC时间的结构体指针(北京时间)
 * 输出  :无
 * 调用  :外部调用
 */
void Time_Adjust(struct rtc_time* tm)
{
	  /* 等待确保上一次操作完成 */
	  RTC_WaitForLastTask();
		//更新日期
		 GregorianDay(tm);
	
//	  /* 设置当前时间  通过将 小时、分钟和秒都转换成秒 加起来 来实现设置当前计数值 (小时是 24小时制) */
//	  RTC_SetCounter(tm->tm_hour*3600+tm->tm_min*60+tm->tm_sec);  //Tmp HH*3600 Tmp MM*60 Tmp SS
	
	  /* 由日期计算时间戳并写入到RTC计数寄存器 */
	  RTC_SetCounter(mktimev(tm)-TIME_ZOOM);	
	
	  /* 等待确保上一次操作完成 */
	  RTC_WaitForLastTask();
}



void Time_Display(uint32_t TimeVar,struct rtc_time *tm)
{
	   char  buf[20];   //用于暂存oled数据
	   uint32_t BJ_TimeVar;

	   /*  把标准时间转换为北京时间*/
	   BJ_TimeVar =TimeVar + TIME_ZOOM;
	   to_tm(BJ_TimeVar,tm);/*把定时器的值转换为北京时间*/
	
		//打印年
		sprintf(buf,"%d",tm->tm_year);
		OLED_ShowString(75,0,(u8 *)buf,16);
	  OLED_ShowCHinese(75+16*2,0,0);  //打印中文“年”
		//打印时间:
	  sprintf(buf,"%02d:%02d:%02d",tm->tm_hour,tm->tm_min,tm->tm_sec);		
 		OLED_ShowString(64,2,(u8 *)buf,16);		          
		//打印日期:
		sprintf(buf,"%02d",tm->tm_mon);	   
		OLED_ShowString(75,4,(u8 *)buf,12);	//打印月	
		OLED_ShowCHinese_small(75+14,4,0);  //打印中文月	    
		sprintf(buf,"%02d",tm->tm_mday);		 
		OLED_ShowString(75+14+12,4,(u8 *)buf,12);	//打印日
		OLED_ShowCHinese_small(75+14+12+14,4,1);  //打印中文日
		//打印星期:
	  OLED_ShowCHinese(70,5,1);     //打印中文“星”		
	  OLED_ShowCHinese(70+16,5,2);  //打印中文“期”				
		sprintf(buf,"%d",tm->tm_wday);		   
		OLED_ShowString(70+16+16,5,(u8 *)buf,16);
}
#ifndef _RTC_day_h_
#define _RTC_day_h_

#include "headfire.h"

typedef unsigned int  u32;

//定义时间结构体
struct rtc_time {
	int tm_sec;
	int tm_min;
	int tm_hour;
	int tm_mday;
	int tm_mon;
	int tm_year;
	int tm_wday;
};

//初始化时间结构体
extern struct rtc_time time1;

extern uint16_t BMP_cnt,BMP_FLAG;

void GregorianDay(struct rtc_time * tm);
uint32_t mktimev(struct rtc_time *tm);
void to_tm(uint32_t tim, struct rtc_time * tm);
void Time_Display(uint32_t TimeVar,struct rtc_time *tm);
void Time_Adjust(struct rtc_time* tm);

#endif


主函数代码:

#include "main.h"

//时间结构体  在RTC_day.h中初始化了
//时间结构体定义在 RTC_day.h

//刷新时间标志
 uint16_t TimeDisplay_cnt,TimeDisplay;
	
int main(void)
{	
	init_ALL();     //初始化所有函数
  while(1)
	{	
		if(TimeDisplay==1)
		{
			Time_Display(RTC_GetCounter(),&time1);
			TimeDisplay=0;
		}
				switch(BMP_FLAG)
		{
			case 1:OLED_DrawBMP(0,0,64,8,BMP1);  break;
			case 2:OLED_DrawBMP(0,0,64,8,BMP2);  break;
			case 3:OLED_DrawBMP(0,0,64,8,BMP3);  break;
			case 4:OLED_DrawBMP(0,0,64,8,BMP4);  break;
			case 5:OLED_DrawBMP(0,0,64,8,BMP5);  break;
			case 6:OLED_DrawBMP(0,0,64,8,BMP6);  break;
			case 7:OLED_DrawBMP(0,0,64,8,BMP7);  break;
			case 8:OLED_DrawBMP(0,0,64,8,BMP8);  break;
			case 9:OLED_DrawBMP(0,0,64,8,BMP9);  break;
			case 10:OLED_DrawBMP(0,0,64,8,BMP10);  break;
			
			case 11:OLED_DrawBMP(0,0,64,8,BMP11);  break;
			case 12:OLED_DrawBMP(0,0,64,8,BMP12);  break;
			case 13:OLED_DrawBMP(0,0,64,8,BMP13);  break;
			case 14:OLED_DrawBMP(0,0,64,8,BMP14);  break;
			case 15:OLED_DrawBMP(0,0,64,8,BMP15);  break;
			case 16:OLED_DrawBMP(0,0,64,8,BMP16);  break;
			case 17:OLED_DrawBMP(0,0,64,8,BMP17);  break;
			case 18:OLED_DrawBMP(0,0,64,8,BMP18);  break;
			case 19:OLED_DrawBMP(0,0,64,8,BMP19);  break;
			case 20:OLED_DrawBMP(0,0,64,8,BMP20);  break;		

			case 21:OLED_DrawBMP(0,0,64,8,BMP21);  break;
			case 22:OLED_DrawBMP(0,0,64,8,BMP22);  break;
			case 23:OLED_DrawBMP(0,0,64,8,BMP23);  break;
			case 24:OLED_DrawBMP(0,0,64,8,BMP24);  break;
			case 25:OLED_DrawBMP(0,0,64,8,BMP25);  break;
			case 26:OLED_DrawBMP(0,0,64,8,BMP26);  break;
			case 27:OLED_DrawBMP(0,0,64,8,BMP27);  break;
			case 28:OLED_DrawBMP(0,0,64,8,BMP28);  break;

		}
	}
}


//初始化所有函数:
void init_ALL(void)
{
	SysTick_Init(72);         //初始化滴答计时器
	Timer2_Init();						//初始化定时器2
	i2c_GPIO_Config();	      //IIC初始化
	OLED_Init();              //初始化OLED屏幕
	OLED_Clear();             //清空屏幕数据
	RTC_init();
	
}


//定时器2中断服务函数
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{		
		if(++TimeDisplay_cnt==100)  //定时器刷新时间
		{
			TimeDisplay_cnt=0;TimeDisplay=1;
		}
		if(++BMP_cnt==10)		        //定时器   刷新太空人图片
		{
			BMP_cnt=0;BMP_FLAG++;
			if(BMP_FLAG==29){BMP_FLAG=1;}
		}
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清出中断寄存器标志位,用于退出中断
	}
}

//RTC每秒进的中断服务函数
void RTC_IRQHandler(void)
{
	  if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
	  {
	    /* Clear the RTC Second interrupt */
	    RTC_ClearITPendingBit(RTC_IT_SEC);
	
//	    /* Enable time update */
//	    TimeDisplay = 1;
//	
	    /* Wait until last write operation on RTC registers has finished */
	    RTC_WaitForLastTask();
	  }
}

测试效果:

测试工程下载:

https://download.csdn.net/download/qq_64257614/88237879?spm=1001.2014.3001.5503

  • 16
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NULL指向我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值