Overview
IoT devices often need accurate local time (timestamps for data reporting, scheduled tasks). SNTP (Simple Network Time Protocol) syncs time automatically by visiting an NTP server, with errors down to the millisecond level. This tutorial demonstrates: after connecting to Wi-Fi, fetching the time from ntp.aliyun.com and converting it to Beijing time (UTC+8) to print.
In plain words: SNTP is the board setting its clock online — like when your watch is inaccurate and you call the "time service center" to ask what time it is, then calibrate your watch accordingly. Every time the board powers on and gets online it automatically asks the NTP server (this tutorial uses ntp.aliyun.com) for the standard time once, and after getting it converts to Beijing time per your timezone to print. That way the timestamps (the numbers recording "what time on which year-month-day") in device data reports are trustworthy.
This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version
release_bl_iot_sdk_1.6.40) exampleapplications/protocols/sntp_demo; the code can be found directly in your local SDK.
Open a terminal and enter the official sntp_demo example project directory:
cd ~/Ai-Thinker-WB2/applications/protocols/sntp_demo
Note:
cdis the “change directory” command, entering the official example project; all subsequentmakecommands must run in this directory.
Open sntp_demo/main.c and change the SSID/password at the top (same as Connect Wi-Fi), and adjust the timezone as needed:
#define ROUTER_SSID "ssid"
#define ROUTER_PWD "password"
#define sntp_server "ntp.aliyun.com"
#define UTC 8 //your Timezone, for example,beijing Timezone is GMT+8
| Macro | Default | Description |
|---|---|---|
sntp_server |
ntp.aliyun.com |
NTP server address (alternatives: ntp.ntsc.ac.cn etc.); if it can’t be reached, time won’t sync |
UTC |
8 |
Timezone offset (Beijing time = UTC+8; Tokyo = 9, London = 0); without it the printed time is UTC, 8 hours off |
Open sntp_demo/main.c. The full code for this step has been moved to the end of this page:
📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (
applications/protocols/sntp_demo/sntp_demo/main.c).
Code highlights:
| Code | Purpose |
|---|---|
sntp_setoperatingmode(SNTP_OPMODE_POLL) |
Sets SNTP poll mode (actively asks the server at intervals, no manual management) |
sntp_setservername(0, "ntp.aliyun.com") |
Specifies the NTP server (tells it who to ask for the time) |
sntp_init() |
Starts SNTP sync (without calling it nothing works) |
tcpip_callback(_startup_sntp, NULL) |
Starts SNTP in the lwIP context (thread-safe); calling it directly in an event callback crashes the board |
sntp_get_time(&seconds, &frags) |
Gets the synced time: seconds since 1970 + subseconds; before a successful sync the value is stale |
utils_time_date_from_epoch(seconds+UTC*60*60, &date) |
Adds the timezone offset to the seconds and converts to a date struct (seconds are unreadable; this turns them into year-month-day hour:minute:second) |
💡 The
secondsreturned bysntp_get_timeis a Unix timestamp (since 1970-01-01 00:00:00 UTC); addingUTC*60*60(8 hours) gives Beijing time. SNTP must be started after GOT_IP.
Build in the project directory:
make -j8
Note:
makeis the “build” command, turning code into firmware (the program flashed into the board) the board can run;-j8builds with 8 parallel cores, faster.
On success a firmware build_out/sntp_demo.bin is generated.
Keep the board connected via USB, confirm the serial device, and flash:
make flash p=/dev/ttyUSB0 b=921600
Note:
make flashis the “flash” command, writing the compiled firmware into the board. Afterp=comes the serial device (often/dev/ttyUSB0on Linux,COM3-like on Windows — use your computer’s actual one),b=921600is the flash baud rate (serial transfer speed), keep the default.
⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode; wait for the progress bar to complete — that means the flash succeeded.
After flashing, the board automatically restarts and runs. Open a serial assistant (baud rate 921600, the “speech speed” of serial data — both ends must match). You first see the Wi-Fi connection events, then:
[APP] [EVT] GOT IP 5
--------------------------------------- Start NTP now
--------------------------------------- Start NTP Done
[NTP] time is 4123456789:0
Date & time is: 2026-08-11 14:53:30 (Day 2 of week, Day 223 of Year)
Date & time is: appearing with a date that’s not 1970 means success — the time matches your computer/phone’s current time (in the Beijing timezone).
💡 Pitfall: if the date never prints, or the time stays at 1970, SNTP sync failed — first confirm the board is online (you saw
GOT IP), the router can access the public internet, and the NTP server (ntp.aliyun.com) is reachable; try switching servers (e.g.ntp.ntsc.ac.cn), see the FAQ at the end.
API Summary for This Tutorial
sntp_setoperatingmode(mode)
Sets the SNTP working mode.
Parameters:
mode: working mode,SNTP_OPMODE_POLL(poll mode)
Return: none
sntp_setservername(idx, server)
Sets the address of the idx-th NTP server.
Parameters:
idx: server index (starting from0, the official example uses0)server: server domain/address, e.g."ntp.aliyun.com"
Return: none
sntp_get_time(&sec, &frag)
Gets the synced time (Unix seconds + subseconds).
Parameters:
sec: output parameter, Unix seconds (uint32_t)frag: output parameter, subsecond part
Return: 0 on success; negative error code on failure
utils_time_date_from_epoch(epoch, &date)
Converts Unix seconds to a date struct (year-month-day hour:minute:second/week/day of year).
Parameters:
epoch: Unix secondsdate: output parameter,utils_time_date_tstruct
Return: 0 on success; negative error code on failure
tcpip_callback(fn, arg)
Executes a function in the lwIP context (thread-safe; recommended for calling lwIP APIs across tasks).
Parameters:
fn: the callback function to executearg: callback argument
Return: 0 on success; negative error code on failure
wifi_mgmr_sta_connect(if, ssid, pwd, ...)
Connects to a router (same as Connect Wi-Fi).
Parameters:
- See the "API Summary for This Tutorial" of Connect Wi-Fi for the parameter descriptions
Return: 0 on success; negative error code on failure
📌 Date struct
utils_time_date_tfields:ntp_year(year),ntp_month(month),ntp_date(day),ntp_hour(hour),ntp_minute(minute),ntp_second(second),ntp_week_day(weekday 1~7),day_of_year(day of year).
Full Code
Below is the complete sntp_demo/main.c source, identical to the official example (applications/protocols/sntp_demo/sntp_demo/main.c):
📜 Click to expand the full sntp_demo/main.c code
#include <FreeRTOS.h>
#include <task.h>
#include <stdio.h>
#include <string.h>
#include "blog.h"
#include <aos/yloop.h>
#include <aos/kernel.h>
#include <lwip/tcpip.h>
#include <wifi_mgmr_ext.h>
#include <hal_wifi.h>
#include <lwip/tcpip.h>
#include <sntp.h>
#include <utils_time.h>
#define ROUTER_SSID "ssid"
#define ROUTER_PWD "password"
#define sntp_server "ntp.aliyun.com"
#define UTC 8 //your Timezone, for example,beijing Timezone is GMT+8
static wifi_conf_t conf =
{
.country_code = "CN",
};
static void wifi_sta_connect(char* ssid, char* password)
{
wifi_interface_t wifi_interface;
wifi_interface = wifi_mgmr_sta_enable();
wifi_mgmr_sta_connect(wifi_interface, ssid, password, NULL, NULL, 0, 0);
}
void _startup_sntp(void *arg)
{
blog_info("--------------------------------------- Start NTP now\r\n");
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, sntp_server);
sntp_init();
blog_info("--------------------------------------- Start NTP Done\r\n");
}
void sntp_task()
{
tcpip_callback(_startup_sntp, NULL);
vTaskDelay(5000 / portTICK_PERIOD_MS);
while(1)
{
uint32_t seconds = 0, frags = 0;
utils_time_date_t date;
sntp_get_time(&seconds, &frags);
blog_info("[NTP] time is %lu:%lu\r\n", seconds, frags);
utils_time_date_from_epoch(seconds+UTC*60*60, &date);
blog_info("Date & time is: %u-%02u-%02u %02u:%02u:%02u (Day %u of week, Day %u of Year)\r\n",
date.ntp_year,
date.ntp_month,
date.ntp_date,
date.ntp_hour,
date.ntp_minute,
date.ntp_second,
date.ntp_week_day,
date.day_of_year
);
vTaskDelay(1000 / portTICK_RATE_MS);
}
}
static void event_cb_wifi_event(input_event_t* event, void* private_data)
{
static char* ssid;
static char* password;
switch (event->code)
{
case CODE_WIFI_ON_INIT_DONE:
{
blog_info("[APP] [EVT] INIT DONE %lld", aos_now_ms());
wifi_mgmr_start_background(&conf);
}
break;
case CODE_WIFI_ON_MGMR_DONE:
{
blog_info("[APP] [EVT] MGMR DONE %lld", aos_now_ms());
//_connect_wifi();
wifi_sta_connect(ROUTER_SSID, ROUTER_PWD);
}
break;
case CODE_WIFI_ON_SCAN_DONE:
{
blog_info("[APP] [EVT] SCAN Done %lld", aos_now_ms());
// wifi_mgmr_cli_scanlist();
}
break;
case CODE_WIFI_ON_DISCONNECT:
{
blog_info("[APP] [EVT] disconnect %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTING:
{
blog_info("[APP] [EVT] Connecting %lld", aos_now_ms());
}
break;
case CODE_WIFI_CMD_RECONNECT:
{
blog_info("[APP] [EVT] Reconnect %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTED:
{
blog_info("[APP] [EVT] connected %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_PRE_GOT_IP:
{
blog_info("[APP] [EVT] connected %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_GOT_IP:
{
blog_info("[APP] [EVT] GOT IP %lld", aos_now_ms());
blog_info("[SYS] Memory left is %d Bytes", xPortGetFreeHeapSize());
xTaskCreate(sntp_task, (char*)"sntp_task", 1024*4, NULL, 15, NULL);
}
break;
case CODE_WIFI_ON_PROV_SSID:
{
blog_info("[APP] [EVT] [PROV] [SSID] %lld: %s",
aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (ssid)
{
vPortFree(ssid);
ssid = NULL;
}
ssid = (char*)event->value;
}
break;
case CODE_WIFI_ON_PROV_BSSID:
{
blog_info("[APP] [EVT] [PROV] [BSSID] %lld: %s",
aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (event->value)
{
vPortFree((void*)event->value);
}
}
break;
case CODE_WIFI_ON_PROV_PASSWD:
{
blog_info("[APP] [EVT] [PROV] [PASSWD] %lld: %s", aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (password)
{
vPortFree(password);
password = NULL;
}
password = (char*)event->value;
}
break;
case CODE_WIFI_ON_PROV_CONNECT:
{
blog_info("[APP] [EVT] [PROV] [CONNECT] %lld", aos_now_ms());
blog_info("connecting to %s:%s...", ssid, password);
wifi_sta_connect(ssid, password);
}
break;
case CODE_WIFI_ON_PROV_DISCONNECT:
{
blog_info("[APP] [EVT] [PROV] [DISCONNECT] %lld", aos_now_ms());
}
break;
default:
{
blog_info("[APP] [EVT] Unknown code %u, %lld", event->code, aos_now_ms());
/*nothing*/
}
}
}
static void proc_main_entry(void* pvParameters)
{
aos_register_event_filter(EV_WIFI, event_cb_wifi_event, NULL);
hal_wifi_start_firmware_task();
aos_post_event(EV_WIFI, CODE_WIFI_ON_INIT_DONE, 0);
vTaskDelete(NULL);
}
void main()
{
puts("[OS] Starting TCP/IP Stack...");
tcpip_init(NULL, NULL);
puts("[OS] proc_main_entry task...");
xTaskCreate(proc_main_entry, (char*)"main_entry", 1024, NULL, 15, NULL);
}FAQ & Troubleshooting
⚠️ Time stays at 1970
Cause: SNTP didn't sync successfully (server unreachable, DNS resolution failed, router can't access the public internet)
Fix: confirm you can browse the web normally; switch NTP servers (ntp.ntsc.ac.cn, time.windows.com); check that SNTP is started only after GOT_IP
⚠️ Time is 8 hours off
Cause: the UTC timezone macro isn't set to 8 (UTC time is printed by default)
Fix: #define UTC 8; change it per the local timezone of other countries/regions
⚠️ Crashes calling sntp APIs directly in an event callback
Cause: the Wi-Fi event callback isn't in the lwIP context; calling lwIP APIs directly isn't safe
Fix: follow the official approach and wrap the startup logic with tcpip_callback(_startup_sntp, NULL)
⚠️ The fetched time is off (by tens of seconds)
Cause: network latency, heavy server load
Fix: SNTP polling keeps correcting; wait a few minutes for it to converge naturally; for high precision use PTP or a closer NTP server
⚠️ Keeps printing Connecting, never GOT IP (can't connect to the router)
Cause: wrong SSID/password, the router is on 5GHz, or the signal is too weak
Fix: double-check ROUTER_SSID/ROUTER_PWD in sntp_demo/main.c match the router exactly; confirm the router is 2.4GHz (the board doesn't support 5GHz); try moving the board closer to the router
⚠️ Serial device not found / can't open
Cause: USB-to-serial driver not installed, insufficient permission, or the cable only charges and can't transfer data
Fix: on Linux check the device with lsusb/dmesg; if permission denied run sudo chmod 666 /dev/ttyUSB0; on Windows install the driver and check the COM port in Device Manager; try a data-capable cable
⚠️ Flashing keeps waiting / fails
Cause: download mode wasn't entered, wrong baud rate, or a wrong serial number
Fix: press and hold EN during flashing to enter download mode as prompted; change p=/dev/ttyUSB0 to your actual serial port; try another USB port or cable
Self-Check
The Beijing time printed every second on the serial matches the real time — SNTP network time sync is verified.

