Pixhawk原生固件PX4之日期时间的确定
来源:互联网 发布:自学软件编程 编辑:程序博客网 时间:2024/05/29 10:54
欢迎交流~ 个人 Gitter 交流平台,点击直达:
偶然注意到PX4日志中老是出现类似于2000.01.01 00:00:00
这种日期(有兴趣的可以搜索一下千年虫问题),于是结合代码进行了一波分析。
最后定位到gmtime_r(FAR const time_t *timer, FAR struct tm *result)
这个函数以及struct tm
这个结构体。
/* * time.h * /...struct tm{ int tm_sec; /* second (0-61, allows for leap seconds) */ int tm_min; /* minute (0-59) */ int tm_hour; /* hour (0-23) */ int tm_mday; /* day of the month (1-31) */ int tm_mon; /* month (0-11) */ int tm_year; /* years since 1900 */ int tm_wday; /* day of the week (0-6) */ /*not supported by NuttX*/ int tm_yday; /* day of the year (0-365) */ /*not supported by NuttX*/ int tm_isdst; /* non-0 if daylight savings time is in effect */ /*not supported by NuttX*/};
PX4中所有输出的函数都是由tm
这个结构体给出的。
这里需要注意到的是tm_year
是从1900.01.01 00:00:00年开始计的。称为系统时间System Time。
而gmtime_r()
是根据从1970.01.01 00:00:00后的秒数换算过来的。称为格林威治时间/Unix time。
这实在是一件有意思的事情啊。
下面给出gmtime_r()
函数的换算过程吧,有趣
/**************************************************************************** * libc/time/lib_gmtimer.c * * Function: gmtime_r * * Description: * Time conversion (based on the POSIX API) * ****************************************************************************/FAR struct tm *gmtime_r(FAR const time_t *timer, FAR struct tm *result){ time_t epoch; time_t jdn; int year; int month; int day; int hour; int min; int sec; /* Get the seconds since the EPOCH */ /* 纪元开始 1970.01.01 00:00:00*/ epoch = *timer; sdbg("timer=%d\n", (int)epoch); /* Convert to days, hours, minutes, and seconds since the EPOCH */ jdn = epoch / SEC_PER_DAY; // 天数 epoch -= SEC_PER_DAY * jdn; // 减去 天数 * 一天的秒数 hour = epoch / SEC_PER_HOUR; // 小时数 epoch -= SEC_PER_HOUR * hour; // 减去 小时数 * 一小时的秒数 min = epoch / SEC_PER_MIN; // 分钟数 epoch -= SEC_PER_MIN * min; // 减去 分钟数 * 一分钟的秒数 sec = epoch; // 最终秒数 sdbg("hour=%d min=%d sec=%d\n", (int)hour, (int)min, (int)sec); /* Convert the days since the EPOCH to calendar day */ clock_utc2calendar(jdn, &year, &month, &day); // 确定日期,自1970.1.1之后的 sdbg("jdn=%d year=%d month=%d day=%d\n", (int)jdn, (int)year, (int)month, (int)day); /* Then return the struct tm contents */ result->tm_year = (int)year - 1900; /* Relative to 1900 */ result->tm_mon = (int)month - 1; /* zero-based */ result->tm_mday = (int)day; /* one-based */ result->tm_hour = (int)hour; result->tm_min = (int)min; result->tm_sec = (int)sec; return result;}
上面调用了clock_utc2calendar
函数,将UTC确定的天数转换成年月日。
#ifdef CONFIG_GREGORIAN_TIME // 公历时间/* Only handles dates since Jan 1, 1970 *//* 处理1970.1.1之后的日期 */static void clock_utc2calendar(time_t days, int *year, int *month, int *day){ int value; int min; int max; int tmp; bool leapyear; /* There is one leap year every four years, so we can get close with the * following: */ value = days / (4*365 + 1); /* Number of 4-years periods since the epoch 多少个四年 */ days -= value * (4*365 + 1); /* Remaining days 剩余天数 */ value <<= 2; /* Years since the epoch 左移两位,乘以4,得到从EPOCH后的年数 */ /* Then we will brute force the next 0-3 years */ // 简单匹配接下来的0到3年 // 将剩下的天数分配到接下来的3年 for (;;) { /* Is this year a leap year (we'll need this later too) */ leapyear = clock_isleapyear(value + 1970); /* Get the number of days in the year */ tmp = (leapyear ? 366 : 365); /* Do we have that many days? */ if (days >= tmp) { /* Yes.. bump up the year */ value++; days -= tmp; } else { /* Nope... then go handle months */ break; } } /* At this point, value has the year and days has number days into this year */ *year = 1970 + value; /* Handle the month (zero based) */ min = 0; max = 11; do { /* Get the midpoint */ value = (min + max) >> 1; // value = (min + max)/2 /* Get the number of days that occurred before the beginning of the month * following the midpoint. */ tmp = clock_daysbeforemonth(value + 1, leapyear); // (value + 1)月开始前过了多少天了 /* Does the number of days before this month that equal or exceed the * number of days we have remaining? */ if (tmp > days) { /* Yes.. then the month we want is somewhere from 'min' and to the * midpoint, 'value'. Could it be the midpoint? */ tmp = clock_daysbeforemonth(value, leapyear); if (tmp > days) { /* No... The one we want is somewhere between min and value-1 */ max = value - 1; } else { /* Yes.. 'value' contains the month that we want */ break; } } else { /* No... The one we want is somwhere between value+1 and max */ min = value + 1; } /* If we break out of the loop because min == max, then we want value * to be equal to min == max. */ value = min; } while (min < max); /* The selected month number is in value. Subtract the number of days in the * selected month */ days -= clock_daysbeforemonth(value, leapyear); /* At this point, value has the month into this year (zero based) and days has * number of days into this month (zero based) */ *month = value + 1; /* 1-based */ *day = days + 1; /* 1-based */}#endif /* CONFIG_GREGORIAN_TIME */
然后返回到gmtime_r()
函数可以确定,struct tm
结构体的赋值源头所在。
闲人看来是历史遗留问题,懒得改了。
最后的结论是,在使用tm
的结构体时,要把年数加1900,月数加1,才能得到正确的UTC时间。
关于北京东八区的问题这里不再论述,之前在log日志正确显示的博文中已经有介绍,笔者觉得结合这篇博客看才是真。
然后回到2000.01.01
这个日期上来,没有接收GPS的授时信息的情况下,默认时间会是它。而不是1970.01.01
!显然这30年的差距得有代码站出来认了。
关于这个,系统默认时间。
一个猜测是CLOCK_REALTIME与CLOCK_ACTIVETIME两个clk_id
的选取
/* CLOCK_REALTIME refers to the standard time source. For most implementations, * the standard time source is the system timer interrupt. However, if the * platform supports an RTC, then the standard time source will be the RTC * for the clock_gettime() and clock_settime() interfaces (the system timer * is still the time source for all of the interfaces). */#define CLOCK_REALTIME 0/* If an RTC is supported, then the non-standard CLOCK_ACTIVETIME is also * supported to manage time based on the system timer interrupt separately from * the RTC. This may be necessary, for example, in certain cases where the * system timer interrupt has been stopped in low power modes. * * CLOCK_ACTIVETIME is only recognized by clock_gettime() and clock_settime(). */#ifdef CONFIG_RTC# define CLOCK_ACTIVETIME 1#else# define CLOCK_ACTIVETIME CLOCK_REALTIME#endif
因为系统在确定当前时间时调用了这个函数
/* * sdlog2.c */bool get_log_time_tt(struct tm *tt, bool boot_time) { struct timespec ts; px4_clock_gettime(CLOCK_REALTIME, &ts); // 请注意,时间由我定 /* use RTC time for log file naming, e.g. /fs/microsd/2014-01-19/19_37_52.px4log */ time_t utc_time_sec = 0; if (_gpstime_only && has_gps_3d_fix) { utc_time_sec = gps_time_sec; } else { utc_time_sec = ts.tv_sec + (ts.tv_nsec / 1e9); } if (utc_time_sec > PX4_EPOCH_SECS) { /* strip the time elapsed since boot */ if (boot_time) { utc_time_sec -= hrt_absolute_time() / 1e6; } /* apply utc offset (min, not hour) */ utc_time_sec += _utc_offset*60; struct tm *ttp = gmtime_r(&utc_time_sec, tt); return (ttp != NULL); } else { return false; }}
px4_clock_gettime()
的时间由clk_id
决定,最后链接到clock_gettime()
/* * px4_time.h */#elif defined(__PX4_LINUX) || defined(__PX4_NUTTX) || defined(__PX4_DARWIN)#define px4_clock_gettime clock_gettime#define px4_clock_settime clock_settime
这里提一点:目前还是没有能够找到一个合适的编辑器查看PX4代码,时常会跳转错误。特别是涉及到NuttX后,非常多相同名称的函数,针对posix、qurt、NuttX的,还望细心。
/************************************************************************ * Name: clock_gettime * * Description: * Clock Functions based on POSIX APIs * ************************************************************************/int clock_gettime(clockid_t clock_id, struct timespec *tp){#ifdef CONFIG_SYSTEM_TIME64 uint64_t msecs; uint64_t secs; uint64_t nsecs;#else uint32_t msecs; uint32_t secs; uint32_t nsecs;#endif int ret = OK; sdbg("clock_id=%d\n", clock_id); DEBUGASSERT(tp != NULL); /* CLOCK_REALTIME - POSIX demands this to be present. This is the wall * time clock. */#ifdef CONFIG_RTC if (clock_id == CLOCK_REALTIME || clock_id == CLOCK_ACTIVETIME)#else if (clock_id == CLOCK_REALTIME)#endif { /* Do we have a high-resolution RTC that can provie us with the time? */#ifdef CONFIG_RTC_HIRES if (g_rtc_enabled && clock_id != CLOCK_ACTIVETIME) { /* Yes.. Get the hi-resolution time from the RTC *//////////// 使用RTC ////// ret = up_rtc_gettime(tp); } else#endif { /* Get the elapsed time since power up (in milliseconds) biased * as appropriate. */ /* 获取从系统上电后的时间 不上电不走数 */ msecs = MSEC_PER_TICK * (g_system_timer - g_tickbias); sdbg("msecs = %d g_tickbias=%d\n", (int)msecs, (int)g_tickbias); /* Get the elapsed time in seconds and nanoseconds. */ secs = msecs / MSEC_PER_SEC; nsecs = (msecs - (secs * MSEC_PER_SEC)) * NSEC_PER_MSEC; sdbg("secs = %d + %d nsecs = %d + %d\n", (int)msecs, (int)g_basetime.tv_sec, (int)nsecs, (int)g_basetime.tv_nsec); /* Add the base time to this. */ secs += (uint32_t)g_basetime.tv_sec; nsecs += (uint32_t)g_basetime.tv_nsec; /* Handle carry to seconds. */ if (nsecs > NSEC_PER_SEC) { uint32_t dwCarrySecs = nsecs / NSEC_PER_SEC; secs += dwCarrySecs; nsecs -= (dwCarrySecs * NSEC_PER_SEC); } /* And return the result to the caller. */ tp->tv_sec = (time_t)secs; tp->tv_nsec = (long)nsecs; } sdbg("Returning tp=(%d,%d)\n", (int)tp->tv_sec, (int)tp->tv_nsec); } else { sdbg("Returning ERROR\n"); errno = EINVAL; ret = ERROR; } return ret;}
其中RTC的几个宏定义如下
/* * rtc.h *//**************************************************************************** * Pre-processor Definitions ****************************************************************************//* Configuration ************************************************************//* CONFIG_RTC - Enables general support for a hardware RTC. Specific * architectures may require other specific settings. * * CONFIG_RTC_DATETIME - There are two general types of RTC: (1) A simple * battery backed counter that keeps the time when power is down, and (2) * A full date / time RTC the provides the date and time information, often * in BCD format. If CONFIG_RTC_DATETIME is selected, it specifies this * second kind of RTC. In this case, the RTC is used to "seed" the normal * NuttX timer and the NuttX system timer provides for higher resoution * time. * * CONFIG_RTC_HIRES - If CONFIG_RTC_DATETIME not selected, then the simple, * battery backed counter is used. There are two different implementations * of such simple counters based on the time resolution of the counter: * The typical RTC keeps time to resolution of 1 second, usually * supporting a 32-bit time_t value. In this case, the RTC is used to * "seed" the normal NuttX timer and the NuttX timer provides for higher * resoution time. * * If CONFIG_RTC_HIRES is enabled in the NuttX configuration, then the * RTC provides higher resolution time and completely replaces the system * timer for purpose of date and time. * * CONFIG_RTC_FREQUENCY - If CONFIG_RTC_HIRES is defined, then the frequency * of the high resolution RTC must be provided. If CONFIG_RTC_HIRES is * not defined, CONFIG_RTC_FREQUENCY is assumed to be one. * * CONFIG_RTC_ALARM - Enable if the RTC hardware supports setting of an * alarm. A callback function will be executed when the alarm goes off */
有CONFIG_RTC_HIRES
的说明可以看出,定义了此项后,有飞控板上的纽扣电池支持断电情况下的计时器工作,并且精度也是高高的。
那么问题来了。
经过实际测试,编写时间获取程序,发现,时间是在系统上电时才增加。
int fantasy_test_main(int argc, char *argv[]){ PX4_INFO("Hello Sky!"); time_t timeSec = time(NULL);//1970.01.01 struct tm tt; struct tm *timeinfo = gmtime_r(&timeSec, &tt); PX4_INFO("The time is %d-%d-%d %d:%d:%d \n",\ timeinfo->tm_year+1900,\ timeinfo->tm_mon+1,\ timeinfo->tm_mday,\ timeinfo->tm_hour,\ timeinfo->tm_min,\ timeinfo->tm_sec); return 0;}
时间一直是从2000.01.01 00:00:00
开始的,并且只有USB上电时间才增加,难道电池没电?
而GPS给出的UTC秒数,由下列代码可以看出
## vehicle_gps_position.msg#uint64 time_utc_usec # Timestamp (microseconds, UTC), this is the timestamp which comes from the gps module. It might be unavailable right after cold start, indicated by a value of 0
最后的总结,确定当前系统时间时,先获取已经经过的秒数,再调用struct tm *timeinfo = gmtime_r(&timeSec, &tt);
就能获取tm
的时间了。
先接受一次GPS卫星授时吧,需要3d fix。
关于RTC时间,再看。
By Fantasy
- Pixhawk原生固件PX4之日期时间的确定
- Pixhawk原生固件PX4之正确显示log时间
- Pixhawk原生固件PX4之offboard
- Pixhawk原生固件PX4之常用函数解读
- Pixhawk原生固件PX4之添加uORB主题
- Pixhawk原生固件PX4之commander函数
- Pixhawk原生固件PX4之自定义MAVLink消息
- Pixhawk原生固件PX4之串口读取信息
- Pixhawk原生固件PX4之顶层软件结构
- Pixhawk原生固件PX4之MAVLink协议解析
- Pixhawk原生固件PX4之调节怠速
- Pixhawk原生固件PX4之TAKEOFF的启动流程
- Pixhawk原生固件PX4之驱动ID
- Pixhawk原生固件PX4之SPI驱动注册过程
- Pixhawk原生固件PX4之MPU6000驱动分析
- Pixhawk原生固件PX4之添外置传感器MPU6500
- Pixhawk原生固件PX4之必备git指令
- Pixhawk原生固件PX4之MAVLink外部通讯
- DDS-正弦-Modelsim和MATLAB
- MTK6577+Android之Camera驱动) ~% d
- Binder学习笔记
- 设计模式-状态模式
- 用两个栈实现队列
- Pixhawk原生固件PX4之日期时间的确定
- Spring Aop解析自定义注解
- AJax基于JavaScript实现注册时局部判断用户名是否已被使用
- Android动画最新最全详解包含Material Design动画
- “定时炸弹”
- Spring学习-28:Spring中的事务管理之事务的回顾
- 注解的简单介绍以及自定义注解
- 如何获取客户端访问的IP
- windows环境下安装scikit_learn库