شاید شما هم یک پروژه با ماژول GPS انجام دادید که پس از مدتی متوجه مشکل تغییر تاریخ ماژول GPS در پروژه خودتون شدید که باعث به هم ریختن نرم افزارتون میشه، البته اینجور مشکلات حتی بهصورت وسیع چندبار پیش اومده و نمونش Y2K bug (که نمونه داخلیش اول امسال برای خودمون هم پیش اومد و بیشتر توی دستگاههای پوز بهش برخوردیم) هست. پس خوبه که موقع برخوردن به این مشکلات بادید درستی بهش نگاه بکنیم و حواسمون باشه که اینجور مشکلات ممکنه برای ما هم پیش بیاد و باعث سردرگمی ما توی دیباگ کردن پروژه هامون بشه و زمان زیادی رو هدر بده، پس با ما همراه باشید تا به بررسی علت وقوع این مشکل و نحوه برطرف کردنش بپردازیم.
سال ۱۹۷۸ اولین ماهواره GPS در مدار قرار گرفت (۴۴ سال پیش) البته در ابتدا کاربرد نظامی داشته و بعد از چند سال بهصورت عمومی برای همه قابلاستفاده شده (توی موضوعات High tech غالباً همینطور هست و در ابتدا یه تکنولوژی موردنیاز در بحث نظامی توسعه داده میشه و بعد از مدتی ممکنه بهصورت عمومی هم در دسترس قرار بگیره) البته شاید اون زمان خود آمریکاییها همفکر نمیکردند که سیستم GPS قراره اینهمه سال استفاده بشه و این باعث شده که یکسری مشکلاتی برای استفاده طولانیمدت از اون به وجود بیاد.
تاریخ مبهم
ماهوارههای GNSS سیگنالهای مختلفی رو ارسال میکنند تا گیرندههای GNSS اونها رو دریافت کرده و پردازش کنند، سیگنال L1 C/A یکی از این سیگنالها هست که دیتای تاریخ اون مبهم هست و هر ۱۹٫۶ سال یکبار صفر میشه و باعث میشه ماژولهایی که از این دیتا استفاده میکنند تاریخشون ۲۰ یا ۴۰ سال قبل رو نشون بده.
مشکل تغییر تاریخ GPS چگونه بوجود میاد؟
همانطور که گفتیم سیستم GPS تعداد هفتههای گذشته از تاریخ ۶ ژانویه ۱۹۸۰ رو ارسال میکنه و گیرندههای GPS برای محاسبه زمان دقیق از این پارامتر استفاده میکنند. تعداد هفتههای گذشته از ۰ تا ۱۰۲۳ شمرده میشند و بعد از اون شمارش دوباره از ۰ شروع میشه اولین بار که این اتفاق افتاد در آگوست ۱۹۹۹ بوده و جدیداً در آوریل ۲۰۱۹ این اتفاق بار دیگه رخ داده و زمان بعدی در نوامبر ۲۰۳۸ دوباره این مقدار ۰ شروع میشه.
البته ماژولها اکثراً از سیگنالهای دیگهای هم استفاده میکنند مثل دیتای ماهوارههای GLONASS, Galileo, GPS L2C, GPS L5 و این خطا رو تصحیح میکنند. اما اگر که ماژول شما فقط از دیتای GPS L1 C/A استفاده کنه متوجه نمیشه که تعداد هفته، از سال ۱۹۸۰ هست یا ۱۹۹۹ یا ۲۰۱۹ و تاریخ رو به شما ۴۰ یا ۲۰ سال عقبتر نشون میده. البته این مشکل فقط باعث خطا در تاریخی که ماژول به شما میده میشه و باعث اختلال در دیتاهای ناوبری ماژول نمیشه (حداقل در ماژول های شرکت u-blox به گفته این شرکت).
چه ماژول هایی این مشکل رو دارند؟
اگر شما هم ماژول هایی رو میشناسید که این مشکل رو دارند توی کامنت برامون بگید.
چطور میشه این مشکل رو برطرف کرد؟
نکته اول اینکه شرکت ublox از آخر سال ۲۰۰۷ هر ماژولی که تولید کرده داخل فریمورش بهصورت پیشفرض اومده و تعداد هفتههای گذشته از ۶ ژانویه ۱۹۸۰ رو تا روز تولید ماژول رو ذخیره کرده و ماژول با تطبیق این عدد با عددی که ماژول از ماهواره دریافت میکنه دیرتر به این مشکل برمیخوره، توی جدول پایین میتونید تاریخ دقیق این موضوع رو ببینید.
همچنین شرکت ublox برای برطرف کردن این مشکل توی ماژول های خودش سه تا راه حل پیشنهاد داده:
روش اول
در این روش شما باید با اضافه کردن یک تکه کد به برنامتون تشخیص بدید که الان توی کدوم یکی از این بازههای ۱۹٫۶ ساله هستید و تاریخ خودتون رو اصلاح کنید. در ادامه میتونید این کد رو بررسی کنید:
/****************************************************************************** * * Copyright (C) u-blox AG * u-blox AG, Thalwil, Switzerland * * All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose without fee is hereby granted, provided that this entire notice is * included in all copies of any software which is or includes a copy or * modification of this software and in all copies of the supporting * documentation for such software. * * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR U-BLOX MAKES ANY * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF * THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. * ***************************************************************************** * * Old GPS receivers may show old date in NMEA messages or in proprietary * binary formats because GPS date has 20 years cyclic period. GPS date is * counted as week from the start of year 1980. The week number goes * from 0 to 1023 and then back to 0 (i.e. one cycle has 1024 weeks) * The date can be corrected by adding 7 * 1024 days to the receiver date * The adjustment algorithm calculates how many days there are since start * of 1980, adds 7 * 1024 days there and then converts the day number back * to date-format * The algorithm functions here are short, light-weight, and fast, and utilise * only array indexing and integer division arithmetic. In the C-language * integer arithmetic the division operator "/" drops the reminder and * modulo operator "%" gets the reminder * * Many GPS receivers use common industry practice of postponing the roll-over * event impact for some years. Thus the date adjustment is done only if the * date from the receiver is older than GPS week number roll-over date * 2019-04-07 * *****************************************************************************/ #include <stdio.h> #include <stdint.h> #include <assert.h> uint16_t day_number_1980(uint16_t yyyy, uint16_t mm, uint16_t dd); void date_1980(uint16_t days, uint16_t *year, uint16_t *month, uint16_t *day); void gprmc2int(char gprmc[], uint16_t *year, uint16_t *month, uint16_t *day); char *int2gprmc(uint16_t year, uint16_t month, uint16_t day); void tst(); // The main() is a simple example of using NMEA conversions and date adjustment functions // Uncomment the tst() call if you want to verify the date adjustment algorithm // for all GPS dates from 1980-01-01 to 2079-12-31 int main() { // tst(); uint16_t year, month, day; // On 2019-04-07 the receiver believes current Gregorian date to be 1999-08-22 and // $GPRMC shows that as "220899". This needs to be adjusted to correct date "070419" // Note that the year number in the NMEA string has only two digits char *gprmc = "220899"; // first, convert NMEA date to integer format (adds century to the year) gprmc2int(gprmc, &year, &month, &day); // calculate how many days there are since start of 1980 uint16_t day_num = day_number_1980(year, month, day); // adjust date only if it is before previous GPS week number roll-over // which happened 2019-04-06. That is 14341 days after start of 1980 if (day_num <= 14341) day_num = day_num + 1024 * 7; // convert the day number back to integer year, month and day date_1980(day_num, &year, &month, &day); // convert the year, month and day to NMEA string format (drops century from the year) char *adjusted = int2gprmc(year, month, day); printf("$GPRMC date %s adjusted to %s\n", gprmc, adjusted); } // known day_of_year for each month: // Major index 0 is for non-leap years, and 1 is for leap years // Minor index is for month number 1 .. 12, 0 at index 0 is number of days before January static const uint16_t month_days[2][13] = { {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}}; // Count the days since start of 1980 // Counts year * 356 days + leap days + month lengths + days in month // The leap days counting needs the "+ 1" because GPS year 0 (i.e. 1980) was a leap year uint16_t day_number_1980(uint16_t year, uint16_t month, uint16_t day) { uint16_t gps_years = year - 1980; uint16_t leap_year = (gps_years % 4 == 0) ? 1 : 0; uint16_t day_of_year = month_days[leap_year][month - 1] + day; if (gps_years == 0) return day_of_year; return gps_years * 365 + ((gps_years - 1) / 4) + 1 + day_of_year; } // Convert day_number since start of 1980 to year, month, and day: // - integer division of (day_number - 1) by 365.25 gives year number for 1980 to 2099 // - day number - (year number * 365 days + leap days) gives day of year // The leap days needs "+ 1" because GPS year 0 (i.e. 1980) was a leap year // - (day_of_year - 1) / 31 + 1 gives lower limit for month, but this may be one too low // the guessed month is adjusted by checking the month lengths // - days in month is left when the month lengths are subtracted // - year must still be adjusted by 1980 void date_1980(uint16_t day_number, uint16_t *year, uint16_t *month, uint16_t *day) { uint16_t gps_years = ((day_number - 1) * 100) / 36525; uint16_t leap_year = (gps_years % 4 == 0) ? 1 : 0; uint16_t day_of_year = day_number; if (gps_years > 0) day_of_year = day_number - (gps_years * 365 + ((gps_years - 1) / 4) + 1); uint16_t month_of_year = (day_of_year - 1) / 31 + 1; if (day_of_year > month_days[leap_year][month_of_year]) month_of_year++; *day = day_of_year - month_days[leap_year][month_of_year - 1]; *month = month_of_year; *year = 1980 + gps_years; } // Convert NMEA $GPRMC date string to integer components void gprmc2int(char gprmc[], uint16_t *year, uint16_t *month, uint16_t *day) { *day = 10 * (gprmc[0] - '0') + (gprmc[1] - '0'); *month = 10 * (gprmc[2] - '0') + (gprmc[3] - '0'); *year = 10 * (gprmc[4] - '0') + (gprmc[5] - '0'); assert(*year >= 0 && *year <= 99 && *month >= 1 && *month <= 12 && *day >= 1 && *day <= 31); // NMEA $GPRMC year number has only 2 digits if (*year > 79) *year = *year + 1900; else *year = *year + 2000; } // Convert integer date components to NMEA $GPRMC date string char *int2gprmc(uint16_t year, uint16_t month, uint16_t day) { assert(year >= 1980 && year <= 2079 && month >= 1 && month <= 12 && day >= 1 && day <= 31); year = year % 100; // use only decades and years, drop centuries static char gprmc[7]; gprmc[0] = '0' + (day / 10); gprmc[1] = '0' + (day % 10); gprmc[2] = '0' + (month / 10); gprmc[3] = '0' + (month % 10); gprmc[4] = '0' + (year / 10); gprmc[5] = '0' + (year % 10); gprmc[6] = '\0'; return gprmc; } // Verify the date adjustment algorithm for all GPS dates from 1980-01-01 to 2079-12-31 // The algorithm is OK if all day numbers are consecutive and // day number to date conversion gives the loop date void tst() { int previous = 0; for (uint16_t year = 1980; year <= 2079; year++) { uint16_t month_len[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (year % 4 == 0) month_len[2] = 29; for (uint16_t month = 1; month <= 12; month++) { for (uint16_t day = 1; day <= month_len[month]; day++) { // calculate day number since 1980-01-01 uint16_t daynum = day_number_1980(year, month, day); // check that all day numbers are consecutive assert(daynum == previous + 1); // remeber previous date number for next loop round previous = daynum; // calculate date from the day number uint16_t g_year, g_month, g_day; date_1980(daynum, &g_year, &g_month, &g_day); // verify that calclulated date matches with the test loop date assert(g_year == year && g_month == month && g_day == day); printf("%04hu-%02hu-%02hu (%hu)\n", year, month, day, daynum); } } } }
روش دوم
روش دوم تغییر تعداد هفتههای گذشته از ۱۹۸۰ هست که ماژول با جمع اون با دیتای دریافتی از ماهواره عدد صحیح رو پیدا کنه، با کمک دستور UBX-CFG-NAVX5 میتونید تعداد هفتههای گذشته رو به ماژول بدید. ارسال این دیتا باید بعد از هر بار ریست شدن ماژول اتفاق بیوفته چون ماژول چیزی ذخیره نمیکنه.
مثلاً شما الان میخواید محصولتون رو آماده کنید پس باید ۲۰۴۷ رو برای ماژول ارسال کنید که میشه دو بازه ۱۹٫۶ سالی. تکه کد پایین به شما کمک میکنه که دیتا رو برای ارسال توسط دستور UBX-CFG-NAVX5 آماده کنید.
uint16_t week = 2047; uint8_t buf[6 + 40 + 2] = { 0xB5, 0x62, 0x06, 0x23, 40, 0 }; uint8_t *data = &buf[6]; data[2] = 0; // mask 1 lsb data[2+1] = 1<<1; // mask 1 msb 00000010 data[18] = week & 0xff; // week lsb data[18+1] = (week >> 8) & 0xff; // week msb updateCheckSum(buf, sizeof buf);
روش سوم
روش سوم همتغییر فریمور ماژول به آخرین نسخه هست که باعث میشه تا حدود ۲۰ سال از آخرین نسخه فریمور ماژولتون دیتای درست بده (خیلی باید خفن باشید که محصولتون ۲۰ سال بدون هیچ تغییری کار بده و اصلاً تا ۲۰ سال دیگه کارایی داشته باشه ?)
مشکل تغییر تاریخ ماژول GPS رو آقای بابک وارسته بهمون اطلاع داده بودند و قضیه ازاینقرار بود که ایشون پروژهای با ماژول sim908 حدود سه سال پیش تحویل داده بودند که در اون زمان بهدرستی کار میکرده، اما مدتی پیش که دوباره میخواستند از محصولشون استفاده کنند اطلاعات غلط از بردشون دریافت میکردند و بعدازاینکه راهحل رو پیدا کردند با ما درمیون گذاشتند و بهانهای شد برای نوشتن این مقاله.
برای نوشتن این مقاله از این داکیومنت شرکت ublox کمک گرفته شده.
منبع:سیسوگ