W55MH32 solution NTP protocol implements a network timer that integrates time acquisition
W55MH32 solution NTP protocol implements a network timer that integrates time acquisition and time synchronization.
1 Introduction
NTP (Network Time Protocol) is a standardized protocol used for time synchronization in computer networks. Through a client-server model, local devices obtain accurate time from a specified time server and calibrate their local clocks by taking into account factors such as network latency and clock drift. Ultimately, it achieves high-precision synchronization with UTC standard time (accuracy at the millisecond or even microsecond level, affected by network environment and server level). Its core is to ensure the consistency of distributed devices in the time dimension.
The W55MH32L-EVB is a development board based on the W55MH32L chip, with a main frequency of 216MHz, 1MB of flash memory and 96KB of SRAM. It also features a complete hardware TCP/IP offload engine, allowing for Ethernet applications to be implemented with simple socket programming.
Features include:
- Enhanced, true random number generation, hardware encryption algorithm unit
- On-chip 32-bit Arm® Cortex®-M3 core
- Microcontroller with 1024KB flash memory
- 10/100M Ethernet MAC and PHY, integrated full hardware TCP/IP protocol stack engine
- USB, CAN, 17 timers
- 3 ADCs, 2 DACs, 12 communication interfaces
2 Project Environment
2.1 Hardware Environment
- W55MH32L-EVB
- Network connection cable
- Switch or router
2.2 Software Environment
- Development environment: Keil uVision 5
- Freescale Serial Port Assistant
- NTP service address
3 Hardware Connection and Solution
3.1 W5500 Hardware Connection
W55MH32L-EVB network port connects to the switch or router via network cable
W55MH32L-EVB WIZ-Link USB port connects to the computer via data cable
3.2 Solution Diagram

4. Introduction to Time Types and Timestamps
4.1 Unix Timestamps and Date Representation
Unix timestamps represent the offset from Coordinated Universal Time (UTC) January 1, 1970, 00:00:00. There are 24 time zones globally, divided into 12 east and 12 west. All regions use the same timestamp, adjusting the time representation according to their local time zone. Currently, the most common date and time representation standard is ISO 8601, or the more standardized RFC 3339.
For example, Beijing time January 28, 2021, 00:00:00 is represented in RFC 3339 as: 2021-01-28T00:00:00+08:00.
+08:00 indicates East 8 time zone (the entire territory of China excluding Hong Kong, Macau, and Taiwan, but which also uses this time in practice). 2021-01-28T00:00:00 indicates the time seen by people in this time zone. If the plus sign is changed to a minus sign, it indicates the Western Time Zone. The UTC time zone is a special case; it can be represented as 2006-01-02T15:04:05+00:00, but is usually simplified to 2006-01-02T15:04:05Z.
4.2 Date and Time Parsing
Different data sources may use different time representation methods. Based on readability, they are divided into two categories:
Time stamps represented by numbers
Year, month, day, hour, minute, and second represented by strings
Number types will not be explained in detail.
Strings are further divided into two categories based on whether they contain a time zone:
2021-01-28 00:00:00 No time zone information included
2021-01-28T08:00:00+08:00 Includes time zone information.
When parsing strings without time zone information, the programmer usually needs to specify the time zone; otherwise, the default is UTC. If a time zone is specified, it doesn't need to be explicitly specified.
When using these strings, the time display should be adjusted according to the time zone. For example, 1611792000 can be represented as 2021-01-28T00:00:00Z or 2021-01-28T08:00:00+08:00.
5. Main Program Analysis
5.1 Analysis of main.c
By initializing hardware (clock, serial port, etc.), network (WIZnet chip configuration, DHCP), and time modules (RTC, NTP), the main loop synchronizes the time from the NTP server to the local RTC every 10 minutes, prints the current time every second, responds to external NTP time requests, and processes UDP data, integrating NTP client, server, and RTC synchronization functions into one.
/******************************************************************************
* @file main.c
* @brief NTP client + server + RTC synchronization
******************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "wizchip_conf.h"
#include "wiz_interface.h"
#include "bsp_tim.h"
#include "bsp_uart.h"
#include "bsp_rcc.h"
#include "delay.h"
#include "loopback.h"
#include "socket.h"
#include "ntp.h"
#include "w55mh32_rtc.h"
#define SOCKET_ID 0
#define ETHERNET_BUF_MAX_SIZE (1024 * 2)
/* Default network information (DHCP) */
wiz_NetInfo default_net_info = {
.mac = {0x00, 0x08, 0xdc, 0x12, 0x22, 0x05},
.ip = {192, 168, 1, 30},
.gw = {192, 168, 1, 1},
.sn = {255, 255, 255, 0},
.dns = {8, 8, 8, 8},
.dhcp = NETINFO_DHCP
};
uint16_t local_port = 8080;
uint8_t ethernet_buf[ETHERNET_BUF_MAX_SIZE] = {0};
int main(void)
{
rcc_clk_config();
delay_init();
console_usart_init(115200);
tim3_init();
printf("%s NTP example\r\n", _WIZCHIP_ID_);
RTC_Init(); /* Initialize RTC, prescaler compensated for 4 ticks */
wiz_toe_init();
wiz_phy_link_check();
network_init(ethernet_buf, &default_net_info);
ntp_init(); /* Initialize NTP module */
/* ---------- First NTP synchronization ---------- */
ntp_first_sync();
uint32_t last_sync_tick = ntp_get_tick_count();
uint32_t last_print_tick = 0;
/* ---------- Main loop ---------- */
while (1)
{
/* Synchronize again every 10 minutes */
if (ntp_get_tick_count() - last_sync_tick > 600000)
{
ntp_do_sync();
last_sync_tick = ntp_get_tick_count();
}
/* Print the current time once per second */
if (ntp_get_tick_count() - last_print_tick >= 1000)
{
ntp_print_current_time();
last_print_tick = ntp_get_tick_count();
}
ntp_server_task();
loopback_udps(SOCKET_ID, ethernet_buf, local_port);
}
}5.2 ntp.c Analysis
The UDP socket is initialized via ntp_init and the Alibaba Cloud NTP server IP is resolved. The client sends a request to the server via ntp_get_time to obtain the Unix timestamp and synchronize it to the RTC. ntp_first_sync and ntp_do_sync are responsible for the initial and periodic synchronization and printing of UTC and CST times. The server listens on port 123 via ntp_server_task, and generates a response based on the local RTC time after receiving a request. It also provides utility functions such as timestamp and date/time conversion, formatted printing, etc. Time management is implemented using timers and RTC hardware, and network communication is completed via the UDP protocol.
/******************************************************************************
* @file ntp.c
* @brief NTP client & server with encapsulated time functions
******************************************************************************/
#include "ntp.h"
#include "wizchip_conf.h"
#include "socket.h"
#include "w55mh32_rtc.h"
#include "bsp_tim.h"
#include "delay.h"
#include <string.h>
#include <stdio.h>
#define NTP_CLIENT_SOCKET 1
#define NTP_SERVER_SOCKET 2
#define NTP_PACKET_SIZE 48
#define NTP_PORT 123
#define NTP_TIMEOUT 3000 /* ms */
#define NTP_TIMESTAMP_DELTA 2208988800UL /* 1900→1970 Second difference */
static uint8_t ntp_server_ip[4];
static uint8_t ntp_buffer[NTP_PACKET_SIZE];
/* ---------- Utility function implementation ---------- */
uint32_t ntp_get_tick_count(void) {
return TIM3->CNT;
}
DateTime ntp_unix_to_datetime(uint32_t unix_time)
{
DateTime dt;
uint32_t days = unix_time / 86400;
uint32_t sec = unix_time % 86400;
uint8_t mdays[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
dt.year = 1970;
while (1) {
uint16_t ydays = 365 + (((dt.year % 4 == 0 && dt.year % 100 != 0) || (dt.year % 400 == 0)) ? 1 : 0);
if (days >= ydays) { days -= ydays; dt.year++; }
else break;
}
mdays[1] = (((dt.year % 4 == 0 && dt.year % 100 != 0) || (dt.year % 400 == 0)) ? 29 : 28);
dt.month = 1;
for (int m = 0; m < 12; ++m) {
if (days < mdays[m]) break;
days -= mdays[m];
dt.month++;
}
dt.day = days + 1;
dt.hour = sec / 3600;
dt.minute= (sec % 3600) / 60;
dt.second= sec % 60;
return dt;
}
void ntp_print_datetime(DateTime dt)
{
printf("%04d-%02d-%02d %02d:%02d:%02d\r\n",
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second);
}
static uint8_t getDNSbyName(const char *domain, uint8_t *ip)
{
(void)domain;
ip[0] = 203; ip[1] = 107; ip[2] = 6; ip[3] = 88;
return 0;
}
/* ---------- NTP initialization ---------- */
void ntp_init(void)
{
if (getDNSbyName("ntp.aliyun.com", ntp_server_ip) == 0)
printf("Resolved ntp.aliyun.com to %d.%d.%d.%d\r\n",
ntp_server_ip[0], ntp_server_ip[1],
ntp_server_ip[2], ntp_server_ip[3]);
socket(NTP_CLIENT_SOCKET, Sn_MR_UDP, 0, 0);
socket(NTP_SERVER_SOCKET, Sn_MR_UDP, NTP_PORT, 0);
}
/* ---------- Get UNIX timestamp ---------- */
uint32_t ntp_get_time(void)
{
uint8_t src_ip[4]; uint16_t src_port;
int16_t len;
memset(ntp_buffer, 0, NTP_PACKET_SIZE);
ntp_buffer[0] = 0x1B; /* NTP client request identification */
sendto(NTP_CLIENT_SOCKET, ntp_buffer, NTP_PACKET_SIZE, ntp_server_ip, NTP_PORT);
uint32_t start = ntp_get_tick_count();
while (ntp_get_tick_count() - start < NTP_TIMEOUT)
{
len = recvfrom(NTP_CLIENT_SOCKET, ntp_buffer, NTP_PACKET_SIZE, src_ip, &src_port);
if (len >= NTP_PACKET_SIZE &&
memcmp(src_ip, ntp_server_ip, 4) == 0 && src_port == NTP_PORT)
{
/* Extract the timestamp returned by the server and convert it to UNIX time */
uint32_t ntp_sec = ((uint32_t)ntp_buffer[40] << 24) |
((uint32_t)ntp_buffer[41] << 16) |
((uint32_t)ntp_buffer[42] << 8) |
((uint32_t)ntp_buffer[43]);
return ntp_sec - NTP_TIMESTAMP_DELTA;
}
delay_ms(10);
}
printf("NTP timeout\r\n");
return 0;
}
/* ---------- NTP server task ---------- */
void ntp_server_task(void)
{
uint8_t src_ip[4];
uint16_t src_port;
int16_t len = recvfrom(NTP_SERVER_SOCKET, ntp_buffer, NTP_PACKET_SIZE, src_ip, &src_port);
if (len >= NTP_PACKET_SIZE && (ntp_buffer[0] & 0x07) == 0x03)
{
uint32_t rtc = RTC_GetCounter();
if (!rtc) { printf("Invalid RTC, skip response\r\n"); return; }
uint32_t tx = rtc + NTP_TIMESTAMP_DELTA;
memset(ntp_buffer, 0, NTP_PACKET_SIZE);
ntp_buffer[0] = 0x24; /* NTP server response identifier */
ntp_buffer[40] = (tx >> 24) & 0xFF;
ntp_buffer[41] = (tx >> 16) & 0xFF;
ntp_buffer[42] = (tx >> 8) & 0xFF;
ntp_buffer[43] = tx & 0xFF;
sendto(NTP_SERVER_SOCKET, ntp_buffer, NTP_PACKET_SIZE, src_ip, src_port);
printf("Sent NTP response to %d.%d.%d.%d - Time: %lu\r\n",
src_ip[0], src_ip[1], src_ip[2], src_ip[3], tx);
}
}
/* ---------- Encapsulated synchronization and printing function implementation ---------- */
uint8_t ntp_first_sync(void)
{
uint32_t unix_time = ntp_get_time();
if (unix_time > 0)
{
RTC_SetCounter(unix_time); /* Synchronize RTC */
printf("RTC synced to UNIX: %lu\r\n", unix_time);
// Print UTC and CST times
DateTime dt = ntp_unix_to_datetime(unix_time);
printf("Current time (UTC): ");
ntp_print_datetime(dt);
dt = ntp_unix_to_datetime(unix_time + 8*3600);
printf("Current time (CST): ");
ntp_print_datetime(dt);
return 1;
}
else
{
printf("Failed to get NTP time\r\n");
return 0;
}
}
uint8_t ntp_do_sync(void)
{
uint32_t unix_time = ntp_get_time();
if (unix_time > 0)
{
RTC_SetCounter(unix_time); /* Synchronize RTC */
printf("RTC re-synced: %lu\r\n", unix_time);
//Print UTC and CST time
DateTime dt = ntp_unix_to_datetime(unix_time);
printf("Current time (UTC): ");
ntp_print_datetime(dt);
dt = ntp_unix_to_datetime(unix_time + 8*3600);
printf("Current time (CST): ");
ntp_print_datetime(dt);
return 1;
}
return 0;
}
void ntp_print_current_time(void)
{
uint32_t rtc_time = RTC_GetCounter();
if (rtc_time > 0)
{
DateTime dt_utc = ntp_unix_to_datetime(rtc_time);
printf("UTC: ");
ntp_print_datetime(dt_utc);
DateTime dt_cst = ntp_unix_to_datetime(rtc_time + 8*3600);
printf("CST: ");
ntp_print_datetime(dt_cst);
}
else {
printf("RTC time not set\r\n");
}
}5.3 rtc.c Analysis
RTC Initialization (RTC_Init())
Clock Source Selection: Enable LSE (32.768kHz low-frequency crystal oscillator) to ensure timing accuracy (1 tick per second).
Prescaler Configuration: RTC_SetPrescaler(32767) divides the 32768Hz crystal oscillator to 1Hz, matching the UNIX timestamp unit (seconds).
Core Interfaces
RTC_SetCounter(): Writes the UNIX timestamp obtained from NTP to the RTC counter, achieving time synchronization.
RTC_GetCounter(): Reads the current RTC counter value (UNIX timestamp) as the reference for NTP server time synchronization.
/**********************************************************************************
* @file w55mh32_rtc.c
******************************************************************************/
#include "w55mh32_rtc.h"
#include "w55mh32_pwr.h"
#include "w55mh32_bkp.h"
#include "bsp_uart.h"
#include <stdint.h>
#define RTC_LSB_MASK ((uint32_t)0x0000FFFF)
#define PRLH_MSB_MASK ((uint32_t)0x000F0000)
/* -------------- Function Implementation -------------- */
void RTC_Init(void)
{
/* 1. Open PWR & /* BKP Clock */
RCC->APB1ENR |= RCC_APB1Periph_PWR | RCC_APB1Periph_BKP;
PWR->CR |= PWR_CR_DBP;
/* 2. Backup domain soft reset, ensure clean */
RCC->BDCR |= RCC_BDCR_BDRST;
RCC->BDCR &= ~RCC_BDCR_BDRST;
/* 3. Start LSE */
RCC->BDCR |= RCC_BDCR_LSEON;
while (!(RCC->BDCR & RCC_BDCR_LSERDY))
;
/* 4. Select LSE as RTC clock and enable RTC */
RCC->BDCR &= ~RCC_BDCR_RTCSEL;
RCC->BDCR |= RCC_BDCR_RTCSEL_LSE | RCC_BDCR_RTCEN;
/* 5. Wait for synchronization & set the prescaler (32763 = 32768-4) */
RTC_WaitForSynchro();
RTC_SetPrescaler(32763);
/* 6. Clear the counter first, then set the correct UNIX time by NTP */
RTC_SetCounter(0);
printf("RTC initialized with LSE (prescaler 32763)\r\n");
}
/* -------------- Other library functions remain unchanged -------------- */
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState)
{
assert_param(IS_RTC_IT(RTC_IT));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
RTC->CRH |= RTC_IT;
else
RTC->CRH &= (uint16_t)~RTC_IT;
}
void RTC_WaitForSetComplete(void)
{
while ((RTC->CRL & RTC_CRL_RTOFF) == 0)
;
}
void RTC_EnterConfigMode(void)
{
RTC->CRL |= RTC_CRL_CNF;
}
void RTC_ExitConfigMode(void)
{
RTC->CRL &= (uint16_t)~RTC_CRL_CNF;
}
uint32_t RTC_GetCounter(void)
{
uint16_t tmp = RTC->CNTL;
return (((uint32_t)RTC->CNTH << 16) | tmp);
}
void RTC_SetCounter(uint32_t CounterValue)
{
RTC_EnterConfigMode(); RTC->CNTH = CounterValue >> 16;
RTC->CNTL = (CounterValue & RTC_LSB_MASK);
RTC_ExitConfigMode();
}
void RTC_SetPrescaler(uint32_t PrescalerValue)
{
assert_param(IS_RTC_PRESCALER(PrescalerValue));
RTC_EnterConfigMode();
RTC->PRLH = (PrescalerValue & PRLH_MSB_MASK) >> 16;
RTC->PRLL = (PrescalerValue & RTC_LSB_MASK);
RTC_ExitConfigMode();
}
void RTC_SetAlarm(uint32_t AlarmValue)
{
RTC_EnterConfigMode();
RTC->ALRH = AlarmValue >> 16;
RTC->ALRL = (AlarmValue & RTC_LSB_MASK); RTC_ExitConfigMode();
}
uint32_t RTC_GetDivider(void)
{
return (((uint32_t)RTC->DIVH & 0x000F) << 16) | RTC->DIVL;
}
void RTC_WaitForLastTask(void)
{
while ((RTC->CRL & RTC_FLAG_RTOFF) == RESET)
;
}
void RTC_WaitForSynchro(void)
{
RTC->CRL &= (uint16_t)~RTC_FLAG_RSF;
while ((RTC->CRL & RTC_FLAG_RSF) == RESET)
;
}
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG)
{ assert_param(IS_RTC_GET_FLAG(RTC_FLAG));
return (RTC->CRL & RTC_FLAG) ? SET : RESET;
}
void RTC_ClearFlag(uint16_t RTC_FLAG)
{
assert_param(IS_RTC_CLEAR_FLAG(RTC_FLAG));
RTC->CRL &= (uint16_t)~RTC_FLAG;
}
ITStatus RTC_GetITStatus(uint16_t RTC_IT)
{
assert_param(IS_RTC_GET_IT(RTC_IT));
return ((RTC->CRH & RTC_IT) && (RTC->CRL & RTC_IT)) ? SET : RESET;
}
void RTC_ClearITPendingBit(uint16_t RTC_IT)
{ assert_param(IS_RTC_IT(RTC_IT));
RTC->CRL &= (uint16_t)~RTC_IT;
}
6. Functional Verification
6.1 Time Acquisition Verification

6.2 Time Synchronization Verification
The command `w32tm /stripchart /computer:192.168.2.21` is used in Windows systems to check the time synchronization status between the local computer and a specified NTP (Network Time Protocol) server (here, 192.168.1.6). We send an NTP request to the W55MH32L-EVB via the command line: `w32tm /stripchart /computer:192.168.1.6`.
7. Summary
This article introduces how the W55MH32L-EVB achieves time acquisition and synchronization via NTP, analyzing its hardware and software configuration, program logic, including time synchronization and calibration, and functional verification methods. Thank you for your patience in reading! If you have any questions during the reading process, or would like to learn more about this product and its applications, please feel free to leave a message via private message or in the comments section. We will reply to your message as soon as possible to provide you with more detailed answers and assistance!
————————————————
Copyright Notice: This article is an original article by CSDN blogger "Playing with Ethernet," and is licensed under CC 4.0 BY-SA. Please include the original source link and this statement when reprinting.
Original Link: https://blog.csdn.net/2301_81684513/article/details/150446258
