第 42 章 RTC—实时时钟

42.1 RTC 实时时钟简介

42.2 RTC 外设框图剖析

在这里插入图片描述

框图中浅灰色的部分都是属于备份域的,在 VDD 掉电时可在 VBAT 的驱动下继续运行。这部分仅包括 RTC 的分频器,计数器,和闹钟控制器。

运用流程

若 VDD 电源有效,RTC 可以触发 RTC_Second(秒中断)、RTC_Overflflow(溢出事件) 和 RTC_Alarm(闹钟中断)。从结构图可以分析到,其中的定时器溢出事件无法被配置为中断。若 STM32 原本处于待机状态,可由闹钟事件或 WKUP 事件 (外部唤醒事件,属于 EXTI 模块,不属于 RTC) 使它退出待机模式。闹钟事件是在计数器 RTC_CNT 的值等于闹钟寄存器 RTC_ALR 的值时触发的。

在备份域中所有寄存器都是 16 位的,RTC 控制相关的寄存器也不例外。它的计数器 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 核具有了完全独立于 APB1 接口的特性,也因此对 RTC 寄存器的访问要遵守一定的规则

系统复位后,默认禁止访问后备寄存器和 RTC,防止对后备区域 (BKP) 的意外写操作。执行以下操作使能对后备寄存器和 RTC 的访问:

(1) 设置 RCC_APB1ENR 寄存器的 PWREN 和 BKPEN 位来使能电源和后备接口时钟。

(2) 设置 PWR_CR 寄存器的 DBP 位使能对后备寄存器和 RTC 的访问。

设置后备寄存器为可访问后,在第一次通过 APB1 接口访问 RTC 时,因为时钟频率的差异,所以必须等待 APB1 与 RTC 外设同步,确保被读取出来的 RTC 寄存器值是正确的。若在同步之后,一直没有关闭 APB1 的 RTC 外设接口,就不需要再次同步了。

如果内核要对 RTC 寄存器进行任何的写操作,在内核发出写指令后,RTC 模块在 3 个 RTCCLK时钟之后,才开始正式的写 RTC 寄存器操作。由于 RTCCLK 的频率比内核主频低得多,所以每次操作后必须要检查 RTC 关闭操作标志位 RTOFF,当这个标志被置 1 时,写操作才正式完成。

42.3 UNIX 时间戳

如果从现在起,把计数器 RTC_CNT 的计数值置 0,然后每秒加 1,RTC_CNT 什么时候会溢出呢?

由于 RTC_CNT 是 32 位寄存器,可存储的最大值为 (232-1),即这样计时的话,在 2^32 秒后溢出,

即它将在今后的 136 年时溢出:N = 232/365/24/60/60 ≈136 年

假如某个时刻读取到计数器的数值为 X = 606024*2,即两天时间的秒数,而假设又知道计数器是在 2011 年 1 月 1 日的 0 时 0 分 0 秒置 0 的,那么就可以根据计数器的这个相对时间数值,计算得这个 X 时刻是 2011 年 1 月 3 日的 0 时 0 分 0 秒了。而计数器则会在 (2011+136) 年左右溢出,也就是说到了(2011+136)年时,如果我们还在使用这个计数器提供时间的话就会出现问题。

在这个例子中,定时器被置 0 的这个时间被称为计时元年,相对计时元年经过的秒数称为时间戳,也就是计数器中的值。

42.4 与 RTC 控制相关的库函数

4.1 等待时钟同步和操作完成
4.2 使能备份域说及 RTC 配置
4.3 设置 RTC 时钟分频
4.4 设置、获取 RTC 计数器及闹钟

42.5 利用 RTC 提供北京时间

5.1 硬件设计

在这里插入图片描述

原理图中的右上角是备份域的供电电路,在本开发板中提供了一个钮扣电池插槽,可以接入型号为 CR1220 的钮扣电池,该型号的钮扣电池电压为 3.2V,图中的 BAT54C 双向二极管可切换输入到 STM32 备份域电源引脚 VBAT 的供电,当主电源正常供电时,由稳压器输出的 3.3V 供电,当主电源掉电时,由钮扣电池供电。

原理图下方的是本开发板采用的 LSE 晶振电路,此处使用的晶振频率为 32.768KHz,RTC 外设可以使用 LSE 作为时钟,把它进行分频得到 1Hz 的 RTC 计时时钟。

注意: 本实验默认使用 LSI 内部时钟,使用内部时钟时,即使安装了钮扣电池,主电源掉电后时间是不会继续走的,只会保留上次断电的时间。若要持续运行,需要修改 bsp_rtc.h 文件,使用 RTC_CLOCK_SOURCE_LSE 宏,切换成使用 LSE 外部时钟。

5.2 软件设计
5.2.1 程序设计要点

(1) 初始化 RTC 外设;

(2) 设置时间以及添加配置标志;

(3) 获取当前时间;

5.2.2 代码分析

1.RTC 实验配置相关宏定义
在这里插入图片描述

• USE_LCD_DISPLAY:这个宏可以用于切换本工程是否使用液晶屏显示时间,把它注释掉可以关闭液晶显示,方便移植到没有液晶的应用中。

• RTC_CLOCK_SOURCE_LSE/LSI:这两个宏用于选择使用 LSE 作外部时钟还是 LSI 作外部时钟。提供两种选择主要是因为 STM32 的 LSE 晶振在批量产品时容易不起振,而 LSI 则在主电源关闭后计时时间不会继续增加。

• RTC_BKP_DRX 和 RTC_BKP_DATA:这两个宏用于在备份域寄存器设置 RTC 已配置标志,本实验中初始化 RTC 后,向备份域寄存器写入一个数字,若下次芯片上电检测到该标志,说明 RTC 之前已经配置好时间,所以不应该再设置 RTC,而如果备份域电源也掉电,备份域内记录的该标志也会丢失,所以芯片上电后需要重新设置时间。这两个宏的值中,BKP_DR1是备份域的其中一个寄存器,而 0xA5A5 则是随意选择的数字,只要写入和检测一致即可。

• TIME_ZOOM:这个宏用于设置时区的秒数偏移,例如北京时间为 (GMT+8) 时区,即相对于格林威治时间 (GMT) 早 8 个小时,此处使用的宏值即为 8 个小时的秒数(86060),若使用其它时区,修改该宏即可。

2.初始化 RTC

3.时间管理结构体

4.时间格式转换

5.配置时间

6.检查并配置 RTC

7.转换并输出时间

8.中断服务函数

9.main 函数

main.c

#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./rtc/bsp_rtc.h"
#include "./lcd/bsp_ili9341_lcd.h"
#include "./key/bsp_key.h"  


// N = 2^32/365/24/60/60 = 136 年

/*时间结构体,默认时间2000-01-01 00:00:00*/
struct rtc_time systmtime=
{
0,0,0,1,1,2000,0
};

extern __IO uint32_t TimeDisplay ;



//【*】注意事项:
//在bsp_rtc.h文件中:

//1.可设置宏USE_LCD_DISPLAY控制是否使用LCD显示
//2.可设置宏RTC_CLOCK_SOURCE_LSI和RTC_CLOCK_SOURCE_LSE控制使用LSE晶振还是LSI晶振

//3.STM32的LSE晶振要求非常严格,同样的电路、板子批量产品时总有些会出现问题。
//  本实验中默认使用LSI晶振。
//  
//4.!!!若希望RTC在主电源掉电后仍然运行,需要给开发板的电池槽安装钮扣电池,
//  !!!且改成使用外部晶振模式RTC_CLOCK_SOURCE_LSE
//  钮扣电池型号:CR1220
/**
  * @brief  主函数
  * @param  无  
  * @retval 无
  */
int main()
{		
	
//可使用该宏设置是否使用液晶显示
#ifdef  USE_LCD_DISPLAY
	
		ILI9341_Init ();         //LCD 初始化
		LCD_SetFont(&Font8x16);
		LCD_SetColors(RED,BLACK);

		ILI9341_Clear(0,0,LCD_X_LENGTH,LCD_Y_LENGTH);	/* 清屏,显示全黑 */

		ILI9341_DispStringLine_EN(LINE(0),"        BH RTC demo");
#endif
	
	  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);

			} 			
	  }
}

/***********************************END OF FILE*********************************/


calendar.h

/******************** (C) COPYRIGHT 2009 www.armjishu.com ************************
* File Name          : calendar.h
* Author             : www.armjishu.com Team
* Version            : V1.0
* Date               : 10/1/2009
* Description      : 超强的日历,支持农历,24节气几乎所有日历的功能
                          日历时间以1970年为元年,用32bit的时间寄存器可以运
                          行到2100年左右
*******************************************************************************/

#ifndef __CALENDAR_H
#define __CALENDAR_H
#include "stm32f10x.h"


u8 GetMoonDay(u8 month_p,unsigned short table_addr);
u8 GetChinaCalendar(u16  year,u8 month,u8 day,u8 *p);
void GetSkyEarth(u16 year,u8 *p);
void StrCopy(u8 *target,u8 const *source,u8 no);
void GetChinaCalendarStr(u16 year,u8 month,u8 day,u8 *str);
u8 GetJieQi(u16 year,u8 month,u8 day,u8 *JQdate);
u8 GetJieQiStr(u16 year,u8 month,u8 day,u8 *str);
#endif 

calendar.c

#include "./rtc/bsp_calendar.h"

const uint8_t year_code[597]=
{
	0x04,0xAe,0x53, //1901 0
	0x0A,0x57,0x48, //1902 3
	0x55,0x26,0xBd, //1903 6
	0x0d,0x26,0x50, //1904 9
	0x0d,0x95,0x44, //1905 12
	0x46,0xAA,0xB9, //1906 15
	0x05,0x6A,0x4d, //1907 18
	0x09,0xAd,0x42, //1908 21
	0x24,0xAe,0xB6, //1909
	0x04,0xAe,0x4A, //1910
	0x6A,0x4d,0xBe, //1911
	0x0A,0x4d,0x52, //1912
	0x0d,0x25,0x46, //1913
	0x5d,0x52,0xBA, //1914
	0x0B,0x54,0x4e, //1915
	0x0d,0x6A,0x43, //1916
	0x29,0x6d,0x37, //1917
	0x09,0x5B,0x4B, //1918
	0x74,0x9B,0xC1, //1919
	0x04,0x97,0x54, //1920
	0x0A,0x4B,0x48, //1921
	0x5B,0x25,0xBC, //1922
	0x06,0xA5,0x50, //1923
	0x06,0xd4,0x45, //1924
	0x4A,0xdA,0xB8, //1925
	0x02,0xB6,0x4d, //1926
	0x09,0x57,0x42, //1927
	0x24,0x97,0xB7, //1928
	0x04,0x97,0x4A, //1929
	0x66,0x4B,0x3e, //1930
	0x0d,0x4A,0x51, //1931
	0x0e,0xA5,0x46, //1932
	0x56,0xd4,0xBA, //1933
	0x05,0xAd,0x4e, //1934
	0x02,0xB6,0x44, //1935
	0x39,0x37,0x38, //1936
	0x09,0x2e,0x4B, //1937
	0x7C,0x96,0xBf, //1938
	0x0C,0x95,0x53, //1939
	0x0d,0x4A,0x48, //1940
	0x6d,0xA5,0x3B, //1941
	0x0B,0x55,0x4f, //1942
	0x05,0x6A,0x45, //1943
	0x4A,0xAd,0xB9, //1944
	0x02,0x5d,0x4d, //1945
	0x09,0x2d,0x42, //1946
	0x2C,0x95,0xB6, //1947
	0x0A,0x95,0x4A, //1948
	0x7B,0x4A,0xBd, //1949
	0x06,0xCA,0x51, //1950
	0x0B,0x55,0x46, //1951
	0x55,0x5A,0xBB, //1952
	0x04,0xdA,0x4e, //1953
	0x0A,0x5B,0x43, //1954
	0x35,0x2B,0xB8, //1955
	0x05,0x2B,0x4C, //1956
	0x8A,0x95,0x3f, //1957
	0x0e,0x95,0x52, //1958
	0x06,0xAA,0x48, //1959
	0x7A,0xd5,0x3C, //1960
	0x0A,0xB5,0x4f, //1961
	0x04,0xB6,0x45, //1962
	0x4A,0x57,0x39, //1963
	0x0A,0x57,0x4d, //1964
	0x05,0x26,0x42, //1965
	0x3e,0x93,0x35, //1966
	0x0d,0x95,0x49, //1967
	0x75,0xAA,0xBe, //1968
	0x05,0x6A,0x51, //1969
	0x09,0x6d,0x46, //1970
	0x54,0xAe,0xBB, //1971
	0x04,0xAd,0x4f, //1972
	0x0A,0x4d,0x43, //1973
	0x4d,0x26,0xB7, //1974
	0x0d,0x25,0x4B, //1975
	0x8d,0x52,0xBf, //1976
	0x0B,0x54,0x52, //1977
	0x0B,0x6A,0x47, //1978
	0x69,0x6d,0x3C, //1979
	0x09,0x5B,0x50, //1980
	0x04,0x9B,0x45, //1981
	0x4A,0x4B,0xB9, //1982
	0x0A,0x4B,0x4d, //1983
	0xAB,0x25,0xC2, //1984
	0x06,0xA5,0x54, //1985
	0x06,0xd4,0x49, //1986
	0x6A,0xdA,0x3d, //1987
	0x0A,0xB6,0x51, //1988
	0x09,0x37,0x46, //1989
	0x54,0x97,0xBB, //1990
	0x04,0x97,0x4f, //1991
	0x06,0x4B,0x44, //1992
	0x36,0xA5,0x37, //1993
	0x0e,0xA5,0x4A, //1994
	0x86,0xB2,0xBf, //1995
	0x05,0xAC,0x53, //1996
	0x0A,0xB6,0x47, //1997
	0x59,0x36,0xBC, //1998
	0x09,0x2e,0x50, //1999 294
	0x0C,0x96,0x45, //2000 297
	0x4d,0x4A,0xB8, //2001 300
	0x0d,0x4A,0x4C, //2002
	0x0d,0xA5,0x41, //2003
	0x25,0xAA,0xB6, //2004
	0x05,0x6A,0x49, //2005
	0x7A,0xAd,0xBd, //2006
	0x02,0x5d,0x52, //2007
	0x09,0x2d,0x47, //2008
	0x5C,0x95,0xBA, //2009
	0x0A,0x95,0x4e, //2010
	0x0B,0x4A,0x43, //2011
	0x4B,0x55,0x37, //2012
	0x0A,0xd5,0x4A, //2013
	0x95,0x5A,0xBf, //2014
	0x04,0xBA,0x53, //2015
	0x0A,0x5B,0x48, //2016
	0x65,0x2B,0xBC, //2017
	0x05,0x2B,0x50, //2018
	0x0A,0x93,0x45, //2019
	0x47,0x4A,0xB9, //2020
	0x06,0xAA,0x4C, //2021
	0x0A,0xd5,0x41, //2022
	0x24,0xdA,0xB6, //2023
	0x04,0xB6,0x4A, //2024
	0x69,0x57,0x3d, //2025
	0x0A,0x4e,0x51, //2026
	0x0d,0x26,0x46, //2027
	0x5e,0x93,0x3A, //2028
	0x0d,0x53,0x4d, //2029
	0x05,0xAA,0x43, //2030
	0x36,0xB5,0x37, //2031
	0x09,0x6d,0x4B, //2032
	0xB4,0xAe,0xBf, //2033
	0x04,0xAd,0x53, //2034
	0x0A,0x4d,0x48, //2035
	0x6d,0x25,0xBC, //2036
	0x0d,0x25,0x4f, //2037
	0x0d,0x52,0x44, //2038
	0x5d,0xAA,0x38, //2039
	0x0B,0x5A,0x4C, //2040
	0x05,0x6d,0x41, //2041
	0x24,0xAd,0xB6, //2042
	0x04,0x9B,0x4A, //2043
	0x7A,0x4B,0xBe, //2044
	0x0A,0x4B,0x51, //2045
	0x0A,0xA5,0x46, //2046
	0x5B,0x52,0xBA, //2047
	0x06,0xd2,0x4e, //2048
	0x0A,0xdA,0x42, //2049
	0x35,0x5B,0x37, //2050
	0x09,0x37,0x4B, //2051
	0x84,0x97,0xC1, //2052
	0x04,0x97,0x53, //2053
	0x06,0x4B,0x48, //2054
	0x66,0xA5,0x3C, //2055
	0x0e,0xA5,0x4f, //2056
	0x06,0xB2,0x44, //2057
	0x4A,0xB6,0x38, //2058
	0x0A,0xAe,0x4C, //2059
	0x09,0x2e,0x42, //2060
	0x3C,0x97,0x35, //2061
	0x0C,0x96,0x49, //2062
	0x7d,0x4A,0xBd, //2063
	0x0d,0x4A,0x51, //2064
	0x0d,0xA5,0x45, //2065
	0x55,0xAA,0xBA, //2066
	0x05,0x6A,0x4e, //2067
	0x0A,0x6d,0x43, //2068
	0x45,0x2e,0xB7, //2069
	0x05,0x2d,0x4B, //2070
	0x8A,0x95,0xBf, //2071
	0x0A,0x95,0x53, //2072
	0x0B,0x4A,0x47, //2073
	0x6B,0x55,0x3B, //2074
	0x0A,0xd5,0x4f, //2075
	0x05,0x5A,0x45, //2076
	0x4A,0x5d,0x38, //2077
	0x0A,0x5B,0x4C, //2078
	0x05,0x2B,0x42, //2079
	0x3A,0x93,0xB6, //2080
	0x06,0x93,0x49, //2081
	0x77,0x29,0xBd, //2082
	0x06,0xAA,0x51, //2083
	0x0A,0xd5,0x46, //2084
	0x54,0xdA,0xBA, //2085
	0x04,0xB6,0x4e, //2086
	0x0A,0x57,0x43, //2087
	0x45,0x27,0x38, //2088
	0x0d,0x26,0x4A, //2089
	0x8e,0x93,0x3e, //2090
	0x0d,0x52,0x52, //2091
	0x0d,0xAA,0x47, //2092
	0x66,0xB5,0x3B, //2093
	0x05,0x6d,0x4f, //2094
	0x04,0xAe,0x45, //2095
	0x4A,0x4e,0xB9, //2096
	0x0A,0x4d,0x4C, //2097
	0x0d,0x15,0x41, //2098
	0x2d,0x92,0xB5  //2099
};

/ 
//         以下为24节气计算相关程序			   
// 
//    每年24节气标志表   
//    有兴趣的朋友可按照上面给的原理添加其它年份的表格
//    不是很清楚的朋友可给我发EMAIL		   
/
const uint8_t YearMonthBit[]=
{
	0x4E,0xA6,0x99,		//2000
	0x9C,0xA2,0x98,		//2001
	0x80,0x00,0x18,		//2002
	0x00,0x10,0x24,		//2003
	0x4E,0xA6,0x99,		//2004
	0x9C,0xA2,0x98,		//2005
	0x80,0x82,0x18,		//2006
	0x00,0x10,0x24,		//2007
	0x4E,0xA6,0xD9,		//2008
	0x9E,0xA2,0x98,		//2009

	0x80,0x82,0x18,		//2010
	0x00,0x10,0x04,		//2011
	0x4E,0xE6,0xD9,		//2012
	0x9E,0xA6,0xA8,		//2013
	0x80,0x82,0x18,		//2014
	0x00,0x10,0x00,		//2015
	0x0F,0xE6,0xD9,		//2016
	0xBE,0xA6,0x98,		//2017
	0x88,0x82,0x18,		//2018
	0x80,0x00,0x00,		//2019

	0x0F,0xEF,0xD9,		//2020
	0xBE,0xA6,0x99,		//2021
	0x8C,0x82,0x98,		//2022
	0x80,0x00,0x00,		//2023
	0x0F,0xEF,0xDB,		//2024
	0xBE,0xA6,0x99,		//2025
	0x9C,0xA2,0x98,		//2026
	0x80,0x00,0x18,		//2027
	0x0F,0xEF,0xDB,		//2028
	0xBE,0xA6,0x99,		//2029

	0x9C,0xA2,0x98,		//2030
	0x80,0x00,0x18,		//2031
	0x0F,0xEF,0xDB,		//2032
	0xBE,0xA2,0x99,		//2033
	0x8C,0xA0,0x98,		//2034
	0x80,0x82,0x18,		//2035
	0x0B,0xEF,0xDB,		//2036
	0xBE,0xA6,0x99,		//2037
	0x8C,0xA2,0x98,		//2038
	0x80,0x82,0x18,		//2039

	0x0F,0xEF,0xDB,		//2040
	0xBE,0xE6,0xD9,		//2041 
	0x9E,0xA2,0x98,		//2042
	0x80,0x82,0x18,		//2043
	0x0F,0xEF,0xFB,		//2044
	0xBF,0xE6,0xD9,		//2045
	0x9E,0xA6,0x98,		//2046
	0x80,0x82,0x18,		//2047
	0x0F,0xFF,0xFF,		//2048
	0xFC,0xEF,0xD9,		//2049
	0xBE,0xA6,0x18 		//2050
};

const uint8_t days[24]=
{
	6,20,4,19,6,21,         //一月到三月  的节气基本日期
	5,20,6,21,6,21,         //四月到六月  的节气基本日期
	7,23,8,23,8,23,         //七月到九月  的节气基本日期
	8,24,8,22,7,22,         //十月到十二月的节气基本日期
};

//以公历日期先后排序
const int8_t *JieQiStr[24]=   
{
 // 名称        角度    公历日期     周期 //
	"小寒",     //285     1月 6日
	"大寒",     //300     1月20日    29.5天
	"立春",     //315     2月 4日
	"雨水",     //330     2月19日    29.8天
	"惊蛰",     //345     3月 6日
	"春分",     //  0     3月21日    30.2天
	"清明",     // 15     4月 5日
	"谷雨",     // 30     4月20日    30.7天
	"立夏",     // 45     5月 6日
	"夏满",     // 60     5月21日    31.2天
	"芒种",     // 75     6月 6日
	"夏至",     // 90     6月21日    31.4天
	"小暑",     //105     7月 7日
	"大暑",     //120     7月23日    31.4天
	"立秋",     //135     8月 8日
	"处暑",     //150     8月23日    31.1天
	"白露",     //165     9月 8日
	"秋分",     //180     9月23日    30.7天
	"寒露",     //195    10月 8日
	"霜降",     //210    10月24日    30.1天
	"立冬",     //225    11月 8日
	"小雪",     //240    11月22日    29.7天
	"大雪",     //255    12月 7日
	"冬至"      //270    12月22日    29.5天
};

//下部分数据是农历部分要使用的
//月份数据表
uint8_t  const day_code1[9]={0x0,0x1f,0x3b,0x5a,0x78,0x97,0xb5,0xd4,0xf3};
unsigned short const day_code2[3]={0x111,0x130,0x14e};
uint8_t const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
uint8_t const *sky[10]=  {"甲","乙","丙","丁","戊","己","庚","辛","壬","癸",};//天干
uint8_t const *earth[12]={"子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥",};//地支
uint8_t const *monthcode[12]={"一","二","三","四","五","六","七","八","九","十","冬","腊",};//农历月份
uint8_t const *nongliday[4]={"初","十","廿","三",};//农历日期  

///
//支持从1900年到2099年的农历查询
//支持从2000年到2050年的节气查询
//子函数,用于读取数据表中农历月的大月或小月,如果该月为大返回1,为小返回0
uint8_t GetMoonDay(uint8_t month_p,unsigned short table_addr)
{
	switch (month_p)
	{
		case 1:
			if((year_code[table_addr]&0x08)==0)	return(0);
			else 								return(1); 
		case 2:
			if((year_code[table_addr]&0x04)==0)	return(0);
			else 								return(1);
		case 3:
			if((year_code[table_addr]&0x02)==0)	return(0);
			else 								return(1);
		case 4:
			if((year_code[table_addr]&0x01)==0)	return(0);
			else 								return(1);
		case 5:
			if((year_code[table_addr+1]&0x80)==0)	return(0);
			else 									return(1);
		case 6:
			if((year_code[table_addr+1]&0x40)==0)	return(0);
			else 									return(1);
		case 7:
			if((year_code[table_addr+1]&0x20)==0)	return(0);
			else 									return(1);
		case 8:
			if((year_code[table_addr+1]&0x10)==0)	return(0);
			else 									return(1);
		case 9:
			if((year_code[table_addr+1]&0x08)==0)	return(0);
			else 									return(1);
		case 10:
			if((year_code[table_addr+1]&0x04)==0)	return(0);
			else 									return(1);
		case 11:
			if((year_code[table_addr+1]&0x02)==0)	return(0);
			else 									return(1);
		case 12:
			if((year_code[table_addr+1]&0x01)==0)	return(0);
			else 									return(1);
		case 13:
			if((year_code[table_addr+2]&0x80)==0)	return(0);
			else 									return(1);
	}
	return(0);
}

/ 
// 函数名称:GetChinaCalendar
//功能描述:公农历转换(只允许1901-2099年)
// 输 入:  year        公历年
//          month       公历月
//          day         公历日
//          p           储存农历日期地址
// 输 出:  1           成功
//          0           失败																			 
/
uint8_t GetChinaCalendar(uint16_t  year,uint8_t month,uint8_t day,uint8_t *p)
{ 
	uint8_t temp1,temp2,temp3,month_p,yearH,yearL;	
	uint8_t flag_y;
	unsigned short temp4,table_addr;

	yearH=year/100;	yearL=year%100;//年份的高低两个字节 
	if((yearH!=19)&&(yearH!=20))return(0);//日期不在19xx ~ 20xx 范围内,则退出
	
	// 定位数据表地址  
	if(yearH==20)	table_addr=(yearL+100-1)*3;
	else  			table_addr=(yearL-1)*3;

	// 取当年春节所在的公历月份  
	temp1=year_code[table_addr+2]&0x60;	
	temp1>>=5;

	// 取当年春节所在的公历日  
	temp2=year_code[table_addr+2]&31; 

	// 计算当年春年离当年元旦的天数,春节只会在公历1月或2月  
	if(temp1==1) 	temp3=temp2-1; 
	else 			temp3=temp2+31-1; 

	// 计算公历日离当年元旦的天数  
	if (month<10) 	temp4=day_code1[month-1]+day-1;
	else  			temp4=day_code2[month-10]+day-1;
	// 如果公历月大于2月并且该年的2月为闰月,天数加1  
	if ((month>2)&&(yearL%4==0)) 	temp4++;

	// 判断公历日在春节前还是春节后  
	if (temp4>=temp3)
	{ 						
		temp4-=temp3;
		month=1;
		month_p=1;
							
		flag_y=0;
		if(GetMoonDay(month_p,table_addr)==0)	temp1=29; //小月29天
		else 									temp1=30; //大小30天
		// 从数据表中取该年的闰月月份,如为0则该年无闰月  
		temp2=year_code[table_addr]/16; 	
		while(temp4>=temp1)
		{
			temp4-=temp1;
			month_p++;
			if(month==temp2)
			{
				flag_y=~flag_y;
				if(flag_y==0)month++;
			}
			else month++;
			if(GetMoonDay(month_p,table_addr)==0)	temp1=29;
			else 									temp1=30;
		}
		day=temp4+1;
	}
	// 公历日在春节前使用下面代码进行运算  
	else
	{ 						
		temp3-=temp4;
		if (yearL==0)
		{
			yearL=100-1;
			yearH=19;
		}
		else yearL--;
		table_addr-=3;
		month=12;
		temp2=year_code[table_addr]/16; 	
		if (temp2==0)	month_p=12; 
		else 			month_p=13; 

		flag_y=0;
		if(GetMoonDay(month_p,table_addr)==0)	temp1=29; 
		else 									temp1=30; 
		while(temp3>temp1)
		{
			temp3-=temp1;
			month_p--;
			if(flag_y==0)		month--;
			if(month==temp2)	flag_y=~flag_y;
			if(GetMoonDay(month_p,table_addr)==0)	temp1=29;
			else 									temp1=30;
		}
		day=temp1-temp3+1;
	}

	*p++=yearH;
	*p++=yearL;
	*p++=month;
	*p=day;	
	return(1);
}

//
// 函数名称:GetSkyEarth
// 功能描述:输入公历日期得到一个甲子年(只允许1901-2099年)
// 输 入:  year        公历年
//          p           储存星期地址
// 输 出:  无																							   
/
void GetSkyEarth(uint16_t year,uint8_t *p)
{
	uint8_t x;
	
	if(year>=1984)
	{
		year=year-1984;
		x=year%60;				
	}
	else
	{
		year=1984-year;
		x=60-year%60;
	}
	*p=x;
}
//将指定字符source复制no个给target
void StrCopy(uint8_t *target,uint8_t const *source,uint8_t no)
{
	uint16_t i;	 
	for(i=0;i<no;i++)
	{
		*target++=*source++;
	}
}
//
// 函数名称:GetChinaCalendarStr
// 功能描述:输入公历日期得到农历字符串	
//          如:GetChinaCalendarStr(2007,02,06,str) 返回str="丙戌年腊月十九"
// 输 入:  year        公历年
//          month       公历月
//          day         公历日
//          str         储存农历日期字符串地址   15Byte
// 输 出:  无																							  
/
void GetChinaCalendarStr(uint16_t year,uint8_t month,uint8_t day,uint8_t *str)
{
	uint8_t NLyear[4];
	uint8_t SEyear;
	
	StrCopy(&str[0],(u8 *)"甲子年正月初一",15);
	if(GetChinaCalendar(year,month,day,(u8 *)NLyear)==0)	return;
	GetSkyEarth(NLyear[0]*100+NLyear[1],&SEyear);
	StrCopy(&str[0],(u8 *)  sky[SEyear%10],2);	//  甲
	StrCopy(&str[2],(u8 *)earth[SEyear%12],2);	//  子	
	
	if(NLyear[2]==1)	StrCopy(&str[6],(u8 *)"正",2);
	else				StrCopy(&str[6],(u8 *)monthcode[NLyear[2]-1],2);		
	
	if(NLyear[3]>10) 	StrCopy(&str[10],(u8 *)nongliday[NLyear[3]/10],2);	
	else				StrCopy(&str[10],(u8 *)"初",2);
	StrCopy(&str[12],(u8 *)monthcode[(NLyear[3]-1)%10],2);
}

//
// 函数名称:GetJieQi
// 功能描述:输入公历日期得到本月24节气日期 day<15返回上半月节气,反之返回下半月	
//          如:GetJieQiStr(2007,02,08,str) 返回str[0]=4
// 输 入:  year        公历年
//          month       公历月
//          day         公历日
//          str         储存对应本月节气日期地址   1Byte
// 输 出:  1           成功
//          0           失败																			  
/
u8 GetJieQi(u16 year,u8 month,u8 day,u8 *JQdate)
{
	u8 bak1,value,JQ;

	if((year<2000)||(year>2050))     return 0;//节气表的范围限制
	if((month==0) ||(month>12))      return 0;
	JQ = (month-1) *2 ;		                        //获得节气顺序标号(0~23
	if(day >= 15) JQ++; 	                        //判断是否是上半月

	bak1=YearMonthBit[(year-2000)*3+JQ/8];          //获得节气日期相对值所在字节  
	value =((bak1<<(JQ%8))&0x80);                   //获得节气日期相对值状态

	*JQdate=days[JQ];								//得到基本节气日
	if( value != 0 )
	{
		//判断年份,以决定节气相对值1代表1,还是-1。
		if( (JQ== 1||JQ== 11||JQ== 18||JQ== 21)&&year< 2044)  (*JQdate)++;
		else                                                  (*JQdate)--;
	}
	return 1;
}
static u8 const MonthDayMax[]={31,28,31,30,31,30,31,31,30,31,30,31,};
//
// 函数名称:GetJieQiStr
// 功能描述:输入公历日期得到24节气字符串	
//          如:GetJieQiStr(2007,02,08,str) 返回str="离雨水还有11天"
// 输 入:  year        公历年
//          month       公历月
//          day         公历日
//          str         储存24节气字符串地址   15Byte
// 输 出:  1           成功
//          0           失败																			  
/
u8 GetJieQiStr(u16 year,u8 month,u8 day,u8 *str)
{
	u8 JQdate,JQ,MaxDay;

	if(GetJieQi(year,month,day,&JQdate)==0)	return 0;

	JQ = (month-1) *2 ;                             //获得节气顺序标号(0~23
	if(day >= 15) JQ++;                             //判断是否是上半月

	if(day==JQdate)                                 //今天正是一个节气日
	{
		StrCopy(str,(u8 *)JieQiStr[JQ],5);
		return 1;
	}
	                                                //今天不是一个节气日
	StrCopy(str,(u8 *)"离立冬还有??天",15);
	if(day<JQdate)                                  //如果今天日期小于本月的节气日期
	{
		StrCopy(&str[2],(u8 *)JieQiStr[JQ],4);
		day=JQdate-day;
	} 
	else                                            //如果今天日期大于本月的节气日期
	{
             if((JQ+1) >23)  return 0;
		StrCopy(&str[2],(u8 *)JieQiStr[JQ+1],4);
		if(day < 15)
		{
			GetJieQi(year,month,15,&JQdate);
			day=JQdate-day;
		}
		else                                        //翻月
		{
			MaxDay=MonthDayMax[month-1];
			if(month==2)                            //润月问题
			{
				if((year%4==0)&&((year%100!=0)||(year%400==0))) MaxDay++;
			}
			if(++month==13)	month=1;
			GetJieQi(year,month,1,&JQdate);
			day=MaxDay-day+JQdate;
		}
	}
	str[10]=day/10+'0';
	str[11]=day%10+'0';
	return 1;
}


data.h

/******************** (C) COPYRIGHT 2009 www.armjishu.com ************************
* File Name          : date.h
* Author             : www.armjishu.com Team
* Version            : V1.0
* Date               : 12/1/2009
* Description        : 日期相关函数
*******************************************************************************/
#ifndef __DATE_H
#define __DATE_H

#include "stm32f10x.h"

struct rtc_time {
	int tm_sec;
	int tm_min;
	int tm_hour;
	int tm_mday;
	int tm_mon;
	int tm_year;
	int tm_wday;
};
    
void GregorianDay(struct rtc_time * tm);
uint32_t mktimev(struct rtc_time *tm);
void to_tm(uint32_t tim, struct rtc_time * tm);
#endif 

data.c

/**
  ******************************************************************************
  * @file    bsp_date.c
  * @author  移植自linux万年历
  * @version V1.0
  * @date    2013-xx-xx
  ******************************************************************************
  * @attention
  *
  * 实验平台:野火 F103-指南者 STM32 开发板 
  * 论坛    :http://www.firebbs.cn
  * 淘宝    :https://fire-stm32.taobao.com
  *
  ******************************************************************************
  */

#include "./rtc/bsp_date.h"

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


其他配置:

在这里插入图片描述

注意: 必须强调的是,使用 scanf 通过串口输入时,每次输入完毕后都要加入回车,这样才

能正常接收,见图使用串口配置时间的注意事项 。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

入世浮尘

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

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

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

打赏作者

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

抵扣说明:

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

余额充值