一、创作背景
之前做了个关于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);
原创作品,未经权利人授权禁止转载。详情见转载须知。 举报文章
我要举报该内容理由
×