Skip to content

概念先知道

  • SNTP(简单网络时间协议):通过互联网向 NTP 服务器请求“当前时间”,毫秒级精度,是物联网设备校时最常用的方式。
  • NTP 服务器:提供标准时间的公共服务器,例如阿里云 ntp.aliyun.compool.ntp.org
  • Unix 时间戳(epoch):自 1970-01-01 00:00:00 UTC 起经过的秒数,通常用整数保存。
  • SNTP_SET_SYSTEM_TIME 钩子:lwIP 的 SNTP 默认不保存收到的校时结果(该宏默认是空操作),必须自己定义它把时间写入变量;本示例存入全局变量 g_sntp_time

例程功能简介

SDK 没有独立 SNTP 例程,但 lwIP 协议栈自带 SNTP 客户端(components/net/lwip/lwip/src/apps/sntp/,默认编入所有无线例程)。本页为自编示例,在 wifi_tcp 骨架上编写:

  • 拿到 IP 后创建 sntp_task:设置轮询模式 → 指定服务器 ntp.aliyun.com → 调用 sntp_init()
  • 每 5 秒检查一次同步状态(sntp_getreachability)并打印格式化时间;
  • 通过 lwipopts_user.h 里的 SNTP_SET_SYSTEM_TIME(sec) 把收到的秒数存入 g_sntp_time
  • 相关参考:AT 固件的 SNTP 模块(components/net/netbus/atmodule/)基于同一套 lwIP 实现,并增加 sntp_settimesynccb 回调,AT+SNTP 指令即实现于此。

操作步骤

1
进入例程目录

SDK 没有独立的 SNTP 例程,本页在 wifi_tcp 骨架上编写(标注自编示例)。先进入目录:

cd examples/wifi/sta/wifi_tcp
2
替换 main.c 并扩展 lwipopts

用下方自编代码覆盖例程的 main.c,然后在同目录的 lwipopts_user.h 末尾追加两行(让 lwIP 把收到的校时秒数存入全局变量,供 SNTP 读取):

3
编译工程

Ai-M61 与 Ai-M62 统一填写 bl616

make CHIP=bl616 BOARD=bl616dk
4
烧录固件

按住 BOOT 键不放、短按 EN/RST 进入下载模式后烧录:

make flash CHIP=bl616 COMX=/dev/ttyUSB0
5
连接 Wi-Fi

串口助手波特率 2000000。看到 bouffalolab /> 后连接路由器,拿到 IP 后日志打印 Got IP, starting SNTP,SNTP 任务随即启动:

wifi_sta_connect Your_SSID 12345678
6
运行验证

几秒到十几秒内,日志会打印格式化时间,如 SNTP time: 2026-08-11 10:30:00,说明校时成功;同步前打印 SNTP waiting for sync... 并每 5 秒重试。

lwipopts_user.h 末尾追加:

c
/* 把 SNTP 时间存入全局变量(在本示例中定义) */
#define SNTP_SET_SYSTEM_TIME(sec)   g_sntp_time = (time_t)(sec)
extern volatile time_t g_sntp_time;

代码执行流程

自编示例从启动到打印时间的完整流程如下:

例程调用的 API 介绍

sntp_setoperatingmode(SNTP_OPMODE_POLL)

设置 SNTP 为轮询模式(主动向服务器请求),必须在 sntp_init() 之前调用。

参数

  • SNTP_OPMODE_POLL:轮询模式(默认)

返回值:无

sntp_setservername(idx, "ntp.aliyun.com")

按域名设置第 idx 个 NTP 服务器(内部自动做 DNS 解析),比填 IP 更灵活。

参数

  • idx:服务器编号,从 0 开始
  • server:服务器域名

返回值:无

sntp_init / sntp_stop()

启动 / 停止 SNTP 客户端。

参数:无

返回值:无

sntp_getreachability(idx)

查询第 idx 个服务器是否已同步成功(非 0 表示已同步)。

参数

  • idx:服务器编号

返回值0 未同步;非 0 已同步

SNTP_SET_SYSTEM_TIME(sec)

lwIP 在同步成功时调用的宏,参数为 Unix 秒数。该宏默认是空操作——必须自行定义(本示例在 lwipopts_user.h 中定义,写入 g_sntp_time),否则读不到时间。

参数

  • sec:Unix 秒数(自 1970-01-01 UTC 起)

返回值:无

完整代码

以下为自编示例 main.c(在 SDK wifi_tcp 骨架上编写,非 SDK 文件;g_sntp_time 声明与 lwipopts 追加内容见操作步骤),默认折叠,点击展开:

📜 点击展开 自编 sntp main.c 完整代码
c
/*
 * 自编示例:通过 lwIP SNTP 客户端获取网络时间
 * 使用方法:
 *   1. 用本文件覆盖 examples/wifi/sta/wifi_tcp/main.c;
 *   2. 在 examples/wifi/sta/wifi_tcp/lwipopts_user.h 末尾追加:
 *        #define SNTP_SET_SYSTEM_TIME(sec)   g_sntp_time = (time_t)(sec)
 *        extern volatile time_t g_sntp_time;
 *   3. make CHIP=bl616 BOARD=bl616dk && make flash CHIP=bl616 COMX=/dev/ttyUSB0
 */

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

#include <time.h>

#include <lwip/tcpip.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>
#include <lwip/apps/sntp.h>

#include "wifi_mgmr_ext.h"
#include "fhost_api.h"
#include "wifi_mgmr.h"

#include "bflb_irq.h"
#include "bflb_uart.h"

#include "rfparam_adapter.h"
#include "async_event.h"
#include "mm.h"
#include "board.h"
#include "shell.h"

#define DBG_TAG "MAIN"
#include "log.h"

/* SNTP 同步成功后写入的时间(Unix 秒) */
volatile time_t g_sntp_time = 0;
static volatile uint32_t wifi_state = 0;
static struct bflb_device_s *uart0;

extern void shell_init_with_task(struct bflb_device_s *shell);
extern void wifi_event_handler(async_input_event_t ev, void *priv);

void wifi_event_handler(async_input_event_t ev, void *priv)
{
    switch (ev->code) {
        case CODE_WIFI_ON_GOT_IP:
            wifi_state = 1;
            LOG_I("Got IP, starting SNTP\r\n");
            break;
        case CODE_WIFI_ON_DISCONNECT:
            wifi_state = 0;
            LOG_I("WiFi disconnected\r\n");
            break;
        default:
            break;
    }
}

static void print_sntp_time(void)
{
    struct tm tm_now;
    time_t t = g_sntp_time;

    localtime_r(&t, &tm_now);
    LOG_I("SNTP time: %04d-%02d-%02d %02d:%02d:%02d\r\n",
          tm_now.tm_year + 1900, tm_now.tm_mon + 1, tm_now.tm_mday,
          tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
}

static void sntp_task(void *param)
{
    /* 等待 Wi-Fi 拿到 IP */
    while (!wifi_state) {
        vTaskDelay(pdMS_TO_TICKS(200));
    }

    /* 配置并启动 SNTP */
    sntp_setoperatingmode(SNTP_OPMODE_POLL);
    sntp_setservername(0, "ntp.aliyun.com");
    sntp_init();
    LOG_I("SNTP started, server: ntp.aliyun.com\r\n");

    while (1) {
        if (sntp_getreachability(0) && g_sntp_time != 0) {
            print_sntp_time();
        } else {
            LOG_I("SNTP waiting for sync...\r\n");
        }
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

static void wifi_start_firmware_task(void *param)
{
    LOG_I("Starting wifi ...\r\n");
    async_register_event_filter(EV_WIFI, wifi_event_handler, NULL);
    wifi_task_create();
    LOG_I("Starting fhost ...\r\n");
    fhost_init();
    vTaskDelete(NULL);
}

int main(void)
{
    board_init();

    uart0 = bflb_device_get_by_name("uart0");
    shell_init_with_task(uart0);

    if (0 != rfparam_init(0, NULL, 0)) {
        LOG_I("PHY RF init failed!\r\n");
        return 0;
    }

    tcpip_init(NULL, NULL);

    xTaskCreate(wifi_start_firmware_task, "wifi init", 1024, NULL, 10, NULL);
    xTaskCreate(sntp_task, "sntp", 1024, NULL, 11, NULL);

    vTaskStartScheduler();

    while (1) {
    }
}

FAQ

一直打印 waiting for sync

确认 Wi-Fi 已拿到 IP(日志显示 Got IP, starting SNTP);公共 NTP 服务器偶尔会丢 UDP 包,重试几次(默认 15 秒超时)通常能成功;也可以换 pool.ntp.org 或公司内网 NTP 服务器。

打印的时间是 1970

说明 g_sntp_time 仍为 0:检查 lwipopts_user.h 里的 SNTP_SET_SYSTEM_TIME 是否真的追加并重新编译;该宏必须在 sntp 头文件处理前定义(lwipopts 先于 lwIP 处理,满足此条件)。

时间比北京时间慢 8 小时

SNTP 返回的是 UTC,北京是 UTC+8。示例直接打印 UTC;打印前加 8 小时(t += 8 * 3600),或使用时区库转换。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23