STM32电子万年历制作详解(RTC实战)

16 篇文章 9 订阅
15 篇文章 6 订阅

 

首先附上效果图:

 

 

 

 

 

博主在重温了STM32的RTC后心血来潮,决定用RTC在做个万年历,其实也不算万年历,就是可以实时显示当前时间,而且大家也都知道,STM32自带的RTC的精度实在让人不好意思说,大概20分钟会有40S的误差,不过对于体验理解还是十分有帮助的,这个作品大概耗时2小时左右(汉字字库生成耗掉我大半精力呀T_T)所以我们一起来记录一下这个作品。但是由于精力有限,所以只写出部分主要问题和易错代码,若有同学需要完整工程请站内私聊。

 

 

首先是一个问题:器件选型,博主今天碰到一个很奇怪的事,相同的代码在不同型号的f103中不一定能运行(都配置好了),我也曾考虑是不是寄存器地址都不太对应,其实有这个可能,但是我往同型号烧相同代码有时候也会卡死(进不了main函数),遇到相同问题的同学可以抱团取暖,最终解决方法比较暴力,新建一个rct6的工程重新来T_T。

 

下面我们来把主要代码讲解一下:

int main()
{			
	  delay_init();	    	 //延时函数初始化	  
	  NVIC_Configuration(); 	 //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 
	  LED_Init();			     //LED端口初始化
	  OLED_Init();			//初始化OLED  
	  OLED_Clear()  	; 
	  USART_Config();		
	
	  Key_GPIO_Config();

		/* 配置RTC秒中断优先级 */
	  RTC_NVIC_Config();
	  RTC_CheckAndConfig(&systmtime);
	
	  while (1)
	  {
	    /* 每过1s 更新一次时间*/
	    if (TimeDisplay == 1)
	    {
				/* 当前时间 */
	      Time_Display( RTC_GetCounter(),&systmtime); 		  
	      TimeDisplay = 0;
	    }
			
			//按下按键,通过串口修改时间
			if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON  )
			{
				struct rtc_time set_time;

				/*使用串口接收设置的时间,输入数字时注意末尾要加回车*/
				Time_Regulate_Get(&set_time);
				/*用接收到的时间设置RTC*/
				Time_Adjust(&set_time);
				
				//向备份寄存器写入标志
				BKP_WriteBackupRegister(RTC_BKP_DRX, RTC_BKP_DATA);

			} 			
	  }
}

主函数就这么多,比较简单,就是初始化了一下OLED,配置了一下RTC,这些代码都比较好找到,下面来看看Time_display的具体代码:

/*
 * 函数名:Time_Display
 * 描述  :显示当前时间值
 * 输入  :-TimeVar RTC计数值,单位为 s
 * 输出  :无
 * 调用  :内部调用
 */	
void Time_Display(uint32_t TimeVar,struct rtc_time *tm)
{
	   static uint32_t FirstDisplay = 1;
	   uint32_t BJ_TimeVar;
	   uint8_t str[200]; // 字符串暂存  	

	   /*  把标准时间转换为北京时间*/
	   BJ_TimeVar =TimeVar + TIME_ZOOM;

	   to_tm(BJ_TimeVar, tm);/*把定时器的值转换为北京时间*/	
	
	  if((!tm->tm_hour && !tm->tm_min && !tm->tm_sec)  || (FirstDisplay))
	  {
	      
	      GetChinaCalendar((u16)tm->tm_year, (u8)tm->tm_mon, (u8)tm->tm_mday, str);	
					printf("\r\n 今天新历:%0.2d%0.2d,%0.2d,%0.2d", str[0], str[1], str[2],  str[3]);
			    OLED_ShowString(1,0,"RTC",16);  
			  	OLED_ShowCHinese(28,0,0);//景
		      OLED_ShowCHinese(46,0,1);//园
	      	OLED_ShowCHinese(64,0,2);//电
		      OLED_ShowCHinese(82,0,3);//子
			    OLED_ShowString(1,2,"Design by ZF",16); 
		    //  OLED_ShowCHinese(88,0,5);//科
		    //  OLED_ShowCHinese(104,0,6);//技
			    
	
	      GetChinaCalendarStr((u16)tm->tm_year,(u8)tm->tm_mon,(u8)tm->tm_mday,str);
					printf("\r\n 今天农历:%s\r\n", str);
	
	     if(GetJieQiStr((u16)tm->tm_year, (u8)tm->tm_mon, (u8)tm->tm_mday, str))
					printf("\r\n 今天农历:%s\r\n", str);
			 
	
	      FirstDisplay = 0;
	  }	 	  	

	  /* 输出时间戳,公历时间 */
	  printf(" UNIX时间戳 = %d 当前时间为: %d年(%s年) %d月 %d日 (星期%s)  %0.2d:%0.2d:%0.2d\r",TimeVar,
	                    tm->tm_year, zodiac_sign[(tm->tm_year-3)%12], tm->tm_mon, tm->tm_mday, 
	                    WEEK_STR[tm->tm_wday], tm->tm_hour, 
	                    tm->tm_min, tm->tm_sec);
		   OLED_ShowNum(0,4,tm->tm_year,4,16); 
	   	 OLED_ShowCHinese(33,4,4);//电
			 OLED_ShowNum(51,4,tm->tm_mon,2,16); 
		   OLED_ShowCHinese(69,4,5);//电
		   OLED_ShowNum(87,4,tm->tm_mday,2,16); 
		   OLED_ShowCHinese(105,4,6);//电
		   OLED_ShowNum(0,6,tm->tm_hour,2,16); 
		   OLED_ShowString(18,6,":",16);
		   OLED_ShowNum(37,6,tm->tm_min,2,16); 
		   OLED_ShowString(55,6,":",16);
		   OLED_ShowNum(74,6,tm->tm_sec,2,16); 
}

用过OLED的都知道,这块代码就是把寄存器的数值读出来,通过计算换算为我们的时间(PS:理论数字上线,时间计时最多为130年,所以从0年到2018年是不可能的,我们把1970年定义为元年,所以大家会发现很少有万年历会比1970年更早)

 

下面就是用串口来设置时间(大部分开发板上有个纽扣电池的位置,RTC在不掉电的情况下可以一直计数,一旦掉电就会重新再来,类似于电脑主板和单片机上的电池位置,都是用来获取时间的,所以上面有没有电池都无所谓,博主做的这个用的是最小系统板,没有电池位置,只能一直接口插电了,一旦断电就得重新设置。。。反正是个玩具。。)

/*
 * 函数名:Time_Regulate_Get
 * 描述  :保存用户使用串口设置的时间,
 *         以便后面转化成时间戳存储到RTC 计数寄存器中。
 * 输入  :用于读取RTC时间的结构体指针
 * 注意  :在串口调试助手输入时,输入完数字要加回车
 */
void Time_Regulate_Get(struct rtc_time *tm)
{
	  uint32_t temp_num = 0;
		uint8_t day_max=0 ;

	  printf("\r\n=========================设置时间==================");
		
	  do 
	  {
			printf("\r\n  请输入年份(Please Set Years),范围[1970~2038],输入字符后请加回车:");
			scanf("%d",&temp_num);
			if(temp_num <1970 || temp_num >2038)
			{
				printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);
					  
			}
			else
			{	  
				printf("\n\r  年份被设置为: %d\n\r", temp_num);

				tm->tm_year = temp_num;
				break;
			}
	  }while(1);


	 do 
	  {
			printf("\r\n  请输入月份(Please Set Months):范围[1~12],输入字符后请加回车:");
			scanf("%d",&temp_num);
			if(temp_num <1 || temp_num >12)
			{
				printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);
					  
			}
			else
			{	  
				printf("\n\r  月份被设置为: %d\n\r", temp_num);

				tm->tm_mon = temp_num;
				break;
			}
	  }while(1);
		
		/*根据月份计算最大日期*/
		switch(tm->tm_mon)
			{
				case 1:
				case 3:
				case 5:
				case 7:
				case 8:
				case 10:
				case 12:					
						day_max = 31;
					break;
				
				case 4:
				case 6:
				case 9:
				case 11:
						day_max = 30;
					break;
				
				case 2:					
				     /*计算闰年*/
						if((tm->tm_year%4==0) &&
							 ((tm->tm_year%100!=0) || (tm->tm_year%400==0)) &&
							 (tm->tm_mon>2)) 
								{
									day_max = 29;
								} else 
								{
									day_max = 28;
								}
					break;			
			}

		do 
	  {				
			printf("\r\n  请输入日期(Please Set Months),范围[1~%d],输入字符后请加回车:",day_max);
			scanf("%d",&temp_num);
			
			if(temp_num <1 || temp_num >day_max)
			{
				printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);
			}
			else
			{
				printf("\n\r  日期被设置为: %d\n\r", temp_num);

				tm->tm_mday = temp_num;
				break;
			}
	  }while(1);
		
		do 
	  {				
			printf("\r\n  请输入时钟(Please Set Hours),范围[0~23],输入字符后请加回车:");
			scanf("%d",&temp_num);
			
			if( temp_num >23)
			{
				printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);
			}
			else
			{
				printf("\n\r  时钟被设置为: %d\n\r", temp_num);

				tm->tm_hour = temp_num;
				break;
			}
	  }while(1);

		do 
	  {				
			printf("\r\n  请输入分钟(Please Set Minutes),范围[0~59],输入字符后请加回车:");
			scanf("%d",&temp_num);
			
			if( temp_num >59)
			{
				printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);
			}
			else
			{
				printf("\n\r  分钟被设置为: %d\n\r", temp_num);

				tm->tm_min = temp_num;
				break;
			}
	  }while(1);

		do 
	  {				
			printf("\r\n  请输入秒钟(Please Set Seconds),范围[0~59],输入字符后请加回车:");
			scanf("%d",&temp_num);
			
			if( temp_num >59)
			{
				printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);
			}
			else
			{
				printf("\n\r  秒钟被设置为: %d\n\r", temp_num);

				tm->tm_sec = temp_num;
				break;
			}
	  }while(1);

上面的代码就是我们一旦断电后需要设置时间,具体的使用不再截图。

下一篇我们来讲讲这个OLED的汉字显示,链接后续附上:点击打开链接

要工程的同学比较多哈,这里贴上下载链接:https://download.csdn.net/download/VCA821/12899468

基于STM32的智能万年历设计 [ps:该⽂章排版有点错乱,如果需要,有PDF版本和WORD版本可供下载观看,还有资源免费下载,请移步作者主页下载,仅供学习参 考。该设计是学校要求的课程设计,也是第⼀次做,很多函数并没有进⾏封装,部分逻辑⽐较乱,勉强完成功能设计,介意勿扰哈,最后再 说⼀遍:免费下载,仅供学习参考使⽤] 1 设计任务及要求 1.1 引⾔ 本⽂提出了⼀种基于 STM32 的智能万年历设计⽅案,本⽂案以 STM32F103C8T6 最⼩ 系统作为主控核⼼,与 OLED 显⽰、 DS18B20 温度传感器、 DST11 温湿度传感器等模块组 成硬件系统。 其中以 STM32 内部的 RTC(实时时钟) 实现⽇历和时间的功能,通过修改计 数器的值可以重新设置系统的当前时间和⽇期。综上所述,此电⼦时钟具有读取⽅便、 显⽰直观、功能多样、电路简洁、成本低廉等诸多优点,符合电⼦仪器仪表的发展趋 势,具有⼴阔的市场前景。 1.2 功能要求 (1) 系统组成: 系统选⽤ STM32F103C8T6 单⽚机为微处理器, 使⽤各模块实现万年历的功 能。 (2) 实现功能: 1) 在 OLED 显⽰屏上显⽰实时的⽇期和时间。 2)具有闰年的⾃动调整功能。 3)能够显⽰实时温度。 4)能够显⽰实时湿度。 (3) 模块组成: STM32 单⽚机最⼩系统、 OLED 显⽰模块、温度传感器模块、湿度传感器模 块; 2 设计⽅案 2.1 可选择的芯⽚⽅案 整个系统⽤ STM32F103 单⽚机作为中央控制器,由单⽚机采集内部 RTC 值,时钟信 号通过单⽚机 I / O ⼝传给 OLED, 单⽚机模块控制驱动模块驱动显⽰模块,通过显⽰模 块来实现信号的输出、 OLED 的显⽰及相关的控制功能。 ⽅案 1:采⽤ 89C51 芯⽚采⽤ 89C51 芯⽚作为硬件核⼼,采⽤ Flash ROM,内部具有 4KB ROM 存储空间,能于 3V 的超低压⼯作,⽽且与 MCS-51 系列单⽚机完全兼容,但是运 ⽤于电路设计中时由于不具备 ISP 在线编程技术,当在对电路进⾏调试时,由于程序的 错误修改或对程序的新增功能需要烧⼊程序时,对芯⽚的多次拔插会对芯⽚造成⼀定的 损坏。 ⽅案 2: 采⽤ STM32 单⽚机。 STM32F103C8T6 是⼀款基于 ARM Cortex-M 内核 STM32 系列的 32 位的微控制器,程序存储器容量是 64KB,需要电压 2V ~ 3.6V,⼯作温度为- 40° C ~ 85° C。 主要性能有:与 MCS-51 单⽚机产品兼容、 37 个可编程 I/0 ⼝线、 贴⽚ 8M 晶振(通过芯⽚内部 PLL 最⾼可达 72M)、⽀持 JTAG/SWD、 20K RAM, 64K ROM, 16 位 的定时、计数器、 中断源、全双⼯ UART 串⾏通道等。 价格相对较便宜, 从单⽚机芯⽚主 要性能⾓度和价格优势出发,本设计的智能万年历单⽚机芯⽚选择设计采⽤⽅案采⽤ STM32F103C8T6。 2.2 显⽰模块选择⽅案 ⽅案 1: 采⽤ OLED 显⽰模块 OLED 显⽰模块的显⽰功能强⼤,可显⽰⼤量⽂字图形显⽰多样,清晰可见,显⽰质 量⾼没有电磁辐射,可视⾯积⼤, 数字接⼝,匀称⼩巧, 功耗⼩。 应⽤范围较⼴,常被 ⽤于智能⼿表,智能⼿环等设备的显⽰。 相对于 LCD 来说, OLED 是⾃发光的,不需要背 光。 ⽅案 2: 采⽤点阵式数码管显⽰ 动态显⽰,即各位数码管轮流点亮, 对于显⽰器各位数码管, 每隔⼀段延时时间循 环点亮⼀次,利⽤⼈的视觉暂留功能可以看到整个显⽰,但需保证扫描速度⾜够快,⼈3 的视觉暂留功能才可察觉不到字符闪烁, 显⽰器的亮度与导通电流、 点亮时间及间隔时 间的⽐例有关。 调整参数可以实现较⾼稳定度的显⽰,动态显⽰节省了 I/O ⼝,降低了 能耗。 从节省单⽚机芯⽚ I/O ⼝和降低能耗⾓度出发,本设计采⽤ OLED 显⽰。 2.3 温湿度模块的选择⽅案 湿度测量模块选择: DHT11 传感器包括 1 个电阻式感湿元件和 1 个 NTC 测温元件,并 与 1 个⾼性能 8 位单⽚机相连接。单线制串⾏接⼝,使系统连接可以更简洁。功耗极 低,信号传输距离可达 20 m 以上。作为⼀种新型的单总线数字温湿度传感器, DHT11 具 有体积⼩、功耗低、响应速度快、抗⼲扰能⼒强、控制简单、性价⽐⾼等优点,能够⼴ 泛应⽤于各个领域。 温度测量模块的选择: DS18B20 的测温范围-55 ~ 125 ,分辨率最⼤可达 0.0625 。 DS18B20 可以直接读出被测温度值。⽽且采⽤ 3 线制与单⽚机相连,减少了 外部硬件电路,具有低成本和易使⽤的特点。 DS18B20 是 Dallas 半导体公司的数字化
评论 129
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值