一、創(chuàng)作背景
之前做了個(gè)關(guān)于STM32低功耗信號(hào)采集的項(xiàng)目,使用STM32L031單片機(jī),項(xiàng)目要求是這樣的:
設(shè)備使用電池供電,檢測(cè)傳感器的信號(hào),并將這個(gè)信號(hào)無線傳出來。設(shè)備每次采集信號(hào)到傳輸出去的時(shí)間就幾十mS,其他時(shí)間進(jìn)入深度休眠,以節(jié)省電量。
這個(gè)項(xiàng)目最主要的是,設(shè)備每天工作時(shí)間不確定,客戶可能要求每個(gè)小時(shí)采集一次,也可能每天采集一次,或者只工作日才采集。
因?yàn)楣ぷ鞯拈g隔不確定,而且通過無線網(wǎng)絡(luò)能夠輕易的得到UTC時(shí)間。所以,我決定做一個(gè)萬年歷,使用單片機(jī)的RTC外設(shè)和鬧鐘功能,每次設(shè)備采集完成之后,進(jìn)入休眠之前,根據(jù)客戶設(shè)置的工作機(jī)制,將下一次的RTC鬧鐘設(shè)置好,通過RTC的鬧鐘中斷事件將程序喚醒。

二、UTC的調(diào)查
經(jīng)過調(diào)研UTC存在一個(gè)Y2038的一個(gè)BUG,即在2038年1月19日(星期二)03:14:07am(GMT)正式發(fā)。因?yàn)?2位電腦系統(tǒng)都用帶符號(hào)32位整型來存儲(chǔ)time_t的值,也就是說t_time只能用31位二進(jìn)制數(shù)來表示(第一位用來表示正負(fù)號(hào)),而其最大值轉(zhuǎn)換為十進(jìn)制是2147483647,換算成日期和時(shí)間剛好是2038年1月19日03:14:07am(GMT),而這一秒過后,t_time的值將變成-2147483647這樣32位軟硬件系統(tǒng)的日期時(shí)間顯示就都亂套了。
現(xiàn)在離2038年也不是太遠(yuǎn),所以這個(gè)隱患不能在這里埋下(必進(jìn)這次寫的代碼下次我還想在用,總不能下次在研究)。
三、方案設(shè)計(jì)
由于STM32的RTC肯定是32位的,擺在我面前的有兩個(gè)方法:
方法一:在通用的規(guī)則中,UTC=0 表示1970/1/1 0:0:0 我在項(xiàng)目中將這個(gè)時(shí)間進(jìn)行平移,比如移到2010/1/1 0:0:0,這樣Y2038就變成了2078。
方法二:C語言的time.h文件中,用的是 int計(jì)秒。我用準(zhǔn)備用uint進(jìn)行計(jì)秒,這樣,就可以將千年蟲的BUG推遲到北京時(shí)間2106/2/7 14:28:15
所以,最終,我還是毫不猶豫的選擇了方案二,因?yàn)檫@樣不用去改動(dòng)大家都遵循的標(biāo)準(zhǔn)。
四、程序設(shè)計(jì)
定義程序結(jié)構(gòu)提類型
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;
創(chuàng)建變量?
//平年累積月分天數(shù)表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};//閏年累積月分天數(shù)表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;//得到秒余數(shù)LocalTime.tm_min=(UtcVal/60)%60;//得到整數(shù)分鐘數(shù)Hour=(UtcVal/60)/60;//得到整數(shù)小時(shí)數(shù)LocalTime.tm_hour=Hour%24+TimeZone;//得到小時(shí)余數(shù)+時(shí)區(qū)if(LocalTime.tm_hour>23){LocalTime.tm_hour-=24;Days=Hour/24+1;}else{Days=Hour/24;}LocalTime.tm_wday=(Days+4)%7;//計(jì)算星期,0-表示星期天注:1970-1-1 是星期4//注:400年=146097天,100年=36524天,4年=1461天Year = 1970;//utc時(shí)間從1970開始Year += (Days/146097)*400;Days %= 146097;//計(jì)算400年內(nèi)的剩余天數(shù)Year += (Days/36525)*100;Days %= 36525; Year += (Days/1461)*4;Days %= 1461;//計(jì)算4年內(nèi)剩余天數(shù),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;//看一下有幾個(gè)400年,幾個(gè)100年,幾個(gè)4年uint32_t dy = (y / 400);uint32_t days = dy * (400 * 365 + 97);//400年的天數(shù)dy = (y % 400) / 100;days += dy * (100 * 365 + 25);//100年的天數(shù)dy = (y % 100) / 4;days += dy * (4 * 365 + 1);//4年的天數(shù)dy = y % 4;//注意:這里1972是閏年,與1970只差2年days += dy * 365 ;if(dy == 3)//這個(gè)4年里,有沒有閏年就差1天{days++;//只有這個(gè)是要手動(dòng)加天數(shù)的,因?yàn)?973年計(jì)算時(shí)前面的天數(shù)按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]; //如果給定的月份數(shù)為x則,只有x-1個(gè)整數(shù)月}}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基本就完成了。
調(diào)用的時(shí)候,只需要使用如下兩個(gè)函數(shù)就能進(jìn)行互轉(zhuǎn):
TimeType alg_Utc2LocalTime(uint32_t UtcVal, int8_t TimeZone);uint32_t? alg_LocalTime2Utc(TimeType LocalTime, int8_t TimeZone);
電子發(fā)燒友App
















評(píng)論