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 通过串口输入时,每次输入完毕后都要加入回车,这样才
能正常接收,见图使用串口配置时间的注意事项 。