WIFI学习六(SNTP)

简介

        SNPT(Simple Network Time Protocal简单网络时间协议)用于跨广域网或局域网时间同步的协议,具有较高的精确度(几十毫秒)。SNTP是NTP协议的简化版

SNTP的工作方式

        SNTP协议采用客户端/服务器的工作方式,可以采用单播(点对点)或者广播(一点对多点)模式操作。

        单播模式下,客户端能够通过定期访问SNTP服务器来获取精确的时间信息,用于调整客户端自身的系统时间。

        广播模式下。SNTP服务器周期性地发送消息给指定的IP广播地址或IP多播地址。SNTP客户端通过监听这些地址来获取时间信息

         网络中一般存在多台SNTP服务器,客户端会通过一定的算法选择最好的几台服务器使用。如果一台SNTP服务器在工作过程中发生异常,则会通知SNTP客户端,那么SNTP客户端就会丢弃发生故障的SNTP服务器发给它的时间信息,然后重新选择其他的SNTP服务器

SNTP校准原理

        SNTP协议主要是通过记录客户端向服务器发送数据包时的时间戳 t1服务器端接收到该数据包时的时间戳 t2服务器向客户端回应的时间戳 t3最后客户端接收到服务器回应时的时间戳 t4来计算客户端时间和服务端时间的偏差,从而进行校准时间操作,如下图所示:

         则 t1 和 t2之间的时间差为((t2 - t1) + (t4 - t3)) / 2

        数据包在网络上传输的时间为(t2 - t1) + (t4 - t3)

        获取到时间差后,设备会校准RTC时间,来保证时间的正确性。

源码分析

sntp_setoperatingmode()

        设置操作模式

函数原型:

void sntp_setoperatingmode(u8_t operating_mode);

参数:

operating_mode 操作模式

SNTP_OPMODE_POLL             //单播模式   
SNTP_OPMODE_LISTENONLY            //组播模式

返回值:

实例:

sntp_setoperatingmode(SNTP_OPMODE_POLL);  //设置为单播模式

sntp_setservername()

        设置要连接的服务器主机名字

函数原型:

void sntp_setservername(u8_t idx, const char *server)
{
  LWIP_ASSERT_CORE_LOCKED();
  if (idx < SNTP_MAX_SERVERS) {
    sntp_servers[idx].name = server;
  }
}

参数:

idx 服务器编号。

server 服务器名字。

        可以看到,服务器可以设置多个,而数量的大小取决于SNTP_MAX_SERVERS参数。这个值可以根据需求自行设置。

返回值:

实例:

sntp_setservername(0, "1.cn.pool.ntp.org");

sntp_setservername(1, "1.hk.pool.ntp.org");

sntp_setserver()

        设置要连接的服务器主机IP

函数原型:

void sntp_setserver(u8_t idx, const ip_addr_t *server)
{
  LWIP_ASSERT_CORE_LOCKED();
  if (idx < SNTP_MAX_SERVERS) {
    if (server != NULL) {
      sntp_servers[idx].addr = (*server);
    } else {
      ip_addr_set_zero(&sntp_servers[idx].addr);
    }
#if SNTP_SERVER_DNS
    sntp_servers[idx].name = NULL;
#endif
  }
}

参数:

idx 服务器编号。

server 服务器IP地址。

返回值:

实例:

struct ip4_addr test_addr;
IP4_ADDR(&test_addr, 213, 161, 194, 93);

sntp_setserver(0, (const ip_addr_t *)(&test_addr));

IP4_ADDR(&test_addr, 129, 6, 15, 29);

sntp_setserver(1, (const ip_addr_t *)(&test_addr));

sntp_init()

        初始化SNTP模块,并创建UDP连接。

函数原型:

void sntp_init(void)
{
#ifdef SNTP_SERVER_ADDRESS
#if SNTP_SERVER_DNS
    sntp_setservername(0, SNTP_SERVER_ADDRESS);
#else
#error SNTP_SERVER_ADDRESS string not supported SNTP_SERVER_DNS==0
#endif
#endif /* SNTP_SERVER_ADDRESS */

    if (sntp_pcb == NULL) {
        sntp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);    //创建一个UDP PCB(协议控制块),IPV4+IPV6
        LWIP_ASSERT("Failed to allocate udp pcb for sntp client", sntp_pcb != NULL);
        if (sntp_pcb != NULL) {
            udp_recv(sntp_pcb, sntp_recv, NULL);    //设置udp接收回调

            if (sntp_opmode == SNTP_OPMODE_POLL) {  //是单播模式
                SNTP_RESET_RETRY_TIMEOUT();
#if SNTP_STARTUP_DELAY
                sys_timeout((u32_t)SNTP_STARTUP_DELAY_FUNC, sntp_request, NULL);
#else
                sntp_request(NULL); //请求
#endif
            } else if (sntp_opmode == SNTP_OPMODE_LISTENONLY) { //广播模式
                ip_set_option(sntp_pcb, SOF_BROADCAST);
                udp_bind(sntp_pcb, IP_ANY_TYPE, SNTP_PORT); //绑定
            }
        }
    }
}

参数:

返回值:

udp_new_ip_type()

        创建一个针对IP类型的PCB(Protocol Control Block协议控制块)

函数原型:

struct udp_pcb *udp_new_ip_type(u8_t type)
{
  struct udp_pcb *pcb;

  LWIP_ASSERT_CORE_LOCKED();

  pcb = udp_new();  //创建一个UDP
#if LWIP_IPV4 && LWIP_IPV6
  if (pcb != NULL) {
    IP_SET_TYPE_VAL(pcb->local_ip,  type);
    IP_SET_TYPE_VAL(pcb->remote_ip, type);
  }
#else
  LWIP_UNUSED_ARG(type);
#endif /* LWIP_IPV4 && LWIP_IPV6 */
  return pcb;
}

参数:

type IP地址类型,类型如下

enum lwip_ip_addr_type {
  /** IPv4 */
  IPADDR_TYPE_V4 =   0U,
  /** IPv6 */
  IPADDR_TYPE_V6 =   6U,
  /** IPv4+IPv6 ("dual-stack") */
  IPADDR_TYPE_ANY = 46U
};

返回值:

NULL失败

其他值 UDP的协议控制块

实例:

static struct udp_pcb* sntp_pcb;
sntp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY); 
if (sntp_pcb != NULL) {
    //成功
}

udp_recv()

        设置一个接收回调,从UDP的协议控制块中

函数原型:

void udp_recv(struct udp_pcb *pcb, udp_recv_fn recv, void *recv_arg)
{
  LWIP_ASSERT_CORE_LOCKED();

  LWIP_ERROR("udp_recv: invalid pcb", pcb != NULL, return);

  /* remember recv() callback and user data */
  pcb->recv = recv;
  pcb->recv_arg = recv_arg;
}

参数:

pcb  UDP协议控制块,由udp_new_ip_type返回所得。

recv UDP接收的回调函数、

recv_arg UDP接收的回调函数的参数

返回值:

实例:

udp_recv(sntp_pcb, sntp_recv, NULL);  

sntp_request()

        发送SNTP请求

函数原型:

static void sntp_request(void *arg)
{
  ip_addr_t sntp_server_address;
  err_t err;

  LWIP_UNUSED_ARG(arg);

  /* initialize SNTP server address */
#if SNTP_SERVER_DNS
  if (sntp_servers[sntp_current_server].name) {
    /* always resolve the name and rely on dns-internal caching & timeout */
    ip_addr_set_zero(&sntp_servers[sntp_current_server].addr);
    err = dns_gethostbyname(sntp_servers[sntp_current_server].name, &sntp_server_address,
                            sntp_dns_found, NULL);
    if (err == ERR_INPROGRESS) {
      /* DNS request sent, wait for sntp_dns_found being called */
      LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_request: Waiting for server address to be resolved.\n"));
      return;
    } else if (err == ERR_OK) {
      sntp_servers[sntp_current_server].addr = sntp_server_address;
    }
  } else
#endif /* SNTP_SERVER_DNS */
  {
    sntp_server_address = sntp_servers[sntp_current_server].addr;
    err = (ip_addr_isany_val(sntp_server_address)) ? ERR_ARG : ERR_OK;
  }

  if (err == ERR_OK) {
    LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_request: current server address is %s\n",
                                   ipaddr_ntoa(&sntp_server_address)));
    sntp_send_request(&sntp_server_address);
  } else {
    /* address conversion failed, try another server */
    LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_request: Invalid server address, trying next server.\n"));
    sys_timeout((u32_t)SNTP_RETRY_TIMEOUT, sntp_try_next_server, NULL);
  }
}

参数:

可以忽略

返回值:

实例:

sntp_request(NULL); //请求

sntp_recv()

        UDP从SNTP的PCB的接收回调

函数原型:

static void sntp_recv(void *arg, struct udp_pcb* pcb, struct pbuf *p, const ip_addr_t *addr,u16_t port)

参数:

arg:接收的数据传参

pcb:协议控制块

p:接收到的数据

addr:IP地址

Port:端口

返回值:

整个函数中,需要注意的是sntp_process函数,该函数相当于对接收到的数据进行数据处理

sntp_process()

        对接收到的时间数据进行处理

函数原型:

static void sntp_process(const struct sntp_timestamps *timestamps)
{
    s32_t sec;
    u32_t frac;

    sec = (s32_t)lwip_ntohl(timestamps->xmit.sec);
    frac = lwip_ntohl(timestamps->xmit.frac);

#if SNTP_COMP_ROUNDTRIP
# if SNTP_CHECK_RESPONSE >= 2 
    if (timestamps->recv.sec != 0 || timestamps->recv.frac != 0)
# endif
    {
        LOGI("Roundtrip compare processing...\n");
        s32_t dest_sec;
        u32_t dest_frac;
        u32_t step_sec;

        SNTP_GET_SYSTEM_TIME_NTP(dest_sec, dest_frac);

        step_sec =
                (dest_sec < sec) ? ((u32_t)sec - (u32_t)dest_sec) : ((u32_t)dest_sec - (u32_t)sec);
        /* In order to avoid overflows, skip the compensation if the clock step
         * is larger than about 34 years. */
        if ((step_sec >> 30) == 0) {
            s64_t t1, t2, t3, t4;
            /* t4 the time sntp client recv the reply, or the current time. */
            t4 = SNTP_SEC_FRAC_TO_S64(dest_sec, dest_frac);
            /* t3 the time sntp server transmitt the reply. */
            t3 = SNTP_SEC_FRAC_TO_S64(sec, frac);
            /* t1 the time sntp client send a request. */
            t1 = SNTP_TIMESTAMP_TO_S64(timestamps->orig);
            /* t2 the time sntp server recv the request. */
            t2 = SNTP_TIMESTAMP_TO_S64(timestamps->recv);
            /* Clock offset calculation according to RFC 4330 */
            t4 += ((t2 - t1) + (t3 - t4)) / 2;

            sec = (s32_t)((u64_t)t4 >> 32);
            frac = (u32_t)((u64_t)t4);
        }
    }
#endif /* SNTP_COMP_ROUNDTRIP */			

    time_t tim = (u32_t)((sec) + DIFF_SEC_1970_2036);

    /* change system time and/or the update the RTC clock */
    SNTP_SET_SYSTEM_TIME(sec);
    /* display local time from GMT time */
    //LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_process: %s", ctime(&tim)));
    LOGI("sntp_process: %s", ctime(&tim));

    LWIP_UNUSED_ARG(tim);
}

参数:

timestamps 时间戳,结构体类型如下:

struct sntp_timestamps
{
#if SNTP_COMP_ROUNDTRIP || SNTP_CHECK_RESPONSE >= 2  
    struct sntp_time orig;  //发送前时间戳
    struct sntp_time recv;  //接收到时间戳
#endif  
    struct sntp_time xmit;  //传输
};

sntp_time类型如下:

struct sntp_time
{
    u32_t sec;  //秒
    u32_t frac; //毫秒
};

返回值:

        从上述代码中可以看到,实际上正如在文章开头所讲的那样SNTP是根据时间差来进行校准时间,计算后得到结果后,需要关注的函数是SNTP_SET_SYSTEM_TIME,后续的数据处理,都在该函数中进行。

#define SNTP_SET_SYSTEM_TIME(sec) sntp_set_system_time((u32_t)((sec)+DIFF_SEC_1970_2036),0)

sntp_set_system_time()

        sntp根据计算得到的结果来设置系统时间。

函数原型:

static void sntp_set_system_time(time_t t, u32_t us)
{
    struct tm *gt = NULL;
    hal_rtc_time_t r_time;
    hal_rtc_status_t st = HAL_RTC_STATUS_OK;

    LOGI("sntp_set_system_time input:  %"U32_F" s,%"U32_F" us\n",t,us);
    gt = gmtime(&t);
    if (gt == NULL) {
        gt = localtime(&t);
    }

    r_time.rtc_year = (gt->tm_year % 100);
    r_time.rtc_mon = gt->tm_mon + 1;
    r_time.rtc_day = gt->tm_mday;
    r_time.rtc_week = gt->tm_wday;
    r_time.rtc_hour = gt->tm_hour;
    r_time.rtc_min = gt->tm_min;
    r_time.rtc_sec = gt->tm_sec;
    st = hal_rtc_set_time(&r_time);

    if (sntp_cb) {
        LOGI("sntp callback\n");
        sntp_cb(r_time);
    }

    LOGI("sntp(%d %d %d ",gt->tm_wday,gt->tm_mon,gt->tm_mday); LOGI("%d:%d:%d %d)\n",gt->tm_hour,gt->tm_min,gt->tm_sec,gt->tm_year); LOGI("sntp(atx):(%d:%d)\n",gt->tm_isdst,gt->tm_yday); LOGI("sntp st1(%u)\n",st);
    st = hal_rtc_get_time(&r_time);
    (void)st;

    LOGI("sntp(%u  %u  %u ",r_time.rtc_week,r_time.rtc_mon,r_time.rtc_day); LOGI("%u:%u:%u %u)\n",r_time.rtc_hour,r_time.rtc_min,r_time.rtc_sec,r_time.rtc_year); LOGI("sntp st2(%u)\n",st);
}

参数:

t 时间戳

us  微秒

返回值:

        该函数将数据进行划分计算,得到最终需要的数据结果,然后设置本地的RTC时间。设置完成后,调用sntp_cb函数来进行用户自己的操作。

 sntp_set_callback()

        设置用户自定义的回调结果

函数原型:

void sntp_set_callback(sntp_callback callback)
{
    sntp_cb = callback;
}

参数:

callback 用户自定义回调结果

返回值:

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: ESP8266是一种低成本、高性能的Wi-Fi芯片,广泛应用于物联网(IoT)领域。SNTP(Simple Network Time Protocol)是一种基于UDP协议的时间同步协议,在物联网设备中具有重要的应用价值。 ESP8266通过SNTP协议与互联网中的时间服务器通信,获得准确的时间戳。这些时间戳可用于日志记录、事件触发、时间截止等应用场景。 SNTP协议本身非常简单,可以使用ESP8266的标准库集成到程序中。通过WiFi连接到局域网或互联网后,ESP8266可以与指定的时间服务器同步,获取与本地时间的时间差。为了准确,ES8266通常使用多个时间服务器进行同步,并进行平均计算。 值得注意的是,SNTP协议是基于UDP协议的,因此无法保证在传输过程中的可靠性。为了解决这个问题,ESP8266通常会定期进行时间同步,以保证时间准确性。当然,若是在时间同步失败的情况下采用短时间运行,错误也会相应地累积。因此,一些特殊的应用场景需要采用GPS等时间同步手段来确保时间的准确性。 总之,ESP8266通过SNTP协议可以快速、准确地获取互联网上的时间戳,因此在物联网设备的时间同步方面拥有巨大的应用潜力。 ### 回答2: ESP8266是一款低成本的Wi-Fi芯片,可以方便的接入互联网。SNTP(Simple Network Time Protocol)是一种简单的网络时间协议,用于同步网络设备的时间。 在ESP8266中,可以使用SNTP协议来同步设备的时间。通过连接SNTP服务器,ESP8266可以获取世界标准时间,并将其与本地时间进行比较和更新。这非常有用,因为设备的时间通常需要与其他设备同步,以便它们可以协调它们的操作。 ESP8266支持SNTP协议的库如下: • sntp.h • sntp.c 这些库可以在ESP8266的开发环境中进行安装和使用。使用这些库,可以轻松地与SNTP服务进行通信,以便同步设备的时间。在使用这些库时,用户需要提供SNTP服务器的IP地址和端口号。然后,ESP8266会连接到服务器并获取当前的时间。 SNTP协议的一个重要特点是具有低带宽和延迟。这使得设备可以在非常低的成本和能耗下同步时间。由于ESP8266的能源需求非常低,因此使用SNTP协议进行时间同步是非常有效的方法。这使得ESP8266成为物联网设备设计的首选芯片之一。 ### 回答3: ESP8266是一款高性能、低功耗、易于开发的Wi-Fi芯片,可以通过SNTP协议进行时间同步。SNTP(Simple Network Time Protocol)是NTP协议的简化版本,用于进行网络时间同步。 在ESP8266上使用SNTP进行时间同步,需要先通过WiFi连接到网络。然后,使用SNTP协议向NTP服务器请求时间信息,并将服务器返回的时间设定为ESP8266的系统时间。这个过程可以使用ESP8266自带的API实现,也可以使用第三方库完成。 使用SNTP同步时间有很多好处,其中最主要的就是可以保证设备时间的准确性。在一些需要高精度时间的应用场景,比如金融、物流等领域,时间同步显得尤为关键。 总之,ESP8266虽然是一款小巧的芯片,但它拥有丰富的功能和强大的性能,可以为各种物联网应用提供稳定可靠的支持。SNTP协议则是保证ESP8266时间准确性的重要手段之一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值