42.1 RTC 实时时钟简介

42.2 RTC 外设框图剖析

第 42 章 RTC—实时时钟-LMLPHP

框图中浅灰色的部分都是属于备份域的,在 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 硬件设计

第 42 章 RTC—实时时钟-LMLPHP

原理图中的右上角是备份域的供电电路,在本开发板中提供了一个钮扣电池插槽,可以接入型号为 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 实验配置相关宏定义
第 42 章 RTC—实时时钟-LMLPHP

• 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 函数

#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 

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


/******************** (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 

/**
  ******************************************************************************
  * @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);

}


第 42 章 RTC—实时时钟-LMLPHP

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

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

08-23 13:28