全部例程

W55MH32从网络获取时间

W55MH32

更新于 2026年9月30日

本篇总结

在 W55MH32 上实现 SNTP 授时功能,从 SNTP 服务器获取准确时间并同步到 RTC,主循环每秒打印一次北京时间。

🔑 KEIL MDK、UART调试、硬件TOE引擎、SNTP、DNS、DHCP、RTC

硬件平台:本例程基于 W55MH32L-EVB 开发板运行。

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB

本示例演示如何在 W55MH32 上实现 SNTP 授时功能,通过实战例程讲解如何让 W55MH32 从 SNTP 服务器获取准确的实际时间。该例程用到的其他网络协议(例如 DHCP、DNS),请参考相关章节。有关 W55MH32 的初始化过程,请参考 Network install 章节,这里不再赘述。

前置工具准备

开始本例程前,请确认以下工具已安装:

SNTP 协议简介

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

SNTP 协议特点:

  • 使用 UDP 通信:SNTP 使用 UDP 协议在端口 123 进行通信
  • 支持请求-响应模式:单次请求-响应完成时间同步——客户端发送请求,服务器返回当前时间戳
  • 实现和部署成本低:实现简单,通常只需要少量代码,便于嵌入式系统集成
  • 与 NTP 兼容:SNTP 是 NTP 的子集,客户端可与 NTP 服务器通信以获取时间
  • 支持单向时间同步:特定场景下可仅基于服务器时间戳完成时间同步,无需计算网络延迟

SNTP 应用场景

通过将时间同步到 RTC,实现:协同工作(各模块按预定时间顺序执行任务)、日志与事件管理(确保日志时间和事件记录准确)、定时任务(支持定时任务准确执行)。

时区介绍

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

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

  1. 客户端发送时间请求:客户端向 SNTP 服务器发送请求数据包,并记录 T1 时间戳信息
  2. 服务器接收请求并处理:服务器记录接收时间 T2,生成响应数据包,包含 T2(接收时间)和 T3(发送时间)
  3. 客户端接收响应并计算时间:客户端提取 T2、T3,记录接收时间 T4,计算网络延迟和本地时间校准值
  4. 调整本地时间:客户端根据校准时间调整本地时钟,同步到服务器时间

时间戳定义

时间戳的定义是指从 1900 年 1 月 1 日 00:00:00 UTC 开始的秒数。SNTP 的发送和接收报文为固定结构,通常为 48 字节。

硬件连接与拓扑

请注意:

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

  1. Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
  2. 网线连接开发板 RJ45 网口 到路由器 LAN 口,路由器需连接互联网
┌───────────┐ ┌─────────────────┐ ┌──────────────────┐
│ W55MH32 │──网线──│ 路由器/网关 │──互联网──│ SNTP 服务器 │
│ (SNTP │ │ (DHCP/DNS) │ │ cn.pool.ntp.org │
│ Client) │ │ 192.168.1.1 │ │ UDP :123 │
└───────────┘ └─────────────────┘ └──────────────────┘
↑
DEBUG USB ──→ WIZ UartTool
网络拓扑:W55MH32 通过路由器接入互联网,从 SNTP 服务器获取时间

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\WZTOE\SNTP\,核心文件如下:

SNTP/
├── Project/
│   └── Project.uvprojx       ← Keil 工程文件
└── USER/
    ├── main.c                ← 初始化 + RTC + DNS + SNTP 主流程
    ├── sntp.c/h              ← SNTP 协议实现(SNTP_init / SNTP_run)
    ├── bsp_rtc.c/h           ← RTC 驱动(RTC_Init / RTC_Set / RTC_Get)
    ├── do_dns/               ← DNS 域名解析
    └── uart/
        └── bsp_uart.c/h      ← 调试串口 + printf 重定向

步骤1:初始化 RTC

文件:USER/bsp_rtc.c — 配置外部低速晶振 LSE(32.768kHz)作为 RTC 时钟源,设置预分频值 32767 得到 1Hz 计数(1 秒)。

uint8_t RTC_Init(void)
{
    uint8_t temp = 0;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    PWR_BackupAccessCmd(ENABLE);

    RCC_LSEConfig(RCC_LSE_ON);
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET && temp < 250)
    {
        temp++;
        delay_ms(10);
    }
    if (temp >= 250) return 1;

    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    RCC_RTCCLKCmd(ENABLE);
    RTC_WaitForLastTask();
    RTC_WaitForSynchro();
    RTC_ITConfig(RTC_IT_SEC, ENABLE);
    RTC_WaitForLastTask();
    RTC_EnterConfigMode();
    RTC_SetPrescaler(32767);
    RTC_WaitForLastTask();
    RTC_Set(1900, 1, 1, 0, 0, 1);
    RTC_ExitConfigMode();

    RTC_WaitForSynchro();
    RTC_ITConfig(RTC_IT_SEC, ENABLE);
    RTC_WaitForLastTask();
    RTC_NVIC_Config();
    RTC_Get();
    return 0;
}

RTC_Init() 完成 LSE 晶振使能、RTC 时钟源选择、预分频设置(32768/32768 = 1Hz)、初始时间设置(1900-01-01 00:00:01)、秒中断使能。后续 SNTP 获取到正确时间后,通过 RTC_Set() 更新。

步骤2:通过 DNS 解析 SNTP 服务器域名

文件:USER/main.c — 使用 do_dns() 将 SNTP 服务器域名 cn.pool.ntp.org 解析为 IP 地址。

uint8_t sntp_server_name[] = "cn.pool.ntp.org";
uint8_t sntp_server_ip[4]  = {162, 159, 200, 1};

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

do_dns() 将域名解析为 IP 写入 sntp_server_ip。DNS 解析失败时死循环等待。

步骤3:SNTP 初始化

文件:USER/sntp.c | 函数:SNTP_init() — 传入 Socket 通道号、SNTP 服务器地址、时区和缓存,完成 SNTP 报文组包并注册。

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;
    NTPformat.poll      = 0;
    NTPformat.precision = 0;
    NTPformat.rootdelay = 0;
    NTPformat.rootdisp  = 0;
    NTPformat.refid     = 0;
    NTPformat.reftime   = 0;
    NTPformat.org       = 0;
    NTPformat.rec       = 0;
    NTPformat.xmt       = 1;   /* transmit timestamp */

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

关键参数:version=4(NTP v4)、mode=3(客户端模式)、Flag 字节由 leap、version、mode 拼接而成。

步骤4:发送 SNTP 请求报文并解析响应

文件:USER/sntp.c | 函数:SNTP_run() — 运行 UDP 状态机:SOCK_CLOSED 时打开 Socket,SOCK_UDP 时发送请求并监听响应,收到响应后解析时间戳并转换为日期。

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

    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;
            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)
            {
                sendto(NTP_SOCKET, ntpmessage, sizeof(ntpmessage),
                       NTPformat.dstaddr, ntp_port);
                ntp_retry_cnt++;
            }
            else
            {
                if((ntp_retry_cnt % 0xFFF) == 0)
                {
                    sendto(NTP_SOCKET, ntpmessage, sizeof(ntpmessage),
                           NTPformat.dstaddr, ntp_port);
                    ntp_retry_cnt++;
                }
            }
        }
        else
        {
            ntp_retry_cnt = 0;
            close(NTP_SOCKET);
        }
        break;
    case SOCK_CLOSED:
        socket(NTP_SOCKET, Sn_MR_UDP, ntp_port, 0);
        break;
    }
    return 0;
}

关键点:SNTP 响应报文 48 字节,最后 8 字节是服务器发送时间戳(xmt),因此 startindex = 40。get_seconds_from_ntp_server() 解析时间戳并按时区偏移,calcdatetime() 转换为年/月/日/时/分/秒。

时区处理

get_seconds_from_ntp_server() 里通过 time_zone 变量对 UTC 秒数进行偏移。中国 UTC+8 对应 time_zone = 39,此时 seconds += 8*3600。

步骤5:同步 RTC 并打印北京时间

文件:USER/main.c — 循环调用 SNTP_run() 直到获取时间成功,将时间写入 RTC,然后主循环每秒打印一次。

SNTP_init(SOCKET_ID, sntp_server_ip, timezone, ethernet_buf);
while (1) //上电自动获取时间
{
    if (SNTP_run(&date))
    {
        RTC_Set(date.yy, date.mo, date.dd, date.hh, date.mm, date.ss);
        break;
    }
}
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);
}

运行验证

打开 WIZ UartTool V1.0,连接 DEBUG USB 对应 COM 口(115200 / 8N1)。烧录例程运行后,首先进行 PHY 链路检测和 DHCP 获取网络地址,然后 DNS 解析 SNTP 服务器,获取时间并赋值给 RTC,主循环每秒打印一次当前时间。预期串口输出如下:

W55MH32 SNTP example
PHY link
The current Mbtis speed : 100Mbps
The current Duplex Mode : Full-Duplex
DHCP running
DHCP success!
====================================================================================================
 W55MH32 network configuration : DHCP

 MAC         : 00:08:DC:00:0E:9A
 IP          : 192.168.1.6

 Subnet Mask : 255.255.255.0
 Gateway     : 192.168.1.1
 DNS         : 192.168.1.1
====================================================================================================

> Translated cn.pool.ntp.org to 193.182.111.143
Beijing time now: 0000-00-00  Tuesday  00:00:00
Beijing time now: 0000-00-00  Tuesday  00:00:00
Beijing time now: 0000-00-00  Tuesday  00:00:00
Beijing time now: 0000-00-00  Tuesday  00:00:00
Beijing time now: 0000-00-00  Tuesday  00:00:00
Beijing time now: 0000-00-00  Tuesday  00:00:00
Beijing time now: 2026-09-30  Wednesday  08:59:38
Beijing time now: 2026-09-30  Wednesday  08:59:38
Beijing time now: 2026-09-30  Wednesday  08:59:38
Beijing time now: 2026-09-30  Wednesday  08:59:38
Beijing time now: 2026-09-30  Wednesday  08:59:39
Beijing time now: 2026-09-30  Wednesday  08:59:39

首次打印可能出现 0000-00-00 Tuesday 00:00:00 的异常值,这是因为 RTC 初始化后尚未收到 SNTP 响应,1 秒后 SNTP 获取成功即更新为正确时间。

WIZ UartTool 串口输出 — SNTP 授时成功,RTC 同步北京时间

常见问题

Q: SNTP 和 NTP 有什么区别

SNTP 是 NTP 的简化版本,功能更简单、精度相对较低,但实现和部署成本低,适合嵌入式系统。NTP 精度更高,但协议复杂,需要更多计算资源。SNTP 是 NTP 的子集,可以与 NTP 服务器通信获取时间。

Q: 为什么首次打印时间显示 0000-00-00

RTC 初始化后立即进入主循环打印,但此时 SNTP 可能还没收到响应,RTC 中仍是无效值。等待 SNTP 获取成功并写入 RTC 后,时间即可正确显示。这是正常的启动过程,不影响后续运行。

Q: 为什么 SNTP 使用 UDP 而不是 TCP

SNTP 采用单次请求-响应模式,报文小(48 字节),使用 UDP 可以降低协议开销,避免 TCP 三次握手的延迟。SNTP 只需要一次请求和一次响应即可完成时间同步,UDP 的无连接特性恰好满足这种需求。

Q: 时区参数 time_zone 为什么是 39 而不是 8

time_zone 不是时区小时数,而是代码中定义的时区编号。代码里维护了一张从 UTC-12 到 UTC+14 的完整时区表,中国 UTC+8 对应表中第 39 项,因此传入 39。这个设计便于覆盖全球所有时区(包括半小时、45 分钟时区)。

Q: SNTP 获取时间失败怎么办

排查步骤:① 确认开发板能访问互联网;② 确认 DNS 解析成功(串口打印 > Translated cn.pool.ntp.org to ...);③ 检查 123 端口是否被防火墙拦截;④ 更换其他 SNTP 服务器(如 time.windows.com);⑤ 检查 RTC 的 LSE 晶振是否正常起振。

总结

本文讲解了如何在 W55MH32 芯片上实现 SNTP 授时功能:

  • SNTP 协议:基于 UDP 123 端口的简化网络时间协议,单次请求-响应完成时间同步,实现简单、资源占用少
  • 时区处理:SNTP 返回 UTC 时间,需按时区偏移计算当地时间,中国 UTC+8 对应 time_zone = 39
  • 初始化流程:RTC 初始化(LSE 32768Hz)→ DNS 解析 SNTP 服务器 → SNTP_init 组包 → SNTP_run 循环获取 → RTC_Set 写入 → 主循环打印
  • 关键 API:SNTP_init() 初始化、SNTP_run() 循环获取、RTC_Set() 写入 RTC
  • 应用价值:为日志时间戳、定时任务、多模块协同提供准确的本地时间基准

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家