Skip to content

Concepts First

  • SNTP (Simple Network Time Protocol): requests "current time" from an NTP server over the Internet; millisecond precision, the most common way for IoT devices to calibrate clocks.
  • NTP server: public servers that provide standard time, e.g. Aliyun's ntp.aliyun.com, pool.ntp.org.
  • Unix timestamp (epoch): seconds elapsed since 1970-01-01 00:00:00 UTC, usually stored as an integer.
  • SNTP_SET_SYSTEM_TIME hook: lwIP's SNTP does not store the received time by default (the macro is a no-op); you must define it to save the time into your own variable. This example stores it in the global g_sntp_time.

Example Overview

The SDK has no standalone SNTP example, but the lwIP stack ships an SNTP client (components/net/lwip/lwip/src/apps/sntp/, compiled into every wireless example by default). This self-written example builds on the wifi_tcp skeleton:

  • after getting an IP, creates sntp_task: sets poll mode → sets server ntp.aliyun.com → calls sntp_init();
  • checks sync state every 5 seconds (sntp_getreachability) and prints the formatted time;
  • uses SNTP_SET_SYSTEM_TIME(sec) in lwipopts_user.h to store the received seconds into g_sntp_time.
  • Related reference: the AT firmware's SNTP module (components/net/netbus/atmodule/) is built on the same lwIP implementation and adds an sntp_settimesynccb callback; the AT+SNTP command is implemented there.

Operation Steps

1
Enter the Example Directory

The SDK has no standalone SNTP example; this page is written on the wifi_tcp skeleton (marked self-written). First enter the directory:

cd examples/wifi/sta/wifi_tcp
2
Replace main.c and Extend lwipopts

Overwrite the example’s main.c with the self-written one below; then append two lines to lwipopts_user.h in the same directory (so lwIP stores the received time into a global variable that SNTP can read):

3
Build the Project

Both Ai-M61 and Ai-M62 use bl616:

make CHIP=bl616 BOARD=bl616dk
4
Flash the Firmware

Hold BOOT, briefly press EN/RST to enter download mode, then flash:

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

Serial tool at 2000000 baud. Wait for bouffalolab />, connect to the router; after getting an IP the log shows Got IP, starting SNTP and the SNTP task starts:

wifi_sta_connect Your_SSID 12345678
6
Run and Verify

Within a few to a dozen seconds, the log prints a formatted time like SNTP time: 2026-08-11 10:30:00, meaning time sync succeeded; before sync it prints SNTP waiting for sync... and retries every 5 seconds.

Append the following to lwipopts_user.h:

c
/* Store SNTP time into a global variable (defined in this example) */
#define SNTP_SET_SYSTEM_TIME(sec)   g_sntp_time = (time_t)(sec)
extern volatile time_t g_sntp_time;

Code Execution Flow

The complete flow of the self-written example from boot to printing time:

APIs Used by the Example

sntp_setoperatingmode(SNTP_OPMODE_POLL)

Sets SNTP to poll mode (actively request from the server); must be called before sntp_init().

Parameters:

  • SNTP_OPMODE_POLL: poll mode (default)

Return: none

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

Sets the idx-th NTP server by domain name (DNS resolved internally), more flexible than an IP.

Parameters:

  • idx: server index, starting from 0
  • server: server domain

Return: none

sntp_init / sntp_stop()

Starts / stops the SNTP client.

Parameters: none

Return: none

sntp_getreachability(idx)

Queries whether the idx-th server has synced successfully (nonzero = synced).

Parameters:

  • idx: server index

Return: 0 not synced; nonzero synced

SNTP_SET_SYSTEM_TIME(sec)

Macro invoked by lwIP when sync succeeds; the argument is Unix seconds. It is a no-op by default — you must define it yourself (this example defines it in lwipopts_user.h to write into g_sntp_time), otherwise the time cannot be read.

Parameters:

  • sec: Unix seconds (since 1970-01-01 UTC)

Return: none

Complete Code

The self-written main.c below (written on the SDK wifi_tcp skeleton; not an SDK file; the g_sntp_time declaration and the lwipopts addition are shown in Operation Steps). Collapsed by default; click to expand:

📜 Click to expand self-written sntp main.c full code
c
/*
 * Self-written example: network time via the lwIP SNTP client
 * Usage:
 *   1. Overwrite examples/wifi/sta/wifi_tcp/main.c with this file;
 *   2. Append to 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"

/* Time written after SNTP sync (Unix seconds) */
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)
{
    /* Wait for Wi-Fi to get an IP */
    while (!wifi_state) {
        vTaskDelay(pdMS_TO_TICKS(200));
    }

    /* Configure and start 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

Keeps printing waiting for sync

Confirm Wi-Fi got an IP (log shows Got IP, starting SNTP); public NTP servers occasionally drop UDP packets — a few retries (15s default timeout) usually succeed; try pool.ntp.org or a corporate NTP server.

Printed time is 1970

That means g_sntp_time is still 0: check that SNTP_SET_SYSTEM_TIME in lwipopts_user.h was actually appended and rebuild; the macro must be defined before the sntp header is processed (lwipopts satisfies this because lwIP processes it first).

Time is 8 hours behind Beijing time

SNTP returns UTC; Beijing is UTC+8. The example prints UTC directly; add 8 hours (t += 8 * 3600) before printing, or convert with a timezone library.

Have questions?

For any other questions, visit the unified Q&A and discussion board: Ai-Thinker Discussions

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