W55MH32从网络获取时间
更新于 2026年9月30日
本篇总结
在 W55MH32 上实现 SNTP 授时功能,从 SNTP 服务器获取准确时间并同步到 RTC,主循环每秒打印一次北京时间。
硬件平台:本例程基于 W55MH32L-EVB 开发板运行。
采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB
本示例演示如何在 W55MH32 上实现 SNTP 授时功能,通过实战例程讲解如何让 W55MH32 从 SNTP 服务器获取准确的实际时间。该例程用到的其他网络协议(例如 DHCP、DNS),请参考相关章节。有关 W55MH32 的初始化过程,请参考 Network install 章节,这里不再赘述。
前置工具准备
开始本例程前,请确认以下工具已安装:
- WIZ UartTool V1.0:串口调试工具:点击下载
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 协议同步时间的基本流程
- 客户端发送时间请求:客户端向 SNTP 服务器发送请求数据包,并记录 T1 时间戳信息
- 服务器接收请求并处理:服务器记录接收时间 T2,生成响应数据包,包含 T2(接收时间)和 T3(发送时间)
- 客户端接收响应并计算时间:客户端提取 T2、T3,记录接收时间 T4,计算网络延迟和本地时间校准值
- 调整本地时间:客户端根据校准时间调整本地时钟,同步到服务器时间
时间戳定义
时间戳的定义是指从 1900 年 1 月 1 日 00:00:00 UTC 开始的秒数。SNTP 的发送和接收报文为固定结构,通常为 48 字节。
硬件连接与拓扑
请注意:
本示例需要访问互联网,请确保 W55MH32 的网络环境及配置能够正常访问互联网。
- Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
- 网线连接开发板 RJ45 网口 到路由器 LAN 口,路由器需连接互联网
│ W55MH32 │──网线──│ 路由器/网关 │──互联网──│ SNTP 服务器 │
│ (SNTP │ │ (DHCP/DNS) │ │ cn.pool.ntp.org │
│ Client) │ │ 192.168.1.1 │ │ UDP :123 │
└───────────┘ └─────────────────┘ └──────────────────┘
↑
DEBUG USB ──→ WIZ UartTool
工程结构
工程位于 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 获取成功即更新为正确时间。

常见问题
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 - 应用价值:为日志时间戳、定时任务、多模块协同提供准确的本地时间基准