引言
单片机平台上进行网络应用开发时,总会遇到Unix时间戳与本地时间转换的问题。对于使用低端MCU+WIFI模块的硬件方案,策略是使用MCU的一个定时器,配置定时器1S产生一次中断,中断处理函数中对uint32_t的变量做累加,用于表示本机时间。当MCU通过WIFI模块获取到网络时间后,计算网络时间与本机时间的差值,并保存此差值。 此后MCU端需要使用Unix时间戳是,就无需通过WIFI模块获取网络事件,而是直接通过本地时间和保存的时间差值做累加,即可得出相对准确的Unix时间戳。此种方式维护的时间戳肯定存在误差,仅用于对时间精度要求不高且硬件方案中无RTC模块的方案
以下具体实现过程。如有错误,欢迎提出。
流程图
Created with Rapha?l 2.3.0
开始
启动定时器
并开启中断
获取网络时间
获取成功?
计算本机时间偏差
根据本机时间与时间偏差输出unix时间戳
结束
yes
no
1、定时器相关代码片段
uint32_t globalSystemTime=0;
uint32_t timeAbsDifference = 0;
void TimerInit(void)
{
}
void Timer_IRQHandler(void)
{
if()
{
globalSystemTime++;
}
}
void UpdateUnixTime(uint32_t unix)
{
timeAbsDifference = unix - globalSystemTime;
}
uint32_t GetUnixTime(void)
{
uint32_t u32SysTime = 0;
u32SysTime = globalSystemTime + timeAbsDifference;
return u32SysTime;
}
至此,时间戳本地更新及获取逻辑已经展示完成,下面将展示时间戳转换相关的代码。
2、时间戳转换
uint32_t SEC_PER_YR[2] = {31536000, 31622400};
uint32_t SEC_PER_MT[2][12] = {
{2678400, 2419200, 2678400, 2592000, 2678400, 2592000,
2678400, 2678400, 2592000, 2678400, 2592000, 2678400},
{2678400, 2505600, 2678400, 2592000, 2678400, 2592000,
2678400, 2678400, 2592000, 2678400, 2592000, 2678400},
};
static int is_leap(int yr)
{
if (NULL == (yr % 100))
return (yr % 400 == 0) ? 1 : 0;
else
return (yr % 4 == 0) ? 1 : 0;
}
static unsigned char day_of_week_get(unsigned char month, unsigned char day,
unsigned short year)
{
static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
year -= (uint8_t)(month < 3);
return (year + year / 4 - year / 100 + year / 400 + t[month - 1] + day) % 7;
}
void transformTime(struct devtm *result)
{
int leapyr = 0;
uint32_t ltime = GetUnixTime() + 28800;
memset(result, 0, sizeof(struct devtm));
result->tm_year = EPOCH_YR;
while (1)
{
if (ltime < SEC_PER_YR[is_leap(result->tm_year)])
{
break;
}
ltime -= SEC_PER_YR[is_leap(result->tm_year)];
++(result->tm_year);
}
leapyr = is_leap(result->tm_year);
while (1)
{
if (ltime < SEC_PER_MT[leapyr][result->tm_mon])
break;
ltime -= SEC_PER_MT[leapyr][result->tm_mon];
++(result->tm_mon);
}
result->tm_mday = ltime / SEC_PER_DY;
++(result->tm_mday);
ltime = ltime % SEC_PER_DY;
result->tm_hour = ltime / SEC_PER_HR;
ltime = ltime % SEC_PER_HR;
result->tm_min = ltime / 60;
result->tm_sec = ltime % 60;
result->tm_wday =
day_of_week_get(result->tm_mon + 1, result->tm_mday,
result->tm_year);
result->tm_year -= YEAR0;
}
int getLocalTimeStr(char *pbuf)
{
struct devtm timeInfo;
if (pbuf == 0)
{
return 0;
}
transformTime(&timeInfo);
snprintf(pbuf, 32, "<%4d-%02d-%02d %02d:%02d:%02d>", timeInfo.tm_year + YEAR0,
timeInfo.tm_mon + 1,
timeInfo.tm_mday,
timeInfo.tm_hour,
timeInfo.tm_min,
timeInfo.tm_sec);
return 1;
}
#define YEAR0 (1900)
#define EPOCH_YR (1970)
#define SEC_PER_DY (86400)
#define SEC_PER_HR (3600)
typedef struct devtm
{
uint16_t tm_year;
uint8_t tm_mon;
uint8_t tm_mday;
uint8_t tm_hour;
uint8_t tm_min;
uint8_t tm_sec;
uint8_t tm_wday;
}tm_t;
void transformTime(struct devtm *result);
int getLocalTimeStr(char *pbuf);
至此,时间戳转换相关的代码片段展示完毕。
3、字符串转换函数
考虑到从WIFI模块获取到的时间戳信息可能是以字符串形式发送至MCU,现将字符串转换函数放在下方。
uint32_t UT_GetInt(char **p, int DefaultValue)
{
uint32_t Value = 0;
bool UseDefault;
UseDefault = true;
UT_SkipBlanks(p);
while (((**p) <= '9' && (**p) >= '0'))
{
Value = Value * 10 + (**p) - '0';
UseDefault = false;
++(*p);
}
for (; **p == ' '; ++(*p))
;
if (UseDefault)
{
return DefaultValue;
}
else
{
return Value;
}
}
void StrTimeToValue(char *timeStr)
{
uint32_t unixTimeValue = 0;
char *str = NULL;
char **p = &str;
str = timeStr;
unixTimeValue = UT_GetInt(p,0);
UpdateUnixTime(unixTime);
}
好了,终于将想展示的都写完了,好累~~~~
|