ESP32S3实现利用Flash储存WiFi信息并自动连接与扫描功能ESP-IDF开发

ESP32S3实现利用Flash储存WiFi信息并自动连接与扫描功能ESP-IDF开发


文章目录

  • ESP32S3实现利用Flash储存WiFi信息并自动连接与扫描功能ESP-IDF开发
  • 前言
  • ESP32S3中的Flash与NVS存储系统
    • 1.物理关系
    • 2.数据操作流程
  • 流程图
  • 一、初始化
    • 1.初始化NVS(仅需一次)
    • 2.初始化WIFI(只能初始化一次不然会报错)
  • 二、自动连接
  • 三、连接失败(第一次连接或者找不到wifi)
    • 1.扫描可用的wifi
    • 2.选中可用的WIFI并输入WIFI信息(ssid和密码)
    • 3.连接WIFI
    • 4.将 SSID 和密码保存到 NVS
    • 5.设备启动时尝试从 NVS 读取 WiFi 信息
  • 总结


前言

在ESP32项目开发中,WiFi连接是一个基础且重要的功能。本文将介绍如何通过利用NVS储存WiFi信息来实现WiFi自动连接以及WiFi扫描功能,并着重讲解如何优化代码结构,避免重复初始化。


提示:以下是本篇文章正文内容

ESP32S3中的Flash与NVS存储系统

在这里插入图片描述
NVS是建立在Flash存储器之上的键值对存储系统。

1.物理关系

在这里插入图片描述

2.数据操作流程

在这里插入图片描述

流程图

在这里插入图片描述
WiFi初始化:包括驱动初始化、网络接口创建等
事件处理设置:注册各种WiFi事件的处理函数
连接流程:自动连接配置的WiFi
扫描流程:在连接完成后执行WiFi扫描


一、初始化

1.初始化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);

2.初始化WIFI(只能初始化一次不然会报错)

代码如下:

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

    // 创建默认事件循环
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // 创建默认的WiFi STA接口
    esp_netif_create_default_wifi_sta();

    // 初始化WiFi驱动
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // 设置WiFi模式为STA
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    // 注册事件处理程序
    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));

二、自动连接

代码如下:

    // 尝试自动连接
    char ssid[32] = {0};
    char password[64] = {0};

    if (read_wifi_credentials(ssid, sizeof(ssid), password, sizeof(password)))
    {
        // 创建连接状态显示界面
        lvgl_port_lock(0);
        static lv_style_t style;
        lv_style_init(&style);
        lv_style_set_bg_opa(&style, LV_OPA_COVER);
        lv_style_set_border_width(&style, 0);
        lv_style_set_pad_all(&style, 0);
        lv_style_set_radius(&style, 0);
        lv_style_set_width(&style, 320);
        lv_style_set_height(&style, 240);

        wifi_connect_page = lv_obj_create(lv_scr_act());
        lv_obj_add_style(wifi_connect_page, &style, 0);

        label_wifi_connect = lv_label_create(wifi_connect_page);
        lv_label_set_text(label_wifi_connect, "WLAN连接中...");
        lv_obj_set_style_text_font(label_wifi_connect, &font_alipuhui20, 0);
        lv_obj_align(label_wifi_connect, LV_ALIGN_CENTER, 0, -50);
        lvgl_port_unlock();

        ESP_LOGI(TAG, "尝试自动连接WiFi: SSID=%s", ssid);
        wifi_config_t wifi_config = {
            .sta = {
                .threshold.authmode = WIFI_AUTH_WPA2_PSK,
            },
        };
        strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid));
        strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password));

        ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
        ESP_ERROR_CHECK(esp_wifi_start());
        esp_err_t err = esp_wifi_connect();

        if (err == ESP_OK)
        {
            // 等待连接结果
            EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
                                                   WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
                                                   pdFALSE,
                                                   pdFALSE,
                                                   portMAX_DELAY);

            if (bits & WIFI_CONNECTED_BIT)
            {
                ESP_LOGI(TAG, "WiFi连接成功");
                // 连接成功后延时一会儿再跳转到扫描界面
                vTaskDelay(pdMS_TO_TICKS(1000));
                lvgl_port_lock(0);
                lv_obj_del(wifi_connect_page);
                lvgl_port_unlock();
                app_wifi_connect(); // 跳转到WiFi扫描界面
            }
            else
            {
                ESP_LOGE(TAG, "WiFi连接失败");
                lvgl_port_lock(0);
                lv_label_set_text(label_wifi_connect, "WLAN连接失败");
                lvgl_port_unlock();
                vTaskDelay(pdMS_TO_TICKS(1000));
                lvgl_port_lock(0);
                lv_obj_del(wifi_connect_page);
                lvgl_port_unlock();
                app_wifi_connect(); // 连接失败也跳转到WiFi扫描界面
            }
        }
        else
        {
            ESP_LOGE(TAG, "WiFi连接失败: %s", esp_err_to_name(err));
            lvgl_port_lock(0);
            lv_label_set_text(label_wifi_connect, "WLAN连接失败");
            lvgl_port_unlock();
            vTaskDelay(pdMS_TO_TICKS(1000));
            lvgl_port_lock(0);
            lv_obj_del(wifi_connect_page);
            lvgl_port_unlock();
            app_wifi_connect(); // 连接失败跳转到WiFi扫描界面
        }
    }
    else
    {
        ESP_LOGW(TAG, "未找到存储的WiFi信息,进入手动配网模式");
        app_wifi_connect(); // 没有保存的WiFi信息,直接跳转到扫描界面
    }

图片如下:
请添加图片描述
请添加图片描述

终端显示如下:
I (2120) app_ui: 成功读取WiFi信息: SSID=Mai
I (2130) app_ui: 尝试自动连接WiFi: SSID=Mai
I (2130) phy_init: phy_version 680,a6008b2,Jun 4 2024,16:41:10
I (2170) wifi:mode : sta (d8:3b:da:4d:21:a0)
I (2170) wifi:enable tsf
I (4580) app_ui: retry to connect to the AP
I (4580) app_ui: connect to the AP fail
I (6980) app_ui: retry to connect to the AP
I (6980) app_ui: connect to the AP fail
I (9390) app_ui: retry to connect to the AP
I (9390) app_ui: connect to the AP fail
I (11790) app_ui: connect to the AP fail
E (11790) app_ui: WiFi连接失败

三、连接失败(第一次连接或者找不到wifi)

1.扫描可用的wifi

代码如下:

static void wifi_scan(wifi_ap_record_t ap_info[], uint16_t *ap_number)
{
    // 移除重复的初始化代码,因为在app_wifi_auto_connect()中已经完成初始化

    uint16_t ap_count = 0;
    memset(ap_info, 0, *ap_number * sizeof(wifi_ap_record_t));

    // 直接开始扫描
    ESP_ERROR_CHECK(esp_wifi_scan_start(NULL, true));

    ESP_LOGI(TAG, "Max AP number ap_info can hold = %u", *ap_number);
    ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));              // 获取扫描到的wifi数量
    ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(ap_number, ap_info)); // 获取真实的获取到wifi数量和信息
    ESP_LOGI(TAG, "Total APs scanned = %u, actual AP number ap_info holds = %u", ap_count, *ap_number);
}

图片如下:请添加图片描述
请添加图片描述

终端显示如下:
I (9830) app_ui: Total APs scanned = 36, actual AP number ap_info holds = 10
I (9830) app_ui: SSID Mai
I (9830) app_ui: RSSI -29
I (9840) app_ui: SSID mzh02
I (9840) app_ui: RSSI -53
I (9840) app_ui: SSID YY-139
I (9850) app_ui: RSSI -64
I (9850) app_ui: SSID huangzr66
I (9850) app_ui: RSSI -71
I (9860) app_ui: SSID apple
I (9860) app_ui: RSSI -75
I (9860) app_ui: SSID Gyc
I (9870) app_ui: RSSI -77
I (9870) app_ui: SSID FHP
I (9870) app_ui: RSSI -79
I (9880) app_ui: SSID ChinaNet-PS5U
I (9880) app_ui: RSSI -80
I (9890) app_ui: SSID HUAWEI-XY6E
I (9890) app_ui: RSSI -80
I (9890) app_ui: SSID 小金发
I (9900) app_ui: RSSI -81

2.选中可用的WIFI并输入WIFI信息(ssid和密码)

void app_wifi_connect(void)
{
    lvgl_port_lock(0);
    // 创建WLAN扫描页面
    static lv_style_t style;
    lv_style_init(&style);
    lv_style_set_bg_opa(&style, LV_OPA_COVER); // 背景透明度
    lv_style_set_border_width(&style, 0);      // 边框宽度
    lv_style_set_pad_all(&style, 0);           // 内间距
    lv_style_set_radius(&style, 0);            // 圆角半径
    lv_style_set_width(&style, 320);           // 宽
    lv_style_set_height(&style, 240);          // 高
    wifi_scan_page = lv_obj_create(lv_scr_act());
    lv_obj_add_style(wifi_scan_page, &style, 0);
    // 在WLAN扫描页面显示提示
    lv_obj_t *label_wifi_scan = lv_label_create(wifi_scan_page);
    lv_label_set_text(label_wifi_scan, "WLAN扫描中...");
    lv_obj_set_style_text_font(label_wifi_scan, &font_alipuhui20, 0);
    lv_obj_align(label_wifi_scan, LV_ALIGN_CENTER, 0, -50);
    lvgl_port_unlock();

    // 扫描WLAN信息
    wifi_ap_record_t ap_info[DEFAULT_SCAN_LIST_SIZE]; // 记录扫描到的wifi信息
    uint16_t ap_number = DEFAULT_SCAN_LIST_SIZE;
    wifi_scan(ap_info, &ap_number); // 扫描附近wifi

    lvgl_port_lock(0);
    // 扫描附近wifi信息成功后 删除提示文字
    lv_obj_del(label_wifi_scan);
    // 创建wifi信息列表
    wifi_list = lv_list_create(wifi_scan_page);
    lv_obj_set_size(wifi_list, lv_pct(100), lv_pct(100));
    lv_obj_set_style_border_width(wifi_list, 0, 0);
    lv_obj_set_style_text_font(wifi_list, &font_alipuhui20, 0);
    lv_obj_set_scrollbar_mode(wifi_list, LV_SCROLLBAR_MODE_OFF); // 隐藏wifi_list滚动条
    // 显示wifi信息
    lv_obj_t *btn;
    for (int i = 0; i < ap_number; i++)
    {
        ESP_LOGI(TAG, "SSID \t\t%s", ap_info[i].ssid); // 终端输出wifi名称
        ESP_LOGI(TAG, "RSSI \t\t%d", ap_info[i].rssi); // 终端输出wifi信号质量
        // 添加wifi列表
        btn = lv_list_add_btn(wifi_list, LV_SYMBOL_WIFI, (const char *)ap_info[i].ssid);
        lv_obj_add_event_cb(btn, list_btn_cb, LV_EVENT_CLICKED, NULL); // 添加点击回调函数
    }
    lvgl_port_unlock();

    // 创建wifi连接任务
    xQueueWifiAccount = xQueueCreate(2, sizeof(wifi_account_t));
    xTaskCreatePinnedToCore(wifi_connect, "wifi_connect", 4 * 1024, NULL, 5, NULL, 1); // 创建wifi连接任务
}

提示:这里的lv_style_...是lvgl页面显示
请添加图片描述

3.连接WIFI

提示:这里用了freertos的队列来等待WIFI消息的输入完成

代码如下:

static void wifi_connect(void *arg)
{
    wifi_account_t wifi_account;

    while (true)
    {
        // 如果收到wifi账号队列消息
        if (xQueueReceive(xQueueWifiAccount, &wifi_account, portMAX_DELAY))
        {
            wifi_config_t wifi_config = {
                .sta = {
                    .threshold.authmode = WIFI_AUTH_WPA2_PSK,
                    .sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
                    .sae_h2e_identifier = "",
                },
            };
            strcpy((char *)wifi_config.sta.ssid, wifi_account.wifi_ssid);
            strcpy((char *)wifi_config.sta.password, wifi_account.wifi_password);
            ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
            ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                     wifi_config.sta.ssid, wifi_config.sta.password);
            esp_wifi_connect();
            /* 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",
                         wifi_config.sta.ssid, wifi_config.sta.password);
                lvgl_port_lock(0);
                lv_label_set_text(label_wifi_connect, "WLAN连接成功");
                lvgl_port_unlock();
                save_wifi_credentials(wifi_account.wifi_ssid, wifi_account.wifi_password);//保存wifi消息
            }
            else if (bits & WIFI_FAIL_BIT)
            {
                ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                         wifi_config.sta.ssid, wifi_config.sta.password);
                lvgl_port_lock(0);
                lv_label_set_text(label_wifi_connect, "WLAN连接失败");
                lvgl_port_unlock();
            }
            else
            {
                ESP_LOGE(TAG, "UNEXPECTED EVENT");
                lvgl_port_lock(0);
                lv_label_set_text(label_wifi_connect, "WLAN连接异常");
                lvgl_port_unlock();
            }
        }
    }
}

4.将 SSID 和密码保存到 NVS

void save_wifi_credentials(const char *ssid, const char *password)
{
    nvs_handle_t nvs_handle;
    esp_err_t err = nvs_open("wifi_storage", NVS_READWRITE, &nvs_handle);
    if (err == ESP_OK)
    {
        nvs_set_str(nvs_handle, "ssid", ssid);         // 保存SSID
        nvs_set_str(nvs_handle, "password", password); // 保存密码
        err = nvs_commit(nvs_handle);                  // 提交更改
        nvs_close(nvs_handle);
        if (err == ESP_OK)
        {
            ESP_LOGI(TAG, "WiFi信息已保存: SSID=%s", ssid);
        }
        else
        {
            ESP_LOGE(TAG, "提交WiFi信息失败: %s", esp_err_to_name(err));
        }
    }
    else
    {
        ESP_LOGE(TAG, "保存WiFi信息失败: %s", esp_err_to_name(err));
    }
}
}

5.设备启动时尝试从 NVS 读取 WiFi 信息

bool read_wifi_credentials(char *ssid, size_t ssid_len, char *password, size_t pass_len)
{
    nvs_handle_t nvs_handle;
    esp_err_t err = nvs_open("wifi_storage", NVS_READONLY, &nvs_handle);
    if (err == ESP_OK)
    {
        err = nvs_get_str(nvs_handle, "ssid", ssid, &ssid_len);
        if (err != ESP_OK)
        {
            ESP_LOGE(TAG, "读取SSID失败: %s", esp_err_to_name(err));
            nvs_close(nvs_handle);
            return false;
        }
        err = nvs_get_str(nvs_handle, "password", password, &pass_len);
        if (err != ESP_OK)
        {
            ESP_LOGE(TAG, "读取密码失败: %s", esp_err_to_name(err));
            nvs_close(nvs_handle);
            return false;
        }
        nvs_close(nvs_handle);
        ESP_LOGI(TAG, "成功读取WiFi信息: SSID=%s", ssid);
        return true;
    }
    else
    {
        ESP_LOGE(TAG, "打开NVS失败: %s", esp_err_to_name(err));
        return false;
    }
}

终端显示如下:
I (3420) esp_netif_handlers: sta ip: 192.168.177.251, mask: 255.255.255.0, gw: 192.168.177.248
I (3420) app_ui: got ip:192.168.177.251
I (3420) app_ui: WiFi连接成功


完整的项目工程:https://github.com/JoshuaMaiES/ESP32
链接: link

总结

开发平台:ESP-IDF
主要组件:
WiFi驱动
NVS存储
事件处理系统
FreeRTOS任务管理
注意: WiFi初始化只能初始化一次不然一直报错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值