一、创作背景
之前做了个关于STM32低功耗信号采集的项目,使用STM32L031单片机,项目要求是这样的:
设备使用电池供电,检测传感器的信号,并将这个信号无线传出来。设备每次采集信号到传输出去的时间就几十mS,其他时间进入深度休眠,以节省电量。
这个项目最主要的是,设备每天工作时间不确定,客户可能要求每个小时采集一次,也可能每天采集一次,或者只工作日才采集。
因为工作的间隔不确定,而且通过无线网络能够轻易的得到UTC时间。所以,我决定做一个万年历,使用单片机的RTC外设和闹钟功能,每次设备采集完成之后,进入休眠之前,根据客户设置的工作机制,将下一次的RTC闹钟设置好,通过RTC的闹钟中断事件将程序唤醒。
二、UTC的调查
经过调研UTC存在一个Y2038的一个BUG,即在2038年1月19日(星期二)03:14:07am(GMT)正式发。因为32位电脑系统都用带符号32位整型来存储time_t的值,也就是说t_time只能用31位二进制数来表示(第一位用来表示正负号),而其最大值转换为十进制是2147483647,换算成日期和时间刚好是2038年1月19日03:14:07am(GMT),而这一秒过后,t_time的值将变成-2147483647这样32位软硬件系统的日期时间显示就都乱套了。
现在离2038年也不是太远,所以这个隐患不能在这里埋下(必进这次写的代码下次我还想在用,总不能下次在研究)。
三、方案设计
由于STM32的RTC肯定是32位的,摆在我面前的有两个方法:
方法一:在通用的规则中,UTC=0 表示1970/1/1 0:0:0 我在项目中将这个时间进行平移,比如移到2010/1/1 0:0:0,这样Y2038就变成了2078。
方法二:C语言的time.h文件中,用的是 int计秒。我用准备用uint进行计秒,这样,就可以将千年虫的BUG推迟到北京时间2106/2/7 14:28:15
所以,最终,我还是毫不犹豫的选择了方案二,因为这样不用去改动大家都遵循的标准。
四、程序设计
定义程序结构提类型
typedef struct
{
uint8_t tm_sec;
uint8_t tm_min;
uint8_t tm_hour;
uint8_t tm_mday;
uint8_t tm_mon;
uint8_t tm_wday;
uint16_t tm_year;
//uint16_t tm_yday;
}TimeType;
创建变量
//平年累积月分天数表 static const uint16_t NonleapYearMonth[12] = { 31,//1 31 + 28, //2 31 + 28 + 31, //3 31 + 28 + 31 + 30, //4 31 + 28 + 31 + 30 + 31, //5 31 + 28 + 31 + 30 + 31 + 30, //6 31 + 28 + 31 + 30 + 31 + 30 + 31, //7 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, //8 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, //9 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, //10 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, //11 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, //12 }; //闰年累积月分天数表 static const uint16_t LeapYearMonth[12] = { 31,//1 31 + 29, //2 31 + 29 + 31, //3 31 + 29 + 31 + 30, //4 31 + 29 + 31 + 30 + 31, //5 31 + 29 + 31 + 30 + 31 + 30, //6 31 + 29 + 31 + 30 + 31 + 30 + 31, //7 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31, //8 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30, //9 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, //10 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, //11 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, //12 };
uint8_t alg_IsLeapYear(uint32_t year)
{
if((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))) //能被4整除,不能被百整除,能被400整除。
{
return 1; //闰年
}
else
{
return 0; //平年
}
}
TimeType alg_Utc2LocalTime(uint32_t UtcVal, int8_t TimeZone)
{
uint32_t i = 0;
TimeType LocalTime;
uint32_t Hour,Days,Year;
LocalTime.tm_sec = UtcVal%60; //得到秒余数
LocalTime.tm_min = (UtcVal/60)%60; //得到整数分钟数
Hour = (UtcVal/60)/60; //得到整数小时数
LocalTime.tm_hour = Hour%24+TimeZone; //得到小时余数+时区
if(LocalTime.tm_hour>23)
{
LocalTime.tm_hour-=24;
Days=Hour/24+1;
}
else
{
Days=Hour/24;
}
LocalTime.tm_wday=(Days+4)%7; //计算星期,0-表示星期天 注:1970-1-1 是星期4
//注:400年=146097天,100年=36524天,4年=1461天
Year = 1970; //utc时间从1970开始
Year += (Days/146097)*400;
Days %= 146097; //计算400年内的剩余天数
Year += (Days/36525)*100;
Days %= 36525;
Year += (Days/1461)*4;
Days %= 1461; //计算4年内剩余天数,1970平1972闰年
while( Days > 365)
{
if(alg_IsLeapYear(Year))
{
Days--;
}
Days -= 365;
Year++;
}
if (!alg_IsLeapYear(Year) && (Days == 365) )
{
Year++;
LocalTime.tm_mday =1;
LocalTime.tm_mon =1;
LocalTime.tm_year =Year;
return LocalTime;
}
LocalTime.tm_year =Year;
LocalTime.tm_mon=0;
LocalTime.tm_mday=0;
if (alg_IsLeapYear(Year)) //本年是闰年
{
for (i = 0; i < 12; i++)
{
if (Days < LeapYearMonth[i])
{
LocalTime.tm_mon = i + 1;
if (i == 0)
{
LocalTime.tm_mday = Days;
}
else
{
LocalTime.tm_mday = Days - LeapYearMonth[i - 1];
}
LocalTime.tm_mday++;
return LocalTime;
}
}
}
else //本年是平年
{
for (i = 0; i < 12; i++)
{
if (Days < NonleapYearMonth[i])
{
LocalTime.tm_mon = i + 1;
if (i == 0)
{
LocalTime.tm_mday = Days;
}
else
{
LocalTime.tm_mday = Days - NonleapYearMonth[i - 1];
}
LocalTime.tm_mday++;
return LocalTime;
}
}
}
return LocalTime;
}
uint32_t alg_LocalTime2Utc(TimeType LocalTime, int8_t TimeZone)
{
uint32_t y = LocalTime.tm_year -1970; //看一下有几个400年,几个100年,几个4年
uint32_t dy = (y / 400);
uint32_t days = dy * (400 * 365 + 97); //400年的天数
dy = (y % 400) / 100;
days += dy * (100 * 365 + 25); //100年的天数
dy = (y % 100) / 4;
days += dy * (4 * 365 + 1); //4年的天数
dy = y % 4; //注意:这里1972是闰年,与1970只差2年
days += dy * 365 ;
if(dy == 3) //这个4年里,有没有闰年就差1天
{
days++; //只有这个是要手动加天数的,因为1973年计算时前面的天数按365天算,1972少算了一天
}
if (LocalTime.tm_mon != 1)
{
if(alg_IsLeapYear(LocalTime.tm_year)) //看看今年是闰年还是平年
{
days += LeapYearMonth[(LocalTime.tm_mon - 1) - 1];
}
else
{
days += NonleapYearMonth[(LocalTime.tm_mon - 1) - 1]; //如果给定的月份数为x则,只有x-1个整数月
}
}
days += LocalTime.tm_mday - 1;
return (days * 24 * 3600 + ((uint32_t)LocalTime.tm_hour - TimeZone)* 3600 + (uint32_t)LocalTime.tm_min * 60 + (uint32_t)LocalTime.tm_sec);
}
以上,程序的API基本就完成了。
调用的时候,只需要使用如下两个函数就能进行互转:
TimeType alg_Utc2LocalTime(uint32_t UtcVal, int8_t TimeZone);
uint32_t alg_LocalTime2Utc(TimeType LocalTime, int8_t TimeZone);