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初始化只能初始化一次不然一直报错