Skip to content

wifi

官方文档

基础知识

在多个主机之间进行交流的时候, 主机之间直接连接会导致网络十分复杂, 在实际的使用时候会使用一个转发器进行连接(Access Point AP可接入点), 其他的主机为station(STA)站

基础配置AP

c
#include <stdio.h>
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "string.h"
#define EXAMPLE_ESP_WIFI_SSID "ESP32-C3"

void app_main(void)
{
    nvs_flash_init(); //esp32的nvs分区 #include "nvs_flash.h"

    esp_event_loop_create_default(); //建立一个事件循环 #include "esp_event.h"

    //配置AP esp_netif(可以理解为软件模拟网卡 为了方便使用TCPIP协议族) Wifi外设 
    //初始化网卡的底层配置
    ESP_ERROR_CHECK(esp_netif_init());

    //默认的方式建立一个AP类型的网卡
    esp_netif_t * pnetif = esp_netif_create_default_wifi_ap();


    /* wifi */
    //初始化Wifi底层配置
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);

    //设置Wifi的模式
    esp_wifi_set_mode(WIFI_MODE_AP);

    //配置AP模式特有的属性
    wifi_config_t cfg2 = {
        .ap = {
            .ssid = EXAMPLE_ESP_WIFI_SSID, 
            .ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
            .channel = 1,
            .password = "1234567890",
            .max_connection = 4,
            .authmode = WIFI_AUTH_WPA2_PSK
        }
    };
    esp_wifi_set_config(WIFI_IF_AP, &cfg2);

    //启动Wifi
    esp_wifi_start();

}

image-20240604102023111

c
// 建立一个AP类型的网卡
esp_netif_t* esp_netif_create_default_wifi_ap(void)
{
    esp_netif_config_t cfg = ESP_NETIF_DEFAULT_WIFI_AP(); //一个默认的配置
    esp_netif_t *netif = esp_netif_new(&cfg); //建立一个网络接口
    assert(netif);
    ESP_ERROR_CHECK(esp_netif_attach_wifi_ap(netif)); //举行关联
    ESP_ERROR_CHECK(esp_wifi_set_default_wifi_ap_handlers()); //注册事件
    return netif;
}
c
/** @brief Soft-AP configuration settings for the device */
typedef struct {
    uint8_t ssid[32];           /**< SSID of soft-AP. If ssid_len field is 0, this must be a Null terminated string. Otherwise, length is set according to ssid_len. wifi的名称*/
    uint8_t password[64];       /**< Password of soft-AP. 密码*/
    uint8_t ssid_len;           /**< Optional length of SSID field. 长度*/
    uint8_t channel;            /**< Channel of soft-AP 信道在实际使用的Wifi的频段有13个, 其中1, 6, 13完全没有重叠,一般使用1*/
    wifi_auth_mode_t authmode;  /**< Auth mode of soft-AP. Do not support AUTH_WEP, AUTH_WAPI_PSK and AUTH_OWE in soft-AP mode. When the auth mode is set to WPA2_PSK, WPA2_WPA3_PSK or WPA3_PSK, the pairwise cipher will be overwritten with WIFI_CIPHER_TYPE_CCMP. 认证模式 设置为WIFI_AUTH_OPEN开放式WIFI, WIFI_AUTH_WPA_WPA2_PSK一般使用这一个 */
    uint8_t ssid_hidden;        /**< Broadcast SSID or not, default 0, broadcast the SSID 是否广播SSID 默认为0(进行广播)*/
    uint8_t max_connection;     /**< Max number of stations allowed to connect in 可以连接的个数*/
    uint16_t beacon_interval;   /**< Beacon interval which should be multiples of 100. Unit: TU(time unit, 1 TU = 1024 us). Range: 100 ~ 60000. Default value: 100 广播频率, 0默认值*/
    wifi_cipher_type_t pairwise_cipher;   /**< Pairwise cipher of SoftAP, group cipher will be derived using this. Cipher values are valid starting from WIFI_CIPHER_TYPE_TKIP, enum values before that will be considered as invalid and default cipher suites(TKIP+CCMP) will be used. Valid cipher suites in softAP mode are WIFI_CIPHER_TYPE_TKIP, WIFI_CIPHER_TYPE_CCMP and WIFI_CIPHER_TYPE_TKIP_CCMP. 加密方式0即可*/
    bool ftm_responder;         /**< Enable FTM Responder mode wifi测距, 不需要false*/
    wifi_pmf_config_t pmf_cfg;  /**< Configuration for Protected Management Frame */
    wifi_sae_pwe_method_t sae_pwe_h2e;  /**< Configuration for SAE PWE derivation method */
} wifi_ap_config_t;

事件组

c
void Wifi_callback(void* event_handler_arg,esp_event_base_t event_base,
                        int32_t event_id,void* event_data){
    if(event_base == IP_EVENT){
        if(event_id == IP_EVENT_AP_STAIPASSIGNED){
            ip_event_ap_staipassigned_t *info = (ip_event_ap_staipassigned_t *)event_data;
            //打印一下实际的ip
            printf("\nSTA IP: "IPSTR"\n", IP2STR(&info->ip));
            printf("STA Mac is "MACSTR"\n", MAC2STR(info->mac)); //#include "esp_mac.h"
        }
    }
}
//在发生事件IP_EVENT基底的IP_EVENT_AP_STAIPASSIGNED的时候, 调用Wifi_callback, 传入参数NULL
esp_event_handler_instance_register(IP_EVENT, 
                                    IP_EVENT_AP_STAIPASSIGNED, 
                                    Wifi_callback, 
                                    NULL, NULL);
c
/** Event structure for IP_EVENT_AP_STAIPASSIGNED event */
typedef struct {
    esp_netif_t *esp_netif; /*!< Pointer to the associated netif handle */
    esp_ip4_addr_t ip; /*!< IP address which was assigned to the station */
    uint8_t mac[6];    /*!< MAC address of the connected client */
} ip_event_ap_staipassigned_t;

处理连接事件

c
if(event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED){
    wifi_event_ap_stadisconnected_t *info = (wifi_event_ap_stadisconnected_t *)event_data;
    printf("\n disconnected STA Mac is "MACSTR"\n", MAC2STR(info->mac));
}

esp_event_handler_instance_register(WIFI_EVENT, 
                                    WIFI_EVENT_AP_STADISCONNECTED, 
                                    Wifi_callback, 
                                    NULL, NULL);

处理断开连接

基础配置STA

c
void app_main(void)
{
    nvs_flash_init();
    esp_event_loop_create_default();

    //初始化网络接口
    esp_netif_init();

    //建立sta接口
    esp_netif_create_default_wifi_sta();

    //初始化Wifi底层配置
    wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&wifi_cfg);

    //设置wifi
    esp_wifi_set_mode(WIFI_MODE_STA);

    //配置STA的相关参数, 这两个是必须设置的
    wifi_config_t sta_cfg = {
        .sta = {
            .ssid = "jiao",
            .password = "1234567890"
        }
    };
    esp_wifi_set_config(WIFI_IF_STA, &sta_cfg);

    esp_wifi_start();
    esp_wifi_connect();
}

image-20240604224927559

可使用的事件

c
uint8_t connect = 0;

void wifi_cb(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data){
    if(event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START){
        //wifi启动成功
        esp_wifi_connect();
    }
    if(event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED){
        //wifi连接失败
        connect ++;
        if(connect <= 5){
            esp_wifi_connect();
        }else{
            ESP_LOGE("main", "connected failed");
        }
    }
    if(event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP){
        ESP_LOGI("main", "connected successed");
        ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
        printf("get sta ip "IPSTR"\n", IP2STR(&info->ip_info.ip));
    }


}

esp_event_loop_create_default();
esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_START, wifi_cb, NULL);
//连接失败的回调
esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, wifi_cb, NULL);
//连接成功的事件
esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_cb, NULL);
//初始化网络接口
esp_netif_init();

image-20240604230554741

这时候出现时间比较长的现象, 是省电模式的原因

省电模式

这一个模式是在STA发送信息给AP, AP转发信息给STA的时候

AP设备每一段时间会进行一次广播, 发送一个信号帧, 这一个信号帧可以用于告诉其他的设备自己的存在, 还有一个TIM告诉已经接入的STA设备, 他这里有没有要发送给你的数据(这是一个单播, 是一对一的)

还有一个DTIM, 这一个是特殊的TIM, 不是每一个信号帧里面都有, 他有TIM的功能还有一个组播

含有TIM的信标帧是可以不及时接收的(可以之后重复发送), 但是DTIM是必须接受的, 是不会重复发送的

省电模式的实现实际是让esp32平时的时候是一个省电模式, 只有在有AP发送DTIM的时候唤醒

c
/**
  * @brief     Set current WiFi power save type
  *
  * @attention Default power save type is WIFI_PS_MIN_MODEM.
  *
  * @param     type  power save type
  *
  * @return    ESP_OK: succeed
  */
esp_err_t esp_wifi_set_ps(wifi_ps_type_t type);

typedef enum {
    WIFI_PS_NONE,        /**< No power save */
    WIFI_PS_MIN_MODEM,   /**< Minimum modem power saving. In this mode, station wakes up to receive beacon every DTIM period 这一个模式是默认的*/
    WIFI_PS_MAX_MODEM,   /**< Maximum modem power saving. In this mode, interval to receive beacons is determined by the listen_interval parameter in wifi_sta_config_t */
} wifi_ps_type_t;

使用最大省电模式的时候, 需要设置wifi_config_t 里面的参数 .listen_interval 这一个参数, 会在收到这么多信标帧以后才进行一次接收

image-20240604234224741

image-20240604234941720

c
esp_wifi_set_ps(WIFI_PS_MAX_MODEM);
esp_pm_config_t pm_config = {
 .max_freq_mhz = 160,
 .min_freq_mhz = 40,
 .light_sleep_enable = true
};
ESP_ERROR_CHECK(esp_pm_configure(&pm_config));

静态ip

  1. 停止这一个网卡的DHCP esp_netif_dhcpc_stop()在创建sta网卡以后就可以立即使用

DHCP(Dynamic Host Configuration Protocol)是一种网络协议,用于在局域网中动态分配IP地址

  1. 设置ip
c
esp_err_t esp_netif_set_ip_info(esp_netif_t *esp_netif, const esp_netif_ip_info_t *ip_info);
c
/**
 * @brief IPv4 address
 *
 */
struct esp_ip4_addr {
    uint32_t addr;  /*!< IPv4 address */
};

typedef struct esp_ip4_addr esp_ip4_addr_t;
typedef struct {
    esp_ip4_addr_t ip;      /**< Interface IPV4 address */
    esp_ip4_addr_t netmask; /**< Interface IPV4 netmask */
    esp_ip4_addr_t gw;      /**< Interface IPV4 gateway address */
} esp_netif_ip_info_t;
c
    esp_netif_ip_info_t ip_info = {
        .gw.addr = inet_addr("192.168.97.183"),
        .ip.addr = inet_addr("192.168.97.190"),
        .netmask.addr = inet_addr("255.255.255.0")
    };

    esp_netif_set_ip_info(netif, &ip_info);

扫描

在wifi目录下面有一个scan例程

bash
I (2988) scan: Max AP number ap_info can hold = 10
I (2988) scan: Total APs scanned = 10, actual AP number ap_info holds = 10
I (2988) scan: SSID             jiao
I (2988) scan: RSSI             -45
I (2988) scan: Authmode         WIFI_AUTH_WPA2_PSK
I (2998) scan: Pairwise Cipher  WIFI_CIPHER_TYPE_CCMP
I (2998) scan: Group Cipher     WIFI_CIPHER_TYPE_CCMP
I (3008) scan: Channel          11
c
void app_main(void)
{
    // Initialize NVS
    esp_err_t ret = nvs_flash_init(); //初始化分区表
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK( ret );

    wifi_scan();
}

分区表

分区表 - ESP32-C3 - — ESP-IDF 编程指南 v5.1.3 文档 (espressif.com)\

esp32-c3的 flash 可以包含多个应用程序,以及多种不同类型的数据(例如校准数据、文件系统数据、参数存储数据等)。因此,我们在 flash 的 默认偏移地址 0x8000 处烧写一张分区表。

分区表的长度为 0xC00 字节,最多可以保存 95 条分区表条目。MD5 校验和附加在分区表之后,用于在运行时验证分区表的完整性。分区表占据了整个 flash 扇区,大小为 0x1000 (4 KB)。因此,它后面的任何分区至少需要位于 (默认偏移地址) + 0x1000 处。

image-20240603223407540

  • flash 的 0x10000 (64 KB) 偏移地址处存放一个标记为 "factory" 的二进制应用程序,且启动加载器将默认加载这个应用程序。
  • 分区表中还定义了两个数据区域,分别用于存储 NVS 库专用分区和 PHY 初始化数据。

nvs分区就是用来配置wifi数据的,凡是涉及到需要使用wifi外设的应用程序,必须先初始化nvs分区。

这一个分区里面实际存储的是一些键值对, 之后连接Wifi的时候会自动的吧Wifi的信息写入这一个分区里面

wifi扫描

Wi-Fi 驱动程序 - ESP32-C3 - — ESP-IDF 编程指南 v5.1.3 文档 (espressif.com)

img

c
/* Initialize Wi-Fi as sta and set scan method */
static void wifi_scan(void)
{
    //WIFI初始化
    ESP_ERROR_CHECK(esp_netif_init()); //创建一个LwIP核心任务
    ESP_ERROR_CHECK(esp_event_loop_create_default()); //创建一个系统事任务
    //创建一个有TCP/IP堆栈的默认网络接口
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);
	
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    //创建Wi-Fi驱动程序任务,并初始化Wi-Fi驱动程序。
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
	
    uint16_t number = DEFAULT_SCAN_LIST_SIZE;
    wifi_ap_record_t ap_info[DEFAULT_SCAN_LIST_SIZE];
    uint16_t ap_count = 0;
    memset(ap_info, 0, sizeof(ap_info));
	//将Wi-Fi模式配置为station模式。
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_start());//启动WI-FI驱动程序。
    esp_wifi_scan_start(NULL, true);
    ESP_LOGI(TAG, "Max AP number ap_info can hold = %u", number);
    ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));
    ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info));
    ESP_LOGI(TAG, "Total APs scanned = %u, actual AP number ap_info holds = %u", ap_count, number);
    for (int i = 0; i < number; i++) {
        ESP_LOGI(TAG, "SSID \t\t%s", ap_info[i].ssid);
        ESP_LOGI(TAG, "RSSI \t\t%d", ap_info[i].rssi);
        print_auth_mode(ap_info[i].authmode);
        if (ap_info[i].authmode != WIFI_AUTH_WEP) {
            print_cipher_type(ap_info[i].pairwise_cipher, ap_info[i].group_cipher);
        }
        ESP_LOGI(TAG, "Channel \t\t%d\n", ap_info[i].primary);
    }

}

以上代码,按照顺序,分为wifi的初始化阶段、配置阶段、启动阶段。因为只扫描名称,没有连接,所以没有执行后面的几个wifi阶段。关于wifi的所有阶段介绍,可以通过下面链接查看。

WIFI连接

一般来说, WIFI有两种模式, STA和AP, STA是站点, AP是热点

这个使用官方的station工程

image-20240603230051614

c
I (6401) esp_netif_handlers: sta ip: 192.168.97.190, mask: 255.255.255.0, gw: 192.168.97.183
I (6401) wifi station: got ip:192.168.97.190
I (6401) wifi station: connected to ap SSID:jiao password:1234567890
I (6411) main_task: Returned from app_main()
c
static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); //设置事件组的位
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}
void wifi_init_sta(void)
{
    //创建一个事件组
    s_wifi_event_group = xEventGroupCreate();
	//初始化Wifi
    ESP_ERROR_CHECK(esp_netif_init());

    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    //注册两个事件
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .password = EXAMPLE_ESP_WIFI_PASS,
            /* Setting a password implies station will connect to all security modes including WEP/WPA.
             * However these modes are deprecated and not advisable to be used. Incase your Access point
             * doesn't support WPA2, these mode can be enabled by commenting below line */
	     .threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
	     .sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );

    ESP_LOGI(TAG, "wifi_init_sta finished.");

    /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
     * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
            pdFALSE,
            pdFALSE,
            portMAX_DELAY);

    /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
     * happened. */
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
    } else {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }

    /* The event will not be processed after unregister */
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
    vEventGroupDelete(s_wifi_event_group);
}

sntp

SNTP的全称是Simple Network Time Protocol,意思是简单网络时间协议,用来从网络中获取当前的时间,也可以称为网络授时。

c
void app_main(void)
{
    ++boot_count;
    ESP_LOGI(TAG, "Boot count: %d", boot_count);

    time_t now;
    struct tm timeinfo;
    time(&now); //获取系统时间
    localtime_r(&now, &timeinfo); //把这一个时间转换为可以理解的时间
    // Is time set? If not, tm_year will be (1970 - 1900).
    if (timeinfo.tm_year < (2016 - 1900)) {
        ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
        obtain_time(); //获取时间
        // update 'now' variable with current time
        time(&now);
    }

    char strftime_buf[64];

    // Set timezone to Eastern Standard Time and print local time
    setenv("TZ", "EST5EDT,M3.2.0/2,M11.1.0", 1);
    tzset(); //设置时区
    localtime_r(&now, &timeinfo); //进行转换
    strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);//把时间转换为字符串
    ESP_LOGI(TAG, "The current date/time in New York is: %s", strftime_buf);

    // Set timezone to China Standard Time
    setenv("TZ", "CST-8", 1);
    tzset();
    localtime_r(&now, &timeinfo);
    strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
    ESP_LOGI(TAG, "The current date/time in Shanghai is: %s", strftime_buf);

    if (sntp_get_sync_mode() == SNTP_SYNC_MODE_SMOOTH) {
		...
    }
	//深度睡眠
    const int deep_sleep_sec = 10;
    ESP_LOGI(TAG, "Entering deep sleep for %d seconds", deep_sleep_sec);
    esp_deep_sleep(1000000LL * deep_sleep_sec);
}
c
static void obtain_time(void)
{
    ESP_ERROR_CHECK(nvs_flash_init() );
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

#if LWIP_DHCP_GET_NTP_SRV
    /**
     * NTP server address could be acquired via DHCP,
     * see following menuconfig options:
     * 'LWIP_DHCP_GET_NTP_SRV' - enable STNP over DHCP
     * 'LWIP_SNTP_DEBUG' - enable debugging messages
     *
     * NOTE: This call should be made BEFORE esp acquires IP address from DHCP,
     * otherwise NTP option would be rejected by default.
     */
    ESP_LOGI(TAG, "Initializing SNTP");
    esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(CONFIG_SNTP_TIME_SERVER);
    config.start = false;                       // start SNTP service explicitly (after connecting)
    config.server_from_dhcp = true;             // accept NTP offers from DHCP server, if any (need to enable *before* connecting)
    config.renew_servers_after_new_IP = true;   // let esp-netif update configured SNTP server(s) after receiving DHCP lease
    config.index_of_first_server = 1;           // updates from server num 1, leaving server 0 (from DHCP) intact
    // configure the event on which we renew servers
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
    config.ip_event_to_renew = IP_EVENT_STA_GOT_IP;
#else
    config.ip_event_to_renew = IP_EVENT_ETH_GOT_IP;
#endif
    config.sync_cb = time_sync_notification_cb; // only if we need the notification function
    esp_netif_sntp_init(&config);

#endif /* LWIP_DHCP_GET_NTP_SRV */

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());  //Wifi连接

#if LWIP_DHCP_GET_NTP_SRV
    ESP_LOGI(TAG, "Starting SNTP");
    esp_netif_sntp_start();
#if LWIP_IPV6 && SNTP_MAX_SERVERS > 2
    /* This demonstrates using IPv6 address as an additional SNTP server
     * (statically assigned IPv6 address is also possible)
     */
    ip_addr_t ip6;
    if (ipaddr_aton("2a01:3f7::1", &ip6)) {    // ipv6 ntp source "ntp.netnod.se"
        esp_sntp_setserver(2, &ip6);
    }
#endif  /* LWIP_IPV6 */

#else
    ESP_LOGI(TAG, "Initializing and starting SNTP");
#if CONFIG_LWIP_SNTP_MAX_SERVERS > 1
    /* This demonstrates configuring more than one server
     */
    esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(2,
                               ESP_SNTP_SERVER_LIST(CONFIG_SNTP_TIME_SERVER, "pool.ntp.org" ) );
#else
    /*
     * This is the basic default config with one server and starting the service
     */
    esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(CONFIG_SNTP_TIME_SERVER);
#endif
    config.sync_cb = time_sync_notification_cb;     // Note: This is only needed if we want
#ifdef CONFIG_SNTP_TIME_SYNC_METHOD_SMOOTH
    config.smooth_sync = true;
#endif

    esp_netif_sntp_init(&config);
#endif

    print_servers();

    // wait for time to be set
    time_t now = 0;
    struct tm timeinfo = { 0 };
    int retry = 0;
    //实际的连接, 尝试15次
    const int retry_count = 15;
    while (esp_netif_sntp_sync_wait(2000 / portTICK_PERIOD_MS) == ESP_ERR_TIMEOUT && ++retry < retry_count) {
        ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
    }
    time(&now);
    localtime_r(&now, &timeinfo);

    ESP_ERROR_CHECK( example_disconnect() );
    esp_netif_sntp_deinit();
}