第十章 W55MH32 SNTP示例

目录

1 SNTP协议简介

2 SNTP与NTP的区别

3 SNTP协议特点

4 SNTP应用场景

5 时区介绍

6 通过SNTP协议同步时间的基本流程

7 SNTP协议的报文解析

8 实现过程

9 运行结果


本篇文章我们将详细介绍如何在W55MH32芯片上面实现SNTP授时功能,并通过实战例程,为大家讲解如何让W55MH32从SNTP服务器获取准确的实际时间。

该例程用到的其他网络协议,例如DHCP、DNS请参考相关章节。有关W55MH32的初始化过程,也请参考 Network Install 章节,这里将不再赘述。

1 SNTP协议简介

SNTP(Simple Network Time Protocol)是一种基于UDP协议的网络时间协议,主要用于在计算机网络中同步设备的时间。SNTP旨在提供简单的时间校准服务,比较NTP(Network Time Protocol)而言,SNTP功能更为简单,精度相对较低。

2 SNTP与NTP的区别

SNTP与NTP的区别如下表所示:

特性

SNTP

NTP

精度

精度较低,通常误差在几十毫秒到几百毫秒

高精度,通常误差在毫秒级甚至微秒级

算法复杂度

简单,适用于精度要求不高的应用

复杂,使用精密算法进行校正

服务器选择

通常依赖单一时间服务器

多服务器选择,避免单点故障

应用场景

家庭、办公室、小型设备、嵌入式设备

精确时钟同步的场景,如金融系统、科学研究等

协议复杂性

简单,易于实现

较为复杂,需要更多计算和状态管理

3 SNTP协议特点

  1. 使用UDP通信:SNTP使用UDP协议在端口123进行通信。
  2. 支持请求-响应模式:SNTP使用单次请求-响应模式完成时间同步:客户端向时间服务器发送请求,服务器返回当前时间戳。
  3. 实现和部署成本低:SNTP的实现非常简单,通常只需要少量的代码,便于在嵌入式系统中集成。由于其资源占用少,适合大规模部署。
  4. 与NTP兼容:SNTP 是 NTP 的子集,客户端可以与NTP服务器通信以获取时间。NTP服务器可以无缝提供时间同步服务,无需额外配置。
  5. 支持单向时间同步:在特定场景中(如设备只需同步本地时间,而无需计算网络延迟),SNTP 可以仅基于服务器提供的时间戳完成时间同步。

4 SNTP应用场景

SNTP协议通常用于需要时间同步的场景。通过将时间同步到RTC,能够实现以下功能:

  1. 协同工作:定期同步时间,使各模块能够按照预定的时间顺序执行任务。
  2. 日志与事件管理:确保W55MH32的日志时间和事件记录准确,从而便于后续分析与故障排查。
  3. 定时任务:通过时间同步,支持定时任务的准确执行

5 时区介绍

通过 SNTP 获取世界标准时间 (UTC) 后,需要根据所在时区进行加减运算以计算当地时间。例如,中国位于 UTC+8 时区,在示例代码中定义为 39。因此,获取 UTC 后需加 8 小时才能转换为中国时间。

/*
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±00:00 Cote d'Ivoire, Faroe Islands, Ghana, Iceland, Senegal; (Summer) Ireland, Portugal (continental and Madeira)
22) UTC±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)
*/

6 通过SNTP协议同步时间的基本流程

1. 客户端发送时间请求

  • 客户端向 SNTP 服务器发送一个请求数据包,并记录T1时间戳信息。
  • 请求数据包中通常包含客户端的时间戳(请求发送的时间),以便在计算延迟时使用。

2. 服务器接收请求并处理

  • SNTP 服务器接收到请求后,记录下接收请求的时间T2。
  • 服务器生成一个响应数据包,其中包括以下时间戳信息:
    • T2:服务器接收到请求的时间。
    • T3:服务器发送响应的时间。

3. 客户端接收响应并计算时间

  • 客户端从服务器返回的数据包中提取时间戳信息(T2、T3),并记录接收时间T4。
  • 客户端根据这些时间戳计算本地时间与服务器时间的差异,以及网络延迟:
    • 网络延迟公式: 网络延迟=(T4−T1)−(T3−T2)
    • 本地时间校准公式: 校准时间=T3+(T4−T1)−(T3−T2)/2

4. 调整本地时间

  • 客户端根据校准时间调整本地时钟,以同步到服务器的时间。

7 SNTP协议的报文解析

SNTP的发送和接收报文为固定结构,通常为48字节,报文结构如下:

字节偏移

字段名称

长度(字节)

描述

0

Leap Indicator (LI), Version Number, Mode

1

包含闰秒标志、版本号、模式等信息。

1

Stratum

1

服务器层级(0为未同步,1为主时钟)。

2

Poll Interval

1

轮询间隔,表示客户端请求时间的频率。

3

Precision

1

服务器时间精度。

4–7

Root Delay

4

到主时钟的总延迟,单位为秒的2的负16次方。

8–11

Root Dispersion

4

到主时钟的最大误差,单位为秒的 2 的负 16 次方。

12–15

Reference ID

4

标识时间源(IPv4地址或ASCII标识符)。

16–23

Reference Timestamp

8

参考时间戳,表示最后同步时间的 UTC 时间。

24–31

Originate Timestamp

8

客户端请求时间戳 (T1)。

32–39

Receive Timestamp

8

服务器接收此请求的时间戳 (T2)。

40–47

Transmit Timestamp

8

服务器发送响应时间戳 (T3)。

注意:时间戳的定义是指从1900年1月1日00:00:00 UTC开始的秒数。

SNTP发送请求实例:

偏移

数据

描述

0

23

Leap Indicator (LI): 0

Version Number: 4

Mode: 3 (客户端请求)

1

00

Stratum: 0 (未同步,客户端请求时该字段为 0)

2

00

Poll Interval: 0 (默认值)

3

00

Precision: 0 (未设置)

4 - 7

00000000

Root Delay: 0 (客户端请求时该字段为 0)

8 - 11

00000000

Root Dispersion: 0 (客户端请求时该字段为 0)

12 - 15

00000000

Reference ID: 未设置 (客户端请求时为 0)

16 - 23

0000000000000000

Reference Timestamp: 未设置

24 - 31

0000000000000000

Originate Timestamp (T1): 未设置

32 - 39

0000000000000000

Receive Timestamp (T2): 未设置

40 - 47

0000000000000000

Transmit Timestamp (T3): 未设置

SNTP服务器响应实例:

偏移

数据

描述

0

24

Leap Indicator (LI): 0

Version Number: 4

Mode: 4 (服务器响应)

1

03

Stratum: 3 (三级时钟)

2

00

Poll Interval: 0

3

E7

Precision: -25 (表示时间精度为 2^(-25) 秒)

4 - 7

00000755

Root Delay: 0.007324 秒

8 - 11

00002d0a

Root Dispersion: 0.011017 秒

12 - 15

d30804eb

Reference ID: IPv4 地址 211.8.4.235

16 - 23

2368c6b074d70e00

Reference Timestamp:

秒数部分:0x2368C6B0 (594279216 秒)

小数部分:0x74D70E00 (转换为 0.456789 秒)

24 - 31

0000000000000000

Originate Timestamp (T1): 未设置 (表示客户端未设置请求时间戳)

32 - 39

eb2368fcfb5dfcad

Receive Timestamp (T2):

秒数部分:0xEB2368FC (3949187836 秒)

小数部分:0xFB5DFCAD (转换为 0.982354 秒)

40 - 47

eb2368fcfb618c14

Transmit Timestamp (T3):

秒数部分:0xEB2368FC (3949187836 秒)

小数部分:0xFB618C14 (转换为 0.983214 秒)

8 实现过程

接下来,我们在W55MH32上实现SNTP授时功能。

注意:因为本示例需要访问互联网,请确保 W55MH32 的网络环境及配置能够正常访问互联网。

步骤一:初始化RTC

    RTC_Init();

RTC_Init()函数具体内容如下:

uint8_t RTC_Init(void)
{
    uint8_t temp = 0;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); // 使能PWR和BKP外设时钟
    PWR_BackupAccessCmd(ENABLE);

    RCC_LSEConfig(RCC_LSE_ON);                                        // 设置外部低速晶振(LSE),使用外设低速晶振
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET && temp < 250) // 检查指定的RCC标志位设置与否,等待低速晶振就绪
    {
        temp++;
        delay_ms(10);
    }
    if (temp >= 250) return 1;              // 初始化时钟失败,晶振有问题
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); // 设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
    RCC_RTCCLKCmd(ENABLE);                  // 使能RTC时钟
    RTC_WaitForLastTask();                  // 等待最近一次对RTC寄存器的写操作完成
    RTC_WaitForSynchro();                   // 等待RTC寄存器同步
    RTC_ITConfig(RTC_IT_SEC, ENABLE);       // 使能RTC秒中断
    RTC_WaitForLastTask();                  // 等待最近一次对RTC寄存器的写操作完成
    RTC_EnterConfigMode();                  // 允许配置
    RTC_SetPrescaler(32767);                // 设置RTC预分频的值,计算方式32768/(32767+1) = 1Hz 周期刚好是1秒。
    RTC_WaitForLastTask();                  // 等待最近一次对RTC寄存器的写操作完成
    RTC_Set(1900, 1, 1, 0, 0, 1);           // 设置时间
    RTC_ExitConfigMode();                   // 退出配置模式

    RTC_WaitForSynchro();                   // 等待最近一次对RTC寄存器的写操作完成
    RTC_ITConfig(RTC_IT_SEC, ENABLE);       // 使能RTC秒中断
    RTC_WaitForLastTask();                  // 等待最近一次对RTC寄存器的写操作完成
    RTC_NVIC_Config();                      // RCT中断分组设置
    RTC_Get();                              // 更新时间
    return 0;                               // ok
}

步骤二:通过DNS解析SNTP服务器的IP地址

if (do_dns(ethernet_buf, sntp_server_name, sntp_server_ip))
{
    while (1)
    {
    }
}

步骤三:SNTP初始化

    SNTP_init(SOCKET_ID, sntp_server_ip, timezone, ethernet_buf);

SNTP_init()函数需要传入四个参数,分别是使用的socket通道号,sntp服务器地址,时区,socket缓存,在这个函数中,我们会进行SNTP报文组包操作,并将传入的socket通道号,时区,socket缓存注册到库中,具体内容如下:

void SNTP_init(uint8_t s, uint8_t *ntp_server, uint8_t tz, uint8_t *buf)
{
      NTP_SOCKET = s;

      NTPformat.dstaddr[0] = ntp_server[0];
      NTPformat.dstaddr[1] = ntp_server[1];
      NTPformat.dstaddr[2] = ntp_server[2];
      NTPformat.dstaddr[3] = ntp_server[3];

      time_zone = tz;

      data_buf = buf;

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

      Flag = (NTPformat.leap<<6)+(NTPformat.version<<3)+NTPformat.mode; //one byte Flag
      memcpy(ntpmessage,(void const*)(&Flag),1);
}

步骤四:发送SNTP请求报文获取时间

while (1) // 上电自动获取时间
{
    if (SNTP_run(&date))
    {
        RTC_Set(date.yy, date.mo, date.dd, date.hh, date.mm, date.ss);
        break;
    }
}

接着,我们需要运行SNTP_run()函数来执行发送报文以及解析报文的操作,当成功获取到时间后,我们设置到RTC中。

SNTP_run()函数需要传入一个时间结构体date,它的定义如下:

typedef struct _datetime
{
      uint16_t yy;
      uint8_t mo;
      uint8_t dd;
      uint8_t hh;
      uint8_t mm;
      uint8_t ss;
} datetime;

SNTP_run()函数定义如下:

 

int8_t SNTP_run(datetime *time)
{
      uint16_t RSR_len;
      uint32_t destip = 0;
      uint16_t destport;
      uint16_t startindex = 40; // last 8-byte of data_buf[size is 48 byte] is xmt, so the startindex should be 40

      switch(getSn_SR(NTP_SOCKET))
      {
      case SOCK_UDP:
              if ((RSR_len = getSn_RX_RSR(NTP_SOCKET)) > 0)
              {
                        if (RSR_len > MAX_SNTP_BUF_SIZE) RSR_len = MAX_SNTP_BUF_SIZE;  // if Rx data size is lager than TX_RX_MAX_BUF_SIZE
                        recvfrom(NTP_SOCKET, data_buf, RSR_len, (uint8_t *)&destip, &destport);

                        get_seconds_from_ntp_server(data_buf, startindex);
                        time->yy = Nowdatetime.yy;
                        time->mo = Nowdatetime.mo;
                        time->dd = Nowdatetime.dd;
                        time->hh = Nowdatetime.hh;
                        time->mm = Nowdatetime.mm;
                        time->ss = Nowdatetime.ss;

                        ntp_retry_cnt = 0;
                        close(NTP_SOCKET);

                        return 1;
              }

              if (ntp_retry_cnt < 0xFFFF)
              {
                        if (ntp_retry_cnt == 0) // first send request, no need to wait
                        {
                                sendto(NTP_SOCKET, ntpmessage, sizeof(ntpmessage), NTPformat.dstaddr, ntp_port);
                                ntp_retry_cnt++;
                        }
                        else // send request again? it should wait for a while
                        {
                                if ((ntp_retry_cnt % 0xFFF) == 0) // wait time
                                {
                                        sendto(NTP_SOCKET, ntpmessage, sizeof(ntpmessage), NTPformat.dstaddr, ntp_port);
#ifdef _SNTP_DEBUG_
                                        printf("ntp retry: %d\r\n", ntp_retry_cnt);
#endif
                                        ntp_retry_cnt++;
                                }
                        }
              }
              else // ntp retry fail
              {
                        ntp_retry_cnt = 0;
#ifdef _SNTP_DEBUG_
                        printf("ntp retry failed!\r\n");
#endif
                        close(NTP_SOCKET);
              }
              break;
      case SOCK_CLOSED:
              socket(NTP_SOCKET, Sn_MR_UDP, ntp_port, 0);
              break;
      }

      // Return value
      // 0 - failed / 1 - success
      return 0;
}

在这里会执行一个UDP的状态机,当socket处于SOCK_UDP状态时,首先会执行sendto发送初始化SNTP时组装的SNTP请求报文,然后是监听服务器响应。

步骤五:解析SNTP响应报文

              if ((RSR_len = getSn_RX_RSR(NTP_SOCKET)) > 0)
              {
                        if (RSR_len > MAX_SNTP_BUF_SIZE) RSR_len = MAX_SNTP_BUF_SIZE;  // if Rx data size is lager than TX_RX_MAX_BUF_SIZE
                        recvfrom(NTP_SOCKET, data_buf, RSR_len, (uint8_t *)&destip, &destport);

                        get_seconds_from_ntp_server(data_buf, startindex);
                        time->yy = Nowdatetime.yy;
                        time->mo = Nowdatetime.mo;
                        time->dd = Nowdatetime.dd;
                        time->hh = Nowdatetime.hh;
                        time->mm = Nowdatetime.mm;
                        time->ss = Nowdatetime.ss;

                        ntp_retry_cnt = 0;
                        close(NTP_SOCKET);

                        return 1;
              }

当Sn_RX_RSR (Socket n 空闲接收缓存寄存器)的值大于0时,说明服务器给W55MH32返回了响应。首先会通过recvfrom()函数读取响应报文,然后执行get_seconds_from_ntp_server解析时间。

注意:get_seconds_from_ntp_server()函数目前只解析了服务器响应时间戳(即最后8个字节),没有减去网络延迟。

get_seconds_from_ntp_server()函数定义如下:

void get_seconds_from_ntp_server(uint8_t *buf, uint16_t idx)
{
    tstamp seconds = 0;
    uint8_t i = 0;
    for (i = 0; i < 4; i++)
    {
        seconds = (seconds << 8) | buf[idx + i];
    }
    switch (time_zone)
    {
    case 0:
        seconds -= 12 * 3600;
        break;
    case 1:
        seconds -= 11 * 3600;
        break;
    case 2:
        seconds -= 10 * 3600;
        break;
    case 3:
        seconds -= (9 * 3600 + 30 * 60);
        break;
    case 4:
        seconds -= 9 * 3600;
        break;
    case 5:
    case 6:
        seconds -= 8 * 3600;
        break;
    case 7:
    case 8:
        seconds -= 7 * 3600;
        break;
    case 9:
    case 10:
        seconds -= 6 * 3600;
        break;
    case 11:
    case 12:
    case 13:
        seconds -= 5 * 3600;
        break;
    case 14:
        seconds -= (4 * 3600 + 30 * 60);
        break;
    case 15:
    case 16:
        seconds -= 4 * 3600;
        break;
    case 17:
        seconds -= (3 * 3600 + 30 * 60);
        break;
    case 18:
        seconds -= 3 * 3600;
        break;
    case 19:
        seconds -= 2 * 3600;
        break;
    case 20:
        seconds -= 1 * 3600;
        break;
    case 21:
    case 22:
        break;
    case 23:
    case 24:
    case 25:
        seconds += 1 * 3600;
        break;
    case 26:
    case 27:
        seconds += 2 * 3600;
        break;
    case 28:
    case 29:
        seconds += 3 * 3600;
        break;
    case 30:
        seconds += (3 * 3600 + 30 * 60);
        break;
    case 31:
        seconds += 4 * 3600;
        break;
    case 32:
        seconds += (4 * 3600 + 30 * 60);
        break;
    case 33:
        seconds += 5 * 3600;
        break;
    case 34:
        seconds += (5 * 3600 + 30 * 60);
        break;
    case 35:
        seconds += (5 * 3600 + 45 * 60);
        break;
    case 36:
        seconds += 6 * 3600;
        break;
    case 37:
        seconds += (6 * 3600 + 30 * 60);
        break;
    case 38:
        seconds += 7 * 3600;
        break;
    case 39:
        seconds += 8 * 3600;
        break;
    case 40:
        seconds += 9 * 3600;
        break;
    case 41:
        seconds += (9 * 3600 + 30 * 60);
        break;
    case 42:
        seconds += 10 * 3600;
        break;
    case 43:
        seconds += (10 * 3600 + 30 * 60);
        break;
    case 44:
        seconds += 11 * 3600;
        break;
    case 45:
        seconds += (11 * 3600 + 30 * 60);
        break;
    case 46:
        seconds += 12 * 3600;
        break;
    case 47:
        seconds += (12 * 3600 + 45 * 60);
        break;
    case 48:
        seconds += 13 * 3600;
        break;
    case 49:
        seconds += 14 * 3600;
        break;

    }

    // calculation for date
    calcdatetime(seconds);
}

然后调用calcdatetime()函数将调整后的秒数转换为年、月、日、时、分、秒的具体时间格式:

void calcdatetime(tstamp seconds)
{
    uint8_t yf = 0;
    tstamp n = 0, d = 0, total_d = 0, rz = 0;
    uint16_t y = 0, r = 0, yr = 0;
    signed long long yd = 0;

    n = seconds;
    total_d = seconds / (SECS_PERDAY);
    d = 0;
    uint32_t p_year_total_sec = SECS_PERDAY * 365;
    uint32_t r_year_total_sec = SECS_PERDAY * 366;
    while (n >= p_year_total_sec)
    {
        if ((EPOCH + r) % 400 == 0 || ((EPOCH + r) % 100 != 0 && (EPOCH + r) % 4 == 0))
        {
            if (n < r_year_total_sec)
                break;
            n = n - (r_year_total_sec);
            d = d + 366;

        }
        else
        {
            n = n - (p_year_total_sec);
            d = d + 365;
        }
        r += 1;
        y += 1;

    }

    y += EPOCH;

    Nowdatetime.yy = y;

    yd = 0;
    yd = total_d - d;

    yf = 1;
    while (yd >= 28)
    {

        if (yf == 1 || yf == 3 || yf == 5 || yf == 7 || yf == 8 || yf == 10 || yf == 12)
        {
            yd -= 31;
            if (yd < 0) break;
            rz += 31;
        }

        if (yf == 2)
        {
            if (y % 400 == 0 || (y % 100 != 0 && y % 4 == 0))
            {
                yd -= 29;
                if (yd < 0) break;
                rz += 29;
            }
            else
            {
                yd -= 28;
                if (yd < 0) break;
                rz += 28;
            }
        }
        if (yf == 4 || yf == 6 || yf == 9 || yf == 11)
        {
            yd -= 30;
            if (yd < 0) break;
            rz += 30;
        }
        yf += 1;

    }
    Nowdatetime.mo = yf;
    yr = total_d - d - rz;

    yr += 1;

    Nowdatetime.dd = yr;

    // calculation for time
    seconds = seconds % SECS_PERDAY;
    Nowdatetime.hh = seconds / 3600;
    Nowdatetime.mm = (seconds % 3600) / 60;
    Nowdatetime.ss = (seconds % 3600) % 60;

}

最后在主循环1秒打印一次当前时间:

    while (1)
    {
        printf("Beijing time now: %04d-%02d-%02d  %s  %02d:%02d:%02d\r\n", calendar.w_year, calendar.w_month, calendar.w_date, week_name[calendar.week], calendar.hour, calendar.min, calendar.sec);
        delay_ms(1000);
    }

9 运行结果

烧录例程运行后,首先进行了PHY链路检测,然后是通过DHCP获取网络地址并打印网络地址信息,最后,通过SNTP获取到时间后赋值给RTC,然后主循环1秒打印一次当前时间。如下图所示:

本文讲解了如何在W55MH32芯片上实现SNTP授时功能,通过实例详细展示了从SNTP服务器同步时间的实现流程,包括时间请求、响应解析和本地时间校准等核心步骤。文章还对SNTP的应用场景进行了分析,帮助读者理解其在时间同步中的实际应用价值。

下一篇文章我们将讲解SMTP协议的原理及在邮件通信中的应用,同时讲解如何在W55MH32芯片上实现SMTP功能,敬请期待!

### STM32W55BCGU6低功耗特性与配置 #### 一、STM32W55BCGU6概述 STM32系列微控制器以其高性能和丰富的外设而闻名,其中STM32W55BCGU6型号继承了该家族的优势并特别注重低功耗应用的设计。此款MCU不仅具备强大的处理能力,而且通过多种机制实现了高效的能量管理,在休眠模式下仍能维持必要的外围设备运作。 #### 二、低功耗特性 为了满足不同应用场景的需求,STM32W55BCGU6提供了多样化的省电选项: - **待机模式(Standby Mode)**:当系统处于长时间无活动状态时可启用这种最深程度的节能方式;此时除了RTC之外几乎所有的电路都会被关闭以减少静态电流消耗。 - **停止模式(STOP Mode)**:允许CPU暂停工作但保留SRAM中的数据以及一些基本定时器的功能,以便快速唤醒恢复执行任务。在此期间可以利用外部中断或事件来触发重新激活过程[^4]。 - **睡眠模式(Sleep Mode)**:这是相对较浅的一种节省电量的方法,核心频率降低至最低限度但仍保持足够的响应速度用于即时处理突发情况下的请求。 #### 三、具体配置方法 针对上述提到的各种低功率运行状况,可以通过调整软件设置来进行优化控制: 1. 对于进入STOP模式的情况,开发者可以根据实际需求选择合适的入口函数(`PWR_STOPEntry_WFI` 或 `PWR_STOPEntry_WFE`) 来决定等待条件是特定类型的中断还是任意发生的事件。 ```c if(PWR_STOPEntry == PWR_STOPEntry_WFI) { /* Request Wait For Interrupt */ __WFI(); } else { /* Request Wait For Event */ __WFE(); } ``` 2. 当涉及到更深层次如Standby模式,则需调用相应API接口完成电源管理和实时时钟(RTC)初始化等工作,并确保在此之前保存好所有重要变量的状态信息以防丢失。 3. 此外还有其他参数可供调节比如电压规模(VOS),主PLL分频系数等都对最终达到的效果有着直接影响,因此建议参照官方文档详细了解各项指令的具体含义及其作用范围后再做设定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值