Skip to content

NTP获取时间

image-20240713232047443

image-20240713232618790

NTP报文格式如上图所示,它的字段含义参考如下:

LI 闰秒标识器,占用2个bit

VN 版本号,占用3个bits,表示NTP的版本号,现在为3

Mode 模式,占用3个bits,表示模式

stratum(层),占用8个bits

Poll 测试间隔,占用8个bits,表示连续信息之间的最大间隔

Precision 精度,占用8个bits,,表示本地时钟精度

Root Delay根时延,占用8个bits,表示在主参考源之间往返的总共时延

Root Dispersion根离散,占用8个bits,表示在主参考源有关的名义错误

Reference Identifier参考时钟标识符,占用8个bits,用来标识特殊的参考源

参考时间戳,64bits时间戳,本地时钟被修改的最新时间。

原始时间戳,客户端发送的时间,64bits。

接受时间戳,服务端接受到的时间,64bits。

传送时间戳,服务端送出应答的时间,64bits。

实现

  1. 以 UDP 协议连接阿里云 NTP 服务器。
  2. 发送 NTP 报文到阿里云 NTP 服务器。
  3. 获取阿里云 NTP 服务器返回的数据,取第 40 位到 43 位的十六进制数值。
  4. 把 40 位到 43 位的十六进制数值转成十进制。
  5. 把十进制数值减去 1900-1970 的时间差(2208988800 秒)。
  6. 数值转成年月日时分秒。
c
/**
 *@brief     计算日期时间
 *@param     seconds UUTC 世界标准时间
 *@retval
*/
void lwip_calc_date_time(unsigned long long time)
{
   unsigned int Pass4year;
    int hours_per_year;
    if (time <= 0)
    {
        time = 0;
    }

    g_nowdate.second = (int)(time % 60);   /* 取秒时间 */
    time /= 60;

    g_nowdate.minute = (int)(time % 60);   /* 取分钟时间 */
    time /= 60;
    
    g_nowdate.hour = (int)(time % 24);     /* 小时数 */

    Pass4year = ((unsigned int)time / (1461L * 24L));/*取过去多少个四年,每四年有 1461*24 小时 */

    g_nowdate.year = (Pass4year << 2) + 1970;    /* 计算年份 */

    time %= 1461 * 24;     /* 四年中剩下的小时数 */

    for (;;)               /* 校正闰年影响的年份,计算一年中剩下的小时数 */
    {
        hours_per_year = 365 * 24;         /* 一年的小时数 */

        if ((g_nowdate.year & 3) == 0) /* 判断闰年 */
        {

            hours_per_year += 24; /* 是闰年,一年则多24小时,即一天 */
        }

        if (time < hours_per_year)
        {
            break;
        }

        g_nowdate.year++;
        time -= hours_per_year;
    }

    time /= 24;   /* 一年中剩下的天数 */

    time++;       /* 假定为闰年 */

    if ((g_nowdate.year & 3) == 0)      /* 校正闰年的误差,计算月份,日期 */
    {
        if (time > 60)
        {
            time--;
        }
        else
        {
            if (time == 60)
            {
                g_nowdate.month = 1;
                g_nowdate.day = 29;
                return ;
            }
        }
    }

    for (g_nowdate.month = 0; g_days[g_nowdate.month] < time; g_nowdate.month++) /* 计算月日 */
    {
        time -= g_days[g_nowdate.month];
    }

    g_nowdate.day = (int)(time);

    return;

}
c
typedef struct _NPTformat
{
    char    version;            /* 版本号 */
    char    leap;               /* 时钟同步 */
    char    mode;               /* 模式 */
    char    stratum;            /* 系统时钟的层数 */
    char    poll;               /* 更新间隔 */
    signed char  precision;     /* 精密度 */
    unsigned int   rootdelay;   /* 本地到主参考时钟源的往返时间 */
    unsigned int   rootdisp;    /* 统时钟相对于主参考时钟的最大误差 */
    char    refid;              /* 参考识别码 */
    unsigned long long  reftime;/* 参考时间 */
    unsigned long long  org;    /* 开始的时间戳 */
    unsigned long long  rec;    /* 收到的时间戳 */
    unsigned long long  xmt;    /* 传输时间戳 */
} NPTformat;
c
/**
 *@brief     初始化请求报文
 *@param
 *@retval
*/
void lwip_ntp_client_init(void)
{
    uint8_t flag;

    g_ntpformat.leap = 0;           /* leap indicator */
    g_ntpformat.version = 3;        /* version number */
    g_ntpformat.mode = 3;           /* mode */
    g_ntpformat.stratum = 0;        /* stratum */
    g_ntpformat.poll = 0;           /* poll interval */
    g_ntpformat.precision = 0;      /* precision */
    g_ntpformat.rootdelay = 0;      /* root delay */
    g_ntpformat.rootdisp = 0;       /* root dispersion */
    g_ntpformat.refid = 0;          /* reference ID */
    g_ntpformat.reftime = 0;        /* reference time */
    g_ntpformat.org = 0;            /* origin timestamp */
    g_ntpformat.rec = 0;            /* receive timestamp */
    g_ntpformat.xmt = 0;            /* transmit timestamp */

    flag = (g_ntpformat.version << 3) + g_ntpformat.mode; /* one byte Flag */
    memcpy(g_ntp_message, (void const *)(&flag), 1);//第一位是0x1b
}