一、创作背景
之前做了个关于 STM32 低功耗信号采集的项目,使用STM32L031 单片机 ,项目要求是这样的:
设备使用 电池 供电,检测 传感器 的信号,并将这个信号无线传出来。设备每次采集信号到传输出去的时间就几十mS,其他时间进入深度休眠,以节省电量。
这个项目最主要的是,设备每天工作时间不确定,客户可能要求每个小时采集一次,也可能每天采集一次,或者只工作日才采集。
因为工作的间隔不确定,而且通过无线网络能够轻易的得到UTC时间。所以,我决定做一个万年历,使用单片机的RTC外设和闹钟功能,每次设备采集完成之后,进入休眠之前,根据客户设置的工作机制,将下一次的RTC闹钟设置好,通过RTC的闹钟中断事件将程序唤醒。
二、UTC的调查
经过调研UTC存在一个Y2038的一个BUG,即在2038年1月19日(星期二)03:14:07am(GMT)正式发。因为32位电脑系统都用带符号32位整型来存储 ti me_t的值,也就是说t_ TI me只能用31位二进制数来表示(第一位用来表示正负号),而其最大值转换为十进制是2147483647,换算成日期和时间刚好是2038年1月19日03:14:07am(GMT),而这一秒过后,t_ TI me的值将变成-2147483647这样32位软硬件系统的日期时间显示就都乱套了。
现在离2038年也不是太远,所以这个隐患不能在这里埋下(必进这次写的代码下次我还想在用,总不能下次在研究)。
三、方案设计
由于STM32的RTC肯定是32位的,摆在我面前的有两个方法:
方法一:在通用的规则中,UTC=0 表示1970/1/1 0:0:0 我在项目中将这个时间进行平移,比如移到2010/1/1 0:0:0,这样Y2038就变成了2078。
方法二:C语言的 TI me.h文件中,用的是 int计秒。我用准备用uint进行计秒,这样,就可以将千年虫的BUG推迟到北京时间2106/2/7 14:28:15
所以,最终,我还是毫不犹豫的选择了方案二,因为这样不用去改动大家都遵循的标准。
四、程序设计
定义程序结构提类型
typedef struct{uint8_t tm_sec; uint8_t tm_ mi n; uint8_t tm_hour; uint8_t tm_mday; uint8_t tm_mon; uint8_t tm_wday; uint16_t tm_year; //uint16_t tm_yday; } TI meType;
创建变量
//平年累积月分天数表static const uint16_t NonleapYe arM onth[12] = {31,//131 + 28, //231 + 28 + 31, //331 + 28 + 31 + 30, //431 + 28 + 31 + 30 + 31, //531 + 28 + 31 + 30 + 31 + 30, //631 + 28 + 31 + 30 + 31 + 30 + 31, //731 + 28 + 31 + 30 + 31 + 30 + 31 + 31, //831 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, //931 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, //1031 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, //1131 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, //12};//闰年累积月分天数表static const uint16_t LeapYearMonth[12] = {31,//131 + 29, //231 + 29 + 31, //331 + 29 + 31 + 30, //431 + 29 + 31 + 30 + 31, //531 + 29 + 31 + 30 + 31 + 30, //631 + 29 + 31 + 30 + 31 + 30 + 31, //731 + 29 + 31 + 30 + 31 + 30 + 31 + 31, //831 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30, //931 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, //1031 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, //1131 + 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);