概述
物联网设备常需准确的本地时间(数据上报时间戳、定时任务)。SNTP(简单网络时间协议)通过访问 NTP 服务器自动同步时间,误差可达毫秒级。本教程演示:连接 Wi-Fi 后从 ntp.aliyun.com 获取时间,并换算为**北京时间(UTC+8)**打印。
用大白话讲:SNTP 就是开发板联网对表——像手表时间不准了,打电话问「时间服务中心」现在是几点,再按它说的把表校准。开发板每次开机联网后都会自动向 NTP 服务器(本教程用 ntp.aliyun.com)要一次标准时间,拿到后按你的时区换算成北京时间打印出来。这样设备上报数据的时间戳(记录「某年某月某日几点几分」的数字)才是可信的。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/protocols/sntp_demo编写,代码可在本地 SDK 中直接找到。
打开终端,进入官方 sntp_demo 示例工程目录:
cd ~/Ai-Thinker-WB2/applications/protocols/sntp_demo
说明:
cd是「进入目录」的命令,进入官方示例工程;后面所有make命令都要在这个目录下执行。
打开 sntp_demo/main.c,修改开头的 SSID/密码(同 连接 Wi-Fi),并按需调整时区:
#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
| 宏 | 默认值 | 说明 |
|---|---|---|
sntp_server |
ntp.aliyun.com |
NTP 服务器地址(可选 ntp.ntsc.ac.cn 等);连不上它,时间就同步不了 |
UTC |
8 |
时区偏移(北京时间 = UTC+8;东京 = 9,伦敦 = 0);不加它,打印的是世界标准时间,会差 8 小时 |
打开 sntp_demo/main.c,本步完整代码已移至文末,见:
📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(
applications/protocols/sntp_demo/sntp_demo/main.c)完全一致。
代码要点:
| 代码 | 作用 |
|---|---|
sntp_setoperatingmode(SNTP_OPMODE_POLL) |
设置 SNTP 轮询模式(定时主动问服务器,不用自己管) |
sntp_setservername(0, "ntp.aliyun.com") |
指定 NTP 服务器(告诉它去问谁要时间) |
sntp_init() |
启动 SNTP 同步(不调用它就不干活) |
tcpip_callback(_startup_sntp, NULL) |
在 lwIP 上下文中启动 SNTP(线程安全);在事件回调里直接调会死机 |
sntp_get_time(&seconds, &frags) |
获取同步时间:自 1970 年起的秒数 + 亚秒;没同步成功时拿到的是旧值 |
utils_time_date_from_epoch(seconds+UTC*60*60, &date) |
秒数 + 时区偏移后转为日期结构体(秒数看不懂,转成年月日时分秒) |
💡
sntp_get_time返回的seconds是 Unix 时间戳(1970-01-01 00:00:00 UTC 起);加UTC*60*60(8 小时)即北京时间。SNTP 需在 GOT_IP 之后启动。
在工程目录执行编译:
make -j8
说明:
make是「编译」命令,把代码变成开发板能运行的固件(烧进开发板的程序);-j8表示用 8 个 CPU 核并行编译,更快。
编译成功后生成固件 build_out/sntp_demo.bin。
开发板保持 USB 连接,确认串口设备号后执行烧录:
make flash p=/dev/ttyUSB0 b=921600
说明:
make flash是「烧录」命令,把编译好的固件下载进开发板;p=后面是串口设备号(Linux 下常为/dev/ttyUSB0,Windows 下是COM3之类,以你电脑实际为准),b=921600是烧录波特率(串口传数据的速度),保持默认即可。
⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。
烧录完成后开发板自动重启运行,打开串口助手(波特率 921600,串口传数据的「语速」,两边必须一致),先看到 Wi-Fi 连接事件,然后打印:
[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: 且日期不是 1970 年即成功——时间与电脑/手机当前时间一致(时区为北京时间)。
💡 踩坑:如果一直不打印日期、或时间停在 1970 年,说明 SNTP 同步失败——先确认开发板已联网(先看到
GOT IP)、路由器能访问公网、NTP 服务器(ntp.aliyun.com)可达;可尝试更换服务器(如ntp.ntsc.ac.cn),见文末 FAQ。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
本文 API 汇总
sntp_setservername(idx, server)
设置第 idx 个 NTP 服务器地址。
参数:
idx:服务器序号(从0开始,官方示例用0)server:服务器域名/地址,如"ntp.aliyun.com"
返回值:无
sntp_get_time(&sec, &frag)
获取同步时间(Unix 秒 + 亚秒)。
参数:
sec:输出参数,Unix 秒(uint32_t)frag:输出参数,亚秒部分
返回值:成功返回 0;失败返回负值错误码
utils_time_date_from_epoch(epoch, &date)
Unix 秒转日期结构体(年月日时分秒/星期/年日)。
参数:
epoch:Unix 秒date:输出参数,utils_time_date_t结构体
返回值:成功返回 0;失败返回负值错误码
tcpip_callback(fn, arg)
在 lwIP 上下文中执行函数(线程安全,跨任务调用 lwIP API 推荐)。
参数:
fn:要执行的回调函数arg:回调参数
返回值:成功返回 0;失败返回负值错误码
📌 日期结构体
utils_time_date_t字段:ntp_year(年)、ntp_month(月)、ntp_date(日)、ntp_hour(时)、ntp_minute(分)、ntp_second(秒)、ntp_week_day(星期 1~7)、day_of_year(年内第几天)。
完整代码
以下为 sntp_demo/main.c 完整源码,与官方示例(applications/protocols/sntp_demo/sntp_demo/main.c)完全一致:
📜 点击展开 sntp_demo/main.c 完整代码
#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);
}常见问题与踩坑提示
⚠️ 时间一直停在 1970 年
原因:SNTP 未同步成功(服务器不可达、DNS 解析失败、路由器无法访问公网)
解决:确认能正常浏览网页;更换 NTP 服务器(ntp.ntsc.ac.cn、time.windows.com);检查是否在 GOT_IP 之后才启动 SNTP
⚠️ 时间差 8 小时
原因:UTC 时区宏未设置为 8(默认打印的是 UTC 时间)
解决:#define UTC 8;其他国家/地区按当地时区修改
⚠️ 在事件回调中直接调用 sntp 相关 API 死机
原因:Wi-Fi 事件回调不在 lwIP 上下文,直接调用 lwIP API 不安全
解决:参照官方用 tcpip_callback(_startup_sntp, NULL) 包装启动逻辑
⚠️ 获取的时间有偏差(几十秒)
原因:网络延迟、服务器负载高
解决:SNTP 轮询会持续校正,等待数分钟后自然收敛;要求高精度请用 PTP 或更近的 NTP 服务器
⚠️ 一直打印 Connecting,始终没有 GOT IP(连不上路由器)
原因:SSID/密码填错、路由器是 5GHz、或信号太弱
解决:核对 sntp_demo/main.c 里 ROUTER_SSID/ROUTER_PWD 与路由器完全一致;确认路由器是 2.4GHz(开发板不支持 5GHz);把开发板靠近路由器再试
⚠️ 串口找不到设备 / 打不开
原因:USB 转串口驱动未装、权限不足,或数据线只能充电不能传数据
解决:Linux 用 lsusb/dmesg 查看设备,权限不足可 sudo chmod 666 /dev/ttyUSB0;Windows 装驱动后到设备管理器查 COM 口;换一根能传数据的线
⚠️ 烧录一直等待 / 失败
原因:未进入下载模式、波特率不对、或串口号填错
解决:烧录时按提示长按 EN 键进入下载模式;确认 p=/dev/ttyUSB0 换成你实际的串口;换 USB 口或数据线重试
运行自检
串口每秒打印的北京时间与真实时间一致,即 SNTP 网络授时验证通过。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

