一、創作背景
之前做了個關于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,//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);
評論
查看更多