[嵌入式開發模塊]Network Time Protocol(NTP)模塊

前言

NTP協議是用於在TCP/IP網絡上進行對時的一個協議。

要是你是搜到的這個博客,估計根本也就不需要介紹了,這是運用很廣的一個協議,我們windows上的時間對時就是用它實現的。

簡單來說,整個對時過程就是客戶端發送一個包,向NTP服務器索要當前時間,這個請求包中可選的帶上自己發送包時的時間戳,然後服務器在答覆包中加上收到請求時以及發出答覆時自己時間戳, 然後客戶端收到答覆包時再記錄下時間戳;然後,假定網絡延遲在來回時相同,通過一點計算就能消除網絡傳輸延時算出真正的當前時間並更新自己的時間戳。當然,這是完整的NTP過程,對於精度要求不高的場合,直接把客戶端答覆的時間戳賦值給自己就行,又名SNTP(S即Simple)。

這裏給出一個自己寫的模塊,不依賴於網絡協議棧,提供底層的NTP包封裝和解析,目前只提供SNTP解析,因爲實際上對於我目前的需求也夠了。

順帶一提。之所以會弄這個模塊是因爲在基於W5500進行開發時,其提供的NTP庫寫得我特別難受,耦合特別嚴重。就一個很簡單的需求愣是要獨佔一個SOCKET,而且接口用起來很不友好,於是乾脆就照着自己搞一個吧,所以代碼中會有一小部分的相同部分。

源碼

NTP.h

/*
******************************************************************************************
*
*                              NETWORK TIME PROTOCOL MODULE
*                                         NTP模塊
*
* File : NTP.h
* By   : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date:  2019/04/01
* Version: V1.0 
* History: 
* NOTE(s): 1. the module is to provide interfaces for network time protocol.
*          2. the current version only provide the interface for resolving SNTP
*          3. the parameter 'buf' in the signature refers to the original network stream,
*        and 'msg' refers to the packet parameters which are compatible with the local
*        system. In big-endian system, they are the same.
*          4. To use the module:
*        a. prepare a NTPMSG variable or a buffer which is big enough to hold the NTPMSG.
*        b. use NtpMsg_initRequest to init the buffer as NTP request message.
*        c. initialize a socket as udp and call sendto() to send the whole message to
*            NTP server with port NTP_PORT. Then wait for the respond from NTP server.
*        d. If got respond, resolve the frame with NtpMsg_resolveCurTimeSNTP, then you
*            will get the current (NTP) time stamp.
*        e. call NtpTime_toDatetime with the time stamp to get a readable datetime.
******************************************************************************************
*/

#ifndef _NTP_H
#define _NTP_H

/*
*******************************************************************************************
*                                       INCLUDES
*******************************************************************************************
*/

#include <stdint.h>

/*
*******************************************************************************************
*                                     CONSTANT 常量
*******************************************************************************************
*/

//  00 no warning
//  01 last minute has 61 seconds
//  10 last minute has 59 seconds
//  11 alarm condition (clock not synchronized)
#define NTP_LI_NOWARNING  0
#define NTP_LI_61s        1
#define NTP_LI_59s        2
#define NTP_LI_ALARM      3

#define NTP_VERSION       4

#define NTP_MODE_0          0  // reserved
#define NTP_MODE_SYMACTIVE  1  // symmetric active
#define NTP_MODE_SYMPASSIVE 2  // symmetric passive
#define NTP_MODE_CLIENT     3  // client
#define NTP_MODE_SERVER     4  // server
#define NTP_MODE_BROADCAST  5  // broadcast
#define NTP_MODE_6          6  // reserved for NTP control message
#define NTP_MODE_7          7  // reserved for private use

#define DAYS_LEAPYEAR       366
#define DAYS_COMMYEAR       365
#define SECS_PERMIN         60UL
#define SECS_PERHOUR        (60  * SECS_PERMIN)
#define SECS_PERDAY         (24  * SECS_PERHOUR)
#define SECS_LEAPYEAR       (DAYS_LEAPYEAR * SECS_PERDAY)
#define SECS_COMMYEAR       (DAYS_COMMYEAR * SECS_PERDAY)
#define SECS_1900to1970     0x83aa7e80
#define SECS_1900to2019     0xdfd52c00
// NTP server Domain name of windows
#define NTPSERVER_NAME_WINDOWS  "time.windows.com"
// NTP server IP Address of National Time Service Center Chinese Academy Of Scences
#define NTPSERVER_IP_CHINA  210,72,145,44
#define NTP_PORT            123            // NTP server port number
#define EPOCH               1900           // NTP start year

/*
*******************************************************************************************
*                                     TYPE DEFINITION
*******************************************************************************************
*/

typedef uint32_t NtpSeconds;

typedef struct DATETIME_STRUCT{
  uint16_t yy;
  uint8_t  mo;
  uint8_t  dd;
  uint8_t  hh;
  uint8_t  mm;
  uint8_t  ss;
} Datetime;

typedef struct NTPTIME_STRUCT{
  NtpSeconds sec;
  uint32_t   frac;  // fractions
} NtpTime;

typedef struct NTPMSG_STRUCT{
  // Leap Indicator (LI): This is a two-bit code warning of an impending leap second to be inserted/deleted
  // in the last minute of the current day, with bit 0 and bit 1, respectively. (see NTP_LI_XXXX)
  //unsigned int LI  :2;
  // Version Number (VN): This is a three-bit integer indicating the NTP version number, currently three (4).
  //unsigned int VN  :3;
  // Mode: This is a three-bit integer indicating the mode. (see NTP_MODE_XXX)
  //unsigned int Mode:3; 
  // fields  | LI |  VN | Mode |
  // bitPos  | XX | XXX | XXX  |
  uint8_t Flag;
  // Stratum: This is a eight-bit integer indicating the stratum level of the local clock, with values 
  // defined as follows:
  // 0 unspecified
  // 1 primary reference (e.g., radio clock)
  // 2-255 secondary reference (via NTP)
  // The values that can appear in this field range from zero to NTP.INFIN inclusive
  uint8_t Stratum;
  // Poll Interval: This is an eight-bit signed integer indicating the maximum interval between successive
  // messages, in seconds to the nearest power of two. The values that can appear in this field range from 
  // NTP.MINPOLL to NTP.MAXPOLL inclusive.
  int8_t  Poll;
  // Precision: This is an eight-bit signed integer indicating the precision of the local clock, in seconds
  // to the nearest power of two.
  int8_t  Precision;
//  int8_t       Precision;
  // Root Delay: This is a 32-bit signed fixed-point number indicating the total roundtrip delay to the
  // primary reference source, in seconds with fraction point between bits 15 and 16. Note that this
  // variable can take on both positive and negative values, depending on clock precision and skew.
  int32_t RootDelay;
  // Root Dispersion: This is a 32-bit signed fixed-point number indicating the maximum error relative
  // to the primary reference source, in seconds with fraction point between bits 15 and 16. Only
  // positive values greater than zero are possible.
  int32_t RootDisp;
  // Reference Clock Identifier: This is a 32-bit code identifying the particular reference clock.
  char    ClkID[4];
  // Reference Timestamp: This is the local time at which the local clock was last set or corrected, in
  // 64-bit timestamp format.
  NtpTime RefStamp;
  // Originate Timestamp: This is the local time at which the request departed the client host for the
  // service host, in 64-bit timestamp format.
  NtpTime OrgStamp;
  // Receive Timestamp: This is the local time at which the request arrived at the service host, in 64-bit
  // timestamp format.
  NtpTime RxStamp;
  // Transmit Timestamp: This is the local time at which the reply departed the service host for the client
  // host, in 64-bit timestamp format.
  NtpTime TxStamp;
  // Authenticator (optional): When the NTP authentication mechanism is implemented, this contains
  // the authenticator information defined in Appendix C.
} NTPMSG;

/*
*******************************************************************************************
*                                     INTERFACES
*******************************************************************************************
*/

// initialize the NTP message frame to request time.
// description: initialize the buffer to the NTP frame which will be sended to request time.
// parameter  : buf        the buffer to store the frame.
//              orgStamp   the local time at which the request departed the client host for the
//                         service host. NULL if not used.
// return     : length of the message
// note       :
uint16_t NtpMsg_initRequest(uint8_t * buf, NtpTime const * orgStamp);
// resolve the current time from the NTP message just received.(SNTP)
// description: resolve the current time from the NTP message just received.
// parameter  : buf    the NTP message just received.
//              ret    return the current time stamp.
// return     :
// note       : after call this function, you can pass ret->sec to  NtpTime_toDatetime to 
//              get the date time.
void NtpMsg_resolveCurTimeSNTP(uint8_t const * buf, NtpTime * ret);
// set the first byte of NTP message
// description: used to set the first byte of NTP message
// parameter  : buf    the NTP message buffer.
//              LI     see NTP_LI_XXX
//              VN     expect NTP_VERSION
//              Mode   see NTP_MODE_XXX
// return     :
// note       : 
void NtpMsg_setFlag(NTPMSG * buf,uint8_t LI, uint8_t VN, uint8_t Mode);
// NTP second to Datetime
// description: used to resolve the NTP second in NTP timestamp to the human readable datetime.
// parameter  : seconds    the NTP seconds.
//              ret        return the resolve result.
// return     :
// note       : 
void NtpTime_toDatetime(NtpSeconds seconds, Datetime * ret);

#endif // _NTP_H 

NTP.c

/*
******************************************************************************************
*
*                              NETWORK TIME PROTOCOL MODULE
*                                         NTP模塊
*
* File : NTP.c
* By   : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date:  2019/04/01
* Version: V1.0 
* History: 
* NOTE(s): 
******************************************************************************************
*/

/*
*******************************************************************************************
*                                       INCLUDES
*******************************************************************************************
*/

#include <string.h>
#include "NTP.h"
#include "NetworkLib.h"

/*
*******************************************************************************************
*                                     CONFIGURATION 配置
*******************************************************************************************
*/
/*
00)UTC-12:00 Baker Island, Howland Island (both uninhabited)
01) UTC-11:00 American Samoa, Samoa
02) UTC-10:00 (Summer)French Polynesia (most), United States (Aleutian Islands, Hawaii)
03) UTC-09:30 Marquesas Islands
04) UTC-09:00 Gambier Islands;(Summer)United States (most of Alaska)
05) UTC-08:00 (Summer)Canada (most of British Columbia), Mexico (Baja California)
06) UTC-08:00 United States (California, most of Nevada, most of Oregon, Washington (state))
07) UTC-07:00 Mexico (Sonora), United States (Arizona); (Summer)Canada (Alberta)
08) UTC-07:00 Mexico (Chihuahua), United States (Colorado)
09) UTC-06:00 Costa Rica, El Salvador, Ecuador (Galapagos Islands), Guatemala, Honduras
10) UTC-06:00 Mexico (most), Nicaragua;(Summer)Canada (Manitoba, Saskatchewan), United States (Illinois, most of Texas)
11) UTC-05:00 Colombia, Cuba, Ecuador (continental), Haiti, Jamaica, Panama, Peru
12) UTC-05:00 (Summer)Canada (most of Ontario, most of Quebec)
13) UTC-05:00 United States (most of Florida, Georgia, Massachusetts, most of Michigan, New York, North Carolina, Ohio, Washington D.C.)
14) UTC-04:30 Venezuela
15) UTC-04:00 Bolivia, Brazil (Amazonas), Chile (continental), Dominican Republic, Canada (Nova Scotia), Paraguay,
16) UTC-04:00 Puerto Rico, Trinidad and Tobago
17) UTC-03:30 Canada (Newfoundland)
18) UTC-03:00 Argentina; (Summer) Brazil (Brasilia, Rio de Janeiro, Sao Paulo), most of Greenland, Uruguay
19) UTC-02:00 Brazil (Fernando de Noronha), South Georgia and the South Sandwich Islands
20) UTC-01:00 Portugal (Azores), Cape Verde
21) UTC&#177;00:00 Cote d'Ivoire, Faroe Islands, Ghana, Iceland, Senegal; (Summer) Ireland, Portugal (continental and Madeira)
22) UTC&#177;00:00 Spain (Canary Islands), Morocco, United Kingdom
23) UTC+01:00 Angola, Cameroon, Nigeria, Tunisia; (Summer)Albania, Algeria, Austria, Belgium, Bosnia and Herzegovina,
24) UTC+01:00 Spain (continental), Croatia, Czech Republic, Denmark, Germany, Hungary, Italy, Kinshasa, Kosovo,
25) UTC+01:00 Macedonia, France (metropolitan), the Netherlands, Norway, Poland, Serbia, Slovakia, Slovenia, Sweden, Switzerland
26) UTC+02:00 Libya, Egypt, Malawi, Mozambique, South Africa, Zambia, Zimbabwe, (Summer)Bulgaria, Cyprus, Estonia,
27) UTC+02:00 Finland, Greece, Israel, Jordan, Latvia, Lebanon, Lithuania, Moldova, Palestine, Romania, Syria, Turkey, Ukraine
28) UTC+03:00 Belarus, Djibouti, Eritrea, Ethiopia, Iraq, Kenya, Madagascar, Russia (Kaliningrad Oblast), Saudi Arabia,
29) UTC+03:00 South Sudan, Sudan, Somalia, South Sudan, Tanzania, Uganda, Yemen
30) UTC+03:30 (Summer)Iran
31) UTC+04:00 Armenia, Azerbaijan, Georgia, Mauritius, Oman, Russia (European), Seychelles, United Arab Emirates
32) UTC+04:30 Afghanistan
33) UTC+05:00 Kazakhstan (West), Maldives, Pakistan, Uzbekistan
34) UTC+05:30 India, Sri Lanka
35) UTC+05:45 Nepal
36) UTC+06:00 Kazakhstan (most), Bangladesh, Russia (Ural: Sverdlovsk Oblast, Chelyabinsk Oblast)
37) UTC+06:30 Cocos Islands, Myanmar
38) UTC+07:00 Jakarta, Russia (Novosibirsk Oblast), Thailand, Vietnam
39) UTC+08:00 China, Hong Kong, Russia (Krasnoyarsk Krai), Malaysia, Philippines, Singapore, Taiwan, most of Mongolia, Western Australia
40) UTC+09:00 Korea, East Timor, Russia (Irkutsk Oblast), Japan
41) UTC+09:30 Australia (Northern Territory);(Summer)Australia (South Australia))
42) UTC+10:00 Russia (Zabaykalsky Krai); (Summer)Australia (New South Wales, Queensland, Tasmania, Victoria)
43) UTC+10:30 Lord Howe Island
44) UTC+11:00 New Caledonia, Russia (Primorsky Krai), Solomon Islands
45) UTC+11:30 Norfolk Island
46) UTC+12:00 Fiji, Russia (Kamchatka Krai);(Summer)New Zealand
47) UTC+12:45 (Summer)New Zealand
48) UTC+13:00 Tonga
49) UTC+14:00 Kiribati (Line Islands)
*/
// timezone china
static const uint8_t time_zone = 39;

/*
*******************************************************************************************
*                              LOCAL FUNCTION DECLARATION
*******************************************************************************************
*/

// get the local ntp timestamp for the standard one, depends on the timezone.
static NtpSeconds _stdTimeSecToLocal(NtpSeconds sec);

/*
*******************************************************************************************
*                                 IMPLEMENTATIONS
*******************************************************************************************
*/

uint16_t NtpMsg_initRequest(uint8_t * buf, NtpTime const * orgStamp){
  NTPMSG * pMsg = (NTPMSG *)buf;
  (void)memset(buf,0,sizeof(NTPMSG));
  NtpMsg_setFlag(pMsg,NTP_LI_NOWARNING, NTP_VERSION, NTP_MODE_CLIENT);
  if(orgStamp != NULL){
    (void)NetworkStream_putUint32((uint8_t *)&pMsg->OrgStamp.sec, orgStamp->sec);
    (void)NetworkStream_putUint32((uint8_t *)&pMsg->OrgStamp.frac, orgStamp->frac);
  }
  return sizeof(NTPMSG);
}

void NtpMsg_resolveCurTimeSNTP(uint8_t const * buf, NtpTime * ret){
  NTPMSG const * pMsg = (NTPMSG const *)buf;
  (void)NetworkStream_getUint32((uint8_t *)&pMsg->TxStamp.sec, &ret->sec);
  (void)NetworkStream_getUint32((uint8_t *)&pMsg->TxStamp.frac, &ret->frac);
}

void NtpMsg_setFlag(NTPMSG * buf,uint8_t LI, uint8_t VN, uint8_t Mode){
  *(uint8_t *)buf = (LI << 6) + ((VN & 0x07) << 3) + (Mode & 0x07);
}

static const uint8_t daytab[2][13] = {
  { 0,31,28,31,30,31,30,31,31,30,31,30,31 },
  { 0,31,29,31,30,31,30,31,31,30,31,30,31 }
};

void _DayLeftInAYearToMonthAndDay(uint16_t dayLeft,uint8_t isLeapYear,uint8_t * mon,uint8_t * day){
  uint8_t monthCur = 1;
  while(dayLeft >= daytab[isLeapYear][monthCur]){
    dayLeft -= daytab[isLeapYear][monthCur];
    ++monthCur;
  }
  *mon = monthCur;
  *day = dayLeft + 1;
}

void NtpTime_toDatetime(NtpSeconds sec, Datetime * ret){
  uint8_t isLeap;
  uint32_t dayLeft;
  uint16_t yearCur;

  sec = _stdTimeSecToLocal(sec);
  if(sec >= SECS_1900to2019){
    sec -= SECS_1900to2019;
    yearCur = 2019;
  }else{
    yearCur = EPOCH;
  }

  dayLeft = sec / (SECS_PERDAY);

  for(;;){
    isLeap = (yearCur % 400 == 0 || (yearCur % 100 != 0 && yearCur % 4 == 0));
    if(dayLeft < (uint32_t)(DAYS_COMMYEAR + isLeap))
      break;
    dayLeft -= (uint32_t)(DAYS_COMMYEAR + isLeap);
    ++yearCur;
  }

  ret->yy = yearCur;

  _DayLeftInAYearToMonthAndDay((uint16_t)dayLeft,isLeap,&ret->mo,&ret->dd);
  sec %= SECS_PERDAY;
  ret->hh = (uint8_t)(sec / SECS_PERHOUR);
  sec %= SECS_PERHOUR;
  ret->mm = (uint8_t)(sec / SECS_PERMIN);
  ret->ss = (uint8_t)(sec % SECS_PERMIN);
}

/*
*******************************************************************************************
*                              LOCAL FUNCTION IMPLEMENTATION
*******************************************************************************************
*/

static NtpSeconds _stdTimeSecToLocal(NtpSeconds sec){
  switch (time_zone){
  case 0:
    sec -= 12 * SECS_PERHOUR;
    break;
  case 1:
    sec -= 11 * SECS_PERHOUR;
    break;
  case 2:
    sec -= 10 * SECS_PERHOUR;
    break;
  case 3:
    sec -= (9 * SECS_PERHOUR+30 * SECS_PERMIN);
    break;
  case 4:
    sec -= 9 * SECS_PERHOUR;
    break;
  case 5:
  case 6:
    sec -= 8 * SECS_PERHOUR;
    break;
  case 7:
  case 8:
    sec -= 7 * SECS_PERHOUR;
    break;
  case 9:
  case 10:
    sec -= 6 * SECS_PERHOUR;
    break;
  case 11:
  case 12:
  case 13:
    sec -= 5 * SECS_PERHOUR;
    break;
  case 14:
    sec -= (4 * SECS_PERHOUR + 30 * SECS_PERMIN);
    break;
  case 15:
  case 16:
    sec -= 4 * SECS_PERHOUR;
    break;
  case 17:
    sec -= (3 * SECS_PERHOUR + 30 * SECS_PERMIN);
    break;
  case 18:
    sec -= 3 * SECS_PERHOUR;
    break;
  case 19:
    sec -= 2 * SECS_PERHOUR;
    break;
  case 20:
    sec -= 1 * SECS_PERHOUR;
    break;
  case 21:
  case 22:
    break;
  case 23:
  case 24:
  case 25:
    sec += 1 * SECS_PERHOUR;
    break;
  case 26:
  case 27:
    sec += 2 * SECS_PERHOUR;
    break;
  case 28:
  case 29:
    sec += 3 * SECS_PERHOUR;
    break;
  case 30:
    sec += (3 * SECS_PERHOUR + 30 * SECS_PERMIN);
    break;
  case 31:
    sec += 4 * SECS_PERHOUR;
    break;
  case 32:
    sec += (4 * SECS_PERHOUR + 30 * SECS_PERMIN);
    break;
  case 33:
    sec += 5 * SECS_PERHOUR;
    break;
  case 34:
    sec += (5 * SECS_PERHOUR + 30 * SECS_PERMIN);
    break;
  case 35:
    sec += (5 * SECS_PERHOUR + 45 * SECS_PERMIN);
    break;
  case 36:
    sec += 6 * SECS_PERHOUR;
    break;
  case 37:
    sec += (6 * SECS_PERHOUR + 30 * SECS_PERMIN);
    break;
  case 38:
    sec += 7 * SECS_PERHOUR;
    break;
  case 39:
    sec += 8 * SECS_PERHOUR;
    break;
  case 40:
    sec += 9 * SECS_PERHOUR;
    break;
  case 41:
    sec += (9 * SECS_PERHOUR + 30 * SECS_PERMIN);
    break;
  case 42:
    sec += 10 * SECS_PERHOUR;
    break;
  case 43:
    sec += (10 * SECS_PERHOUR + 30 * SECS_PERMIN);
    break;
  case 44:
    sec += 11 * SECS_PERHOUR;
    break;
  case 45:
    sec += (11 * SECS_PERHOUR + 30 * SECS_PERMIN);
    break;
  case 46:
    sec += 12 * SECS_PERHOUR;
    break;
  case 47:
    sec += (12 * SECS_PERHOUR + 45 * SECS_PERMIN);
    break;
  case 48:
    sec += 13 * SECS_PERHOUR;
    break;
  case 49:
    sec += 14 * SECS_PERHOUR;
    break;
  }
  return sec;
}

NetworkLib.h

#ifndef _NETWORK_LIB_H
#define _NETWORK_LIB_H

#include <stdint.h>


// to help produce better code
// #define _CPU_BIGENDIAN
// or 
// #define _CPU_LITTLEENDIAN

// NetworkStream_getUintXX
// description: get uintXX from the network stream ns and return the pointer to the
//              next position in the stream.
// parameter  : ns    the stream to get uintXX.
//              ret   return the result through the pointer.
// return     : the pointer to the next position in the stream.
// note       : the interface will handle the byte order for you.
uint8_t const * NetworkStream_getUint16(uint8_t const * ns,uint16_t * ret);
uint8_t const * NetworkStream_getUint32(uint8_t const * ns,uint32_t * ret);
// NetworkStream_putUintXX
// description: put uintXX to the network stream ns and return the pointer to the
//              next position in the stream.
// parameter  : ns    the stream to get uintXX.
//              val   the value to be put.
// return     : the pointer to the next position in the stream.
// note       : the interface will handle the byte order for you.
uint8_t * NetworkStream_putUint16(uint8_t * ns,uint16_t val);
uint8_t * NetworkStream_putUint32(uint8_t * ns,uint32_t val);
// whether the cpu is big endian system.
// return     : 1  if is big endian.
//              0  if is little endian.
int isBigEndianSys(void);

#ifdef _CPU_BIGENDIAN
#define htonl(h)   (h)
#define htons(h)   (h)
#define ntohl(n)   (n)
#define ntohs(n)   (n)
#else
#define htonl(h)   NetworkLib_htonl(h)
#define htons(h)   NetworkLib_htons(h)
#define ntohl(n)   NetworkLib_ntohl(n)
#define ntohs(n)   NetworkLib_ntohs(n)
#endif

uint32_t NetworkLib_htonl(uint32_t hostlong);
uint16_t NetworkLib_htons(uint16_t hostshort);
uint32_t NetworkLib_ntohl(uint32_t netlong);
uint16_t NetworkLib_ntohs(uint16_t netshort);

#endif

NetworkLib.c

#include <stdio.h>
#include "NetworkLib.h"

#ifdef _CPU_BIGENDIAN
#define _isBigEndianSys()  1
#endif

#ifdef _CPU_LITTLEENDIAN
#define _isBigEndianSys()  0
#endif

#define BigLittleSwap16(A)  ((((uint16_t)(A) & 0xff00) >> 8) | \
                            (((uint16_t)(A) & 0x00ff) << 8))

#define BigLittleSwap32(A)  ((((uint32_t)(A) & 0xff000000) >> 24) | \
                            (((uint32_t)(A) & 0x00ff0000) >> 8) | \
                            (((uint32_t)(A) & 0x0000ff00) << 8) | \
                            (((uint32_t)(A) & 0x000000ff) << 24))

#ifndef _isBigEndianSys
#define _isBigEndianSys()  (*(uint8_t *)&_flag == 0)
static const uint16_t _flag = 1;
#endif

int isBigEndianSys(void){
  return _isBigEndianSys();
}

uint8_t const * NetworkStream_getUint16(uint8_t const * ns,uint16_t * ret){
  if(_isBigEndianSys()){
    * ret = *(uint16_t *)ns;
  }else{
    ((uint8_t *)ret)[0] = ns[1];
    ((uint8_t *)ret)[1] = ns[0];
  }
  return ns + sizeof(uint16_t);
}

uint8_t const * NetworkStream_getUint32(uint8_t const * ns,uint32_t * ret){
  if(_isBigEndianSys()){
    * ret = *(uint32_t *)ns;
  }else{
    ((uint8_t *)ret)[0] = ns[3];
    ((uint8_t *)ret)[1] = ns[2];
    ((uint8_t *)ret)[2] = ns[1];
    ((uint8_t *)ret)[3] = ns[0];
  }
  return ns + sizeof(uint32_t);
}

uint8_t * NetworkStream_putUint16(uint8_t * ns,uint16_t val){
  if(_isBigEndianSys()){
    *(uint16_t *)ns = val;
  }else{
    ns[0] = ((uint8_t *)&val)[1];
    ns[1] = ((uint8_t *)&val)[0];
  }
  return ns + sizeof(uint16_t);
}
uint8_t * NetworkStream_putUint32(uint8_t * ns,uint32_t val){
  if(_isBigEndianSys()){
    *(uint32_t *)ns = val;
  }else{
    ns[0] = ((uint8_t *)&val)[3];
    ns[1] = ((uint8_t *)&val)[2];
    ns[2] = ((uint8_t *)&val)[1];
    ns[3] = ((uint8_t *)&val)[0];
  }
  return ns + sizeof(uint32_t);
}

uint32_t NetworkLib_htonl(uint32_t hostlong){
  if(_isBigEndianSys()){
    return hostlong;
  }else{
    return BigLittleSwap32(hostlong);
  }
}

uint16_t NetworkLib_htons(uint16_t hostshort){
  if(_isBigEndianSys()){
    return hostshort;
  }else{
    return BigLittleSwap16(hostshort);
  }
}

uint32_t NetworkLib_ntohl(uint32_t netlong){
  if(_isBigEndianSys()){
    return netlong;
  }else{
    return BigLittleSwap32(netlong);
  }
}
uint16_t NetworkLib_ntohs(uint16_t netshort){
  if(_isBigEndianSys()){
    return netshort;
  }else{
    return BigLittleSwap16(netshort);
  }
}

NTPTimeHelper.h

/*
*******************************************************************************************
*
*                                   NTP TIME HELPER MODULE
*
* File : NTPTimeHelper.h
* By   : Lin Shijun(https://blog.csdn.net/lin_strong)
* Date:  2019/09/26
* version: V1.0
* History: 
* note   : the convert interfaces for NTP time.
*********************************************************************************************
*/
#ifndef _NTPTimeHelper_H
#define _NTPTimeHelper_H

#include "NTP.h"
#include <time.h>
typedef struct POSIXTIME_STRUCT{  
   time_t sec;     //秒
   uint32_t usec;  //微秒
} PosixTime;

void NtpTimeToPosixTime(PosixTime *dst, const NtpTime *src);
void PosixTimeToNtpTime(NtpTime *dst, const PosixTime *src);

#endif

NTPTimeHelper.c

/*
*******************************************************************************************
*
*                                   NTP TIME HELPER MODULE
*
* File : NTPTimeHelper.c
* By   : Lin Shijun(https://blog.csdn.net/lin_strong)
* Date:  2019/09/26
* version: V1.0
* History: 
* note   : the convert interfaces for NTP time.
*********************************************************************************************
*/
#include "NTPTimeHelper.h"

#pragma MESSAGE DISABLE C5919
void NtpTimeToPosixTime(PosixTime *dst, const NtpTime *src){
  int valid;
  if(dst == NULL || src == NULL)
    return;
  valid = (src->sec >= SECS_1900to1970);
  dst->sec = (valid)? src->sec - SECS_1900to1970: 0;
  // 1000000/0x100000000 = 0.0002328306436538696...
  dst->usec = (valid)? (uint32_t)((double)src->frac * (double)0.0002328306436538696): 0;
}

void PosixTimeToNtpTime(NtpTime *dst, const PosixTime *src){
  int valid;
  if(dst == NULL || src == NULL)
    return;
  valid = (src->sec <= (0xFFFFFFFF - SECS_1900to1970));
  dst->sec  = (valid)? src->sec + SECS_1900to1970: 0;
  // 0x100000000/1000000 = 4294.967296
  dst->frac = (valid)?(uint32_t)((double)src->usec * 4294.967296): 0;
}

示例

使用起來就是這句話:
初始化一個請求包用UDP發往NTP服務器對應端口,然後等待答覆包並解析出結果。

以下示例基於W5500的官方io庫+uCOSII,已略去如創建任務、初始化網絡等不重要的代碼:

#include "NTP.h"
#define UDP_SOCKET 5

static uint8_t NtpServerIP[4] = {NTPSERVER_IP_CHINA};
static uint8_t udpBuf[200];

static void ConsoleTask (void *p_arg){
  ……
  for(;;){
    c = (uint8_t)getchar();
    if(c == 'n'){
      (void)NtpMsg_initRequest(udpBuf,NULL);
      (void)printf("Send Ntp request\r\n");
      (void)sendto(UDP_SOCKET,udpBuf,sizeof(NTPMSG),NtpServerIP,NTP_PORT);
    }
  }
}

static void UdpRxTask(void *p_arg){
  uint8_t dip[4];
  uint16_t dport;
  int32_t len;
  static Datetime dt;
  NtpTime t;
  (void)p_arg;
  for(;;){
    // 假設網絡已經初始化完成 
    ……
    (void)printf("UDPRx task initing socket.\r\n");
    switch(socket(UDP_SOCKET,Sn_MR_UDP,MDNS_PORT,0)){
      case SOCKERR_SOCKINIT:
        (void)printf("UDPRx init socket fail, network not ready.\r\n");
        (void)OSTimeDlyHMSM(0,0,2,0);
        continue;
      case UDP_SOCKET:
        (void)printf("UDPRx init socket success.\r\n");
        break;
      default:
        (void)printf("UDPRx init socket fail, unknown error.\r\n");
        (void)OSTimeDlyHMSM(0,0,2,0);
        continue;
    }
    for(;;){
      (void)printf("UDPRx task is wating for data!\r\n");
      len = recvfrom(UDP_SOCKET,udpBuf,sizeof(udpBuf),dip,&dport);
      if(len > 0){
         (void)printf("UDPRx received data from %u.%u.%u.%u:%u\r\n",dip[0], dip[1], dip[2], dip[3],dport);
        if(len >= sizeof(NTPMSG) && dport == NTP_PORT){
          printf("NTP packet.\r\n");
          NtpMsg_resolveCurTimeSNTP((NTPMSG *)udpBuf, &t);
          NtpTime_toDatetime(t.sec,&dt);
          printf("%4u-%2u-%2u %2u:%2u:%2u\r\n",dt.yy,dt.mo,dt.dd,dt.hh,dt.mm,dt.ss);
        }
      }else if(len < 0){
        (void)printf("UDPRx found socket err.\r\n");
        break;
      }
    }
  }
}

因爲是獨立的模塊,你可以根據自己的需求自己決定如何整合NTP功能至項目中,比如,其實可以多個基於UDP的功能(比如DNS,其實我也寫了,可能後面會發)都使用同一個UDP SOCKET,通過對方的IP和/或端口號來區分是哪個功能。

測試結果:
在這裏插入圖片描述至於後面那個TimeHelper,則是用於NTP時間戳和Unix時間戳互轉的接口。當你需要使用time.h時自然明白它的作用了。就不示例了。

更新歷史

2019/05/29 第一版
2019/10/02 增加NTPTimeHelper模塊。並更新了NetworkLib,增加了點功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章