当前位置: 首页 > news >正文

ESP32S3开发:实现WiFi扫描与连接功能

ESP32S3开发笔记:实现WiFi扫描与连接功能

一、项目概述

本文将详细讲解如何在ESP32S3开发板上实现WiFi扫描和连接功能。我们将使用LVGL图形库创建一个友好的用户界面,实现:

  • 扫描并显示周围WiFi名称
  • 点击WiFi名称后进入密码输入界面
  • 输入密码连接WiFi

整个项目基于ESP-IDF框架开发,是一个非常实用的应用案例。无论你是嵌入式开发新手还是有经验的工程师,都能从中学到ESP32的WiFi功能实现方法。

二、硬件与软件准备

硬件需求

  • ESP32S3开发板
  • USB数据线
  • 带显示屏的开发板(本例中使用320x240液晶屏)

软件环境

  • ESP-IDF开发环境(本例使用v5.4.2)
  • VSCode编辑器
  • ESP-IDF VSCode插件

三、项目结构解析

与普通ESP-IDF项目相比,本项目有几个特殊之处:

  1. 自定义分区表:使用了自定义的分区表文件partitions.csv,为存储中文字库提供更大空间
  2. 中文字库文件:添加了中文字体支持,以便正确显示中文WiFi名称
  3. LVGL界面:使用LVGL图形库创建用户界面

3.1 项目目录结构

项目根目录
├── main
│   ├── app_ui.c            // 应用程序UI相关代码
│   ├── app_ui.h            // UI头文件
│   ├── esp32_s3_szp.c      // 开发板BSP文件
│   ├── font_alipuhui20.c   // 中文字库文件
│   ├── main.c              // 主程序入口
│   └── CMakeLists.txt      // 编译配置文件
├── partitions.csv          // 自定义分区表
└── CMakeLists.txt          // 主CMakeLists文件

四、分区表详解

4.1 为什么需要自定义分区表?

在ESP32开发中,分区表负责为Flash划分不同的区域,存放不同类型的文件。默认分区表为应用程序分配的空间是1MB,但由于我们的项目包含中文字库,体积较大(约3MB),因此需要自定义分区表,增加应用程序的存储空间。

4.2 分区表内容

# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs,      data, nvs,     0x9000,  0x6000,
phy_init, data, phy,     ,        0x1000,
factory,  app,  factory, ,        7M,

与默认分区表相比,我们将factory分区的大小从1MB增加到了7MB,这样就能容纳我们的应用程序和中文字库。

4.3 配置使用自定义分区表

在menuconfig中,需要选择Custom partition table CSV选项:

  1. 在VSCode中打开终端
  2. 运行idf.py menuconfig
  3. 进入Partition Table选项
  4. 选择Custom partition table CSV
  5. 设置分区表文件名为partitions.csv

五、中文字库制作

5.1 为什么需要中文字库?

ESP32默认不支持中文显示。由于WiFi名称可能包含中文字符,我们需要添加中文字体支持。本项目使用阿里普惠字体,它是免费可商用的。

5.2 字库制作步骤

1. 获取字体文件
  • 阿里普惠字体:从阿里巴巴字体网站下载
  • Awesome字体:用于显示WiFi图标,从官方网站下载
2. 使用LVGL在线工具制作字库

访问LVGL字体转换工具,按以下步骤设置:

  1. 基本设置

    • Name: font_alipuhui20(自定义名称)
    • Size: 20(像素大小)
    • Bpp: 4(每个像素的位数,影响显示效果和文件大小)
  2. 添加中文字体

    • 选择阿里普惠字体文件
    • Range: 0x20-0x2FA1F(包含所有中文字符的Unicode范围)
  3. 添加WiFi图标字体

    • 点击"Include another font"
    • 选择fa-soild-900.ttf文件
    • Range: 0xf1eb(WiFi图标的Unicode码)
  4. 点击"Submit",等待生成字体文件

注意:生成过程可能需要10分钟左右,如果浏览器提示"页面无响应",选择"等待"即可。

5.3 在项目中使用字库

  1. 将生成的font_alipuhui20.c文件复制到main文件夹

  2. 修改文件开头的包含头文件部分:

    // 修改前
    #ifdef LV_LVGL_H_INCLUDE_SIMPLE
    #include "lvgl.h"
    #else
    #include "lvgl/lvgl.h"
    #endif// 修改后
    #include "lvgl.h"
    
  3. 在CMakeLists.txt中添加字体文件:

    idf_component_register(SRCS "font_alipuhui20.c" "app_ui.c" "esp32_s3_szp.c" "main.c"INCLUDE_DIRS ".")
    
  4. 在使用字体的文件中声明:

    LV_FONT_DECLARE(font_alipuhui20);
    
  5. 使用字体:

    lv_obj_set_style_text_font(label, &font_alipuhui20, 0);
    

六、应用程序设计

6.1 主程序流程

主程序入口app_main()函数的代码如下:

void app_main(void)
{// 初始化NVSesp_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 );bsp_i2c_init();       // I2C初始化pca9557_init();       // IO扩展芯片初始化bsp_lvgl_start();     // 初始化液晶屏lvgl接口app_wifi_connect();   // 运行wifi连接程序
}

程序执行流程:

  1. 初始化NVS(非易失性存储,用于存储WiFi配置)
  2. 初始化I2C总线
  3. 初始化IO扩展芯片
  4. 初始化LVGL液晶屏接口
  5. 运行WiFi连接应用

6.2 WiFi扫描与连接界面

app_wifi_connect()函数是整个应用的核心,它完成了以下几个主要功能:

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);                    // 扫描附近wifilvgl_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信息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连接任务
}

这个函数可以分为四个主要部分:

  1. 创建扫描提示页面(4-21行):

    • 创建全屏页面
    • 显示"WLAN扫描中…"提示
  2. 执行WiFi扫描(23-26行):

    • 调用wifi_scan()函数扫描附近WiFi
  3. 显示WiFi列表(28-46行):

    • 删除"扫描中"提示
    • 创建WiFi列表控件
    • 遍历扫描到的WiFi并添加到列表中
    • 为每个WiFi名称添加点击事件
  4. 创建WiFi连接任务(48-50行):

    • 创建消息队列用于传递WiFi账号和密码
    • 创建WiFi连接任务,等待用户输入密码后执行连接

重要提示:使用esp_lvgl_port组件时,所有LVGL相关操作都必须位于lvgl_port_lock(0)lvgl_port_unlock()之间,这确保了LVGL画面的正常显示。

6.3 密码输入界面

当用户点击WiFi名称后,会进入list_btn_cb()函数,该函数负责创建密码输入界面:

// 进入输入密码界面
static void list_btn_cb(lv_event_t * e)
{// 获取点击到的WiFi名称const char *wifi_name=NULL;lv_event_code_t code = lv_event_get_code(e);lv_obj_t * obj = lv_event_get_target(e);if(code == LV_EVENT_CLICKED) {wifi_name = lv_list_get_btn_text(wifi_list, obj);ESP_LOGI(TAG, "WLAN Name: %s", wifi_name);}// 创建密码输入页面wifi_password_page = lv_obj_create(lv_scr_act());lv_obj_set_size(wifi_password_page, 320, 240);lv_obj_set_style_border_width(wifi_password_page, 0, 0); // 设置边框宽度lv_obj_set_style_pad_all(wifi_password_page, 0, 0);      // 设置间隙lv_obj_set_style_radius(wifi_password_page, 0, 0);       // 设置圆角// 创建返回按钮// ...// 显示选中的wifi名称// ...// 创建密码输入框ta_pass_text = lv_textarea_create(wifi_password_page);lv_obj_set_style_text_font(ta_pass_text, &lv_font_montserrat_20, 0);lv_textarea_set_one_line(ta_pass_text, true);              // 一行显示lv_textarea_set_password_mode(ta_pass_text, false);        // 是否使用密码输入显示模式lv_textarea_set_placeholder_text(ta_pass_text, "password"); // 设置提醒词lv_obj_set_width(ta_pass_text, 150);                       // 宽度lv_obj_align(ta_pass_text, LV_ALIGN_TOP_LEFT, 10, 40);     // 位置lv_obj_add_state(ta_pass_text, LV_STATE_FOCUSED);          // 显示光标// 创建"连接按钮"// ...// 创建"删除按钮"// ...// 创建数字、小写字母、大写字母的roller及确认按钮// ...
}

密码输入界面包含以下元素:

  1. 返回按钮:返回到WiFi列表
  2. WiFi名称显示
  3. 密码输入框
  4. 连接按钮(OK)
  5. 删除按钮(退格键)
  6. 三个字符选择器(roller):
    • 数字(0-9)
    • 小写字母(a-z)
    • 大写字母(A-Z)
  7. 每个roller下的确认按钮

6.4 WiFi连接实现

点击"OK"按钮后,会调用btn_connect_cb函数:

static void btn_connect_cb(lv_event_t * e)
{lv_event_code_t code = lv_event_get_code(e);if(code == LV_EVENT_CLICKED) {// 获取wifi名称char* wifi_name = NULL;wifi_name = lv_label_get_text(label_wifi_name);ESP_LOGI(TAG, "WiFi Name: %s", wifi_name);// 获取wifi密码const char* pass_text = lv_textarea_get_text(ta_pass_text);ESP_LOGI(TAG, "WiFi Password: %s", pass_text);// 删除密码界面lv_obj_del(wifi_password_page);// 显示"WLAN连接中..."lv_obj_t *label_wifi_connect = lv_label_create(wifi_scan_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);// 发送wifi账号和密码到连接任务wifi_account_t wifi_account;memset(&wifi_account, 0, sizeof(wifi_account_t));strcpy((char *)wifi_account.ssid, wifi_name);strcpy((char *)wifi_account.password, pass_text);if (xQueueWifiAccount != NULL) {xQueueSend(xQueueWifiAccount, &wifi_account, 0);}}
}

此函数完成了以下操作:

  1. 获取用户选择的WiFi名称
  2. 获取用户输入的密码
  3. 删除密码输入界面
  4. 显示"WLAN连接中…"提示
  5. 将WiFi名称和密码通过队列发送给WiFi连接任务

wifi_connect任务在接收到这些信息后会执行连接:

static void wifi_connect(void *pvParameters)
{wifi_account_t wifi_account;// 等待接收WiFi账号密码if (xQueueReceive(xQueueWifiAccount, &wifi_account, portMAX_DELAY)) {// 开始连接WiFiwifi_init_sta((char *)wifi_account.ssid, (char *)wifi_account.password);// 根据连接结果显示不同提示lvgl_port_lock(0);if (wifi_connect_status == WIFI_CONNECTED_OK) {// 连接成功lv_obj_t *label_wifi_connected = lv_label_create(wifi_scan_page);lv_label_set_text(label_wifi_connected, "WLAN连接成功!");lv_obj_set_style_text_font(label_wifi_connected, &font_alipuhui20, 0);lv_obj_align(label_wifi_connected, LV_ALIGN_CENTER, 0, -50);} else if (wifi_connect_status == WIFI_CONNECTED_FAIL) {// 连接失败lv_obj_t *label_wifi_connected = lv_label_create(wifi_scan_page);lv_label_set_text(label_wifi_connected, "WLAN连接失败!");lv_obj_set_style_text_font(label_wifi_connected, &font_alipuhui20, 0);lv_obj_align(label_wifi_connected, LV_ALIGN_CENTER, 0, -50);} else {// 连接异常lv_obj_t *label_wifi_connected = lv_label_create(wifi_scan_page);lv_label_set_text(label_wifi_connected, "WLAN连接异常!");lv_obj_set_style_text_font(label_wifi_connected, &font_alipuhui20, 0);lv_obj_align(label_wifi_connected, LV_ALIGN_CENTER, 0, -50);}lvgl_port_unlock();}vTaskDelete(NULL);
}

连接完成后,会根据连接结果在屏幕上显示相应的提示:

  • 连接成功:显示"WLAN连接成功!"
  • 连接失败:显示"WLAN连接失败!"
  • 连接异常:显示"WLAN连接异常!"

七、运行与测试

  1. 编译并下载程序到ESP32S3开发板
  2. 程序运行后,屏幕会先显示"WLAN扫描中…"
  3. 扫描完成后,显示附近WiFi的名称列表
  4. 点击想要连接的WiFi
  5. 在密码输入界面输入密码
    • 可以通过三个roller选择数字、小写字母和大写字母
    • 每选择一个字符,点击下方的确认按钮(√)添加到密码框
    • 如果输入错误,可以使用删除按钮删除
  6. 输入完成后点击"OK"按钮
  7. 程序会显示"WLAN连接中…",然后根据连接结果显示成功或失败信息

八、优化与拓展

8.1 信号强度显示

目前程序只显示了WiFi名称,没有显示信号强度。可以通过以下方式添加信号强度显示:

// 根据RSSI值选择不同的WiFi图标
const char* get_wifi_icon(int rssi)
{if (rssi > -50) {return LV_SYMBOL_WIFI;       // 信号强} else if (rssi > -70) {return LV_SYMBOL_WIFI;       // 信号中(这里可以用不同图标)} else {return LV_SYMBOL_WIFI;       // 信号弱}
}// 在添加WiFi列表项时使用
btn = lv_list_add_btn(wifi_list, get_wifi_icon(ap_info[i].rssi), (const char *)ap_info[i].ssid);

8.2 密码输入增强

目前密码输入只支持数字和字母,可以添加特殊字符支持:

// 修改数字roller的选项,添加特殊字符
const char * opts_num = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n!\n@\n#\n$\n%\n^\n&\n*";

8.3 记住密码功能

可以使用NVS存储已连接的WiFi信息,下次自动连接:

// 存储WiFi信息到NVS
void save_wifi_config(const char* ssid, const char* password)
{nvs_handle_t my_handle;ESP_ERROR_CHECK(nvs_open("storage", NVS_READWRITE, &my_handle));ESP_ERROR_CHECK(nvs_set_str(my_handle, "ssid", ssid));ESP_ERROR_CHECK(nvs_set_str(my_handle, "password", password));ESP_ERROR_CHECK(nvs_commit(my_handle));nvs_close(my_handle);
}// 在连接成功后调用
if (wifi_connect_status == WIFI_CONNECTED_OK) {save_wifi_config(wifi_account.ssid, wifi_account.password);// ...
}

九、总结

通过本文,我们详细讲解了如何在ESP32S3开发板上实现WiFi扫描和连接功能,包括:

  1. 创建自定义分区表,解决中文字库占用空间过大的问题
  2. 使用免费商用的阿里普惠字体制作中文字库,支持显示中文WiFi名称
  3. 使用LVGL图形库创建友好的用户界面
  4. 实现WiFi扫描、显示列表、密码输入和连接功能
  5. 通过任务和队列实现不同功能模块之间的通信

这个项目是物联网应用的基础,为后续开发更复杂的联网应用奠定了基础。希望这篇教程对大家有所帮助!


参考资料

  1. ESP-IDF 分区表介绍
  2. ESP-IDF NVS 介绍
  3. 阿里巴巴字体网站
  4. LVGL字体制作工具
  5. Unicode字符范围查询
http://www.dtcms.com/a/265217.html

相关文章:

  • 插值与拟合(3):B样条曲线
  • MySQL 用户管理与权限控制
  • 进阶向:Django框架深度解析各核心组件的作用与协作
  • Spring生态在Java开发
  • 自动驾驶行业向端到端架构转型
  • ArrayList剖析
  • 买卖股票的最佳时机--js 算法
  • linux LAMP 3
  • 开疆智能CCLinkIE转CANopen网关连接GBS20机器人配置案例
  • 第四章 网络传输介质与综合布线基础
  • 04-动态规划
  • OpenHarmony 5.0 解决点击导航栏切换后台按钮再切换到前台导航栏可能覆盖输入法问题,导致输入法下沉,最下面的显示不全
  • day046-tomcat与部署war包、jar包
  • 为什么星敏感器(Star Tracker)需要时间同步?—— 从原理到应用的全解析
  • Day04:玩转标准库中的数据处理与日志记录
  • pytest fixture基础大全详解
  • 爬虫反爬策略实战:UserAgent代理池简明指南
  • 电磁场有限元方法EX2.2-里兹法求解泊松方程控制的边值问题
  • 二刷 苍穹外卖day11
  • 讲解视频:分布滞后非线性模型DLNM​​专题:从基础到进阶学习路径
  • 记录一个QT中pro文件换行需要注意的问题
  • 第29篇:Linux审计系统深度解析:基于OpenEuler 24.03的实践指南
  • 【中文核心期刊推荐】《电子测量技术》
  • RabbitMQ使用topic Exchange实现微服务分组订阅
  • 基于SEP3203微处理器的嵌入式最小硬件系统设计
  • VBA初学习记录
  • OneCode表单架构设计:注解驱动与组件化的完美结合
  • 腾讯云认证考试报名 - TDSQL数据库交付运维专家(TCCE PostgreSQL版)
  • windows的vscode无法通过ssh连接ubuntu的解决办法
  • 网站面临爬虫攻击waf能防护住吗