【立创·实战派ESP32-S3】文档教程
第 12 章 WiFi 扫描并连接
本章例程,将实现扫描你周围所有的 wifi 信号名称,并把它们的名称显示到液晶屏上,然后点击选择你自己的 wifi 名称,输入密码后,连接到 wifi。
12.1 使用例程
把开发板提供的【09-wifi_scan_connect】例程复制到你的实验文件夹当中,并使用 VSCode 打开工程。
连接开发板到电脑,在 VSCode 上选择串口号,选择目标芯片为 esp32s3,串口下载方式,然后点击“一键三联”按钮,等待编译下载打开终端。
开发板开始运行程序后,液晶屏显示正在扫描 WLAN,扫描到附近的 WLAN 信息后,以列表的形式列出 WIFI 名称,最多列 10 个(可以在程序中改这个数目)。
你可以点击其中的一个,进入密码输入页面。在密码输入页面,可以输入数字、小写字母、大写字母,字母的选择,没有使用键盘,因为屏幕太小了,程序中用的是 lvgl 的 roller 部件。目前程序还没有支持特殊符号输入,一般的 wifi 密码,有数字和大小写字母就够了,如果你想支持特殊符号,也比较简单,可以把想要添加的特殊符号添加到数字的 rolloer 中就可以了。每个 roller 下面都有一个选择按钮,用来把 roller 上对应的字母或数字输入到密码输入框。如果不小心输入错误,可以通过删除键删除字母。点击密码左上角的按钮可以返回到 wifi 列表页面,重新选择你要连接的 wifi。
密码输入成功后,点击密码输入框后面的 OK 按键,进行 wifi 连接。液晶屏提示正在连接,如果连接成功,液晶屏提示连接成功,如果连接失败,液晶屏提示连接失败。
以上就是本例程的使用方法。
12.2 例程讲解
12.2.1 分区表
和之前例程相比,一级目录下多了一个 partitions.csv 文件,此文件是分区表文件,负责给 flash 分区。这是我们第 1 次使用自定义的分区表,之前例程,用的都是默认的分区表。
分区表负责给 flash 划分为多个区域,存放不同类型的文件,包括 bootloader、应用程序等。
关于分区表的详细介绍,大家可以看一下官方分区表介绍。
官方分区表介绍:https://docs.espressif.com/projects/esp-idf/zh_CN/v5.1.4/esp32s3/api-guides/partition-tables.html
打开 menuconfig,可以看到它所支持的几种分区表的文件名称,如下图所示:
可以看到,本例程中,我们选择的是 Custom partition table CSV,即自定义分区表。选择自定义分区表,需要在例程中新建一个名称为 partitions.csv 的文件,或者从别的例程中复制也可以。注意,它和 main 文件夹是同级,不是放在 main 文件夹下。如下图所示:
这个文件中的内容如下所示:
# 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,
2
3
4
5
我们先看一下默认的分区表,默认选择的是 Single factory app,no OTA,不用自己放 partitions.csv 文件到工程中,它的分区表内容如下,这个内容是在上面“官方分区表介绍”链接里面复制过来的。
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
2
3
4
5
它以行列形式排列,#部分是注释,除去注释,分为 3 行 6 列,其中第 6 列没有写内容。每一列的名称,在它的上面注释部分。
我们看 Name 为 factory 的这一行,这个是我们自己写的应用程序存放的地址,Offset 是偏移地址,Size 是应用程序空间大小。这个分区表中,应用程序被放到了 0x10000 地址,且给它分配了 1M 的空间。
接下来,我们再看一下例程中自定义的分区表,因为本例程编译后的 bin 文件大于了默认的 1M,所以这里我们只是把 factory 的存储空间改大了。另外,Offset 偏移地址,可以省略不写,它会自动使用前一个分区的偏移地址 + 存储空间大小的和作为下一个分区的偏移地址。
12.2.2 main 文件夹
接下来再看 main 文件夹里面的文件。
esp32_s3_szp.c 就是之前例程写好的开发板 bsp 文件。
app_ui.c 是本例程实现的应用程序。
font_alipuhui20.c 文件,是字库文件,它使用 lvgl 在线工具生成,使用开源、免费、可商用的阿里普惠字体,包含了 27780 个中文汉字(是 GB18030-2022 规定的 2 级要求,覆盖了《通用规范汉字表》),也就是几乎囊括了所有常用和不常用的汉字。因为我们扫描到的 wifi 名称,可能含有中文,且不知道是什么汉字,所以必须包含“所有”的汉字,才可以正常显示。如果只使用英文库,显示到的中文 wifi 名称,会用一个特殊符号代替。
关于字库制作的详细介绍,在下一节介绍。
其它几个文件,之前的例程中已经介绍过,这里就不介绍了。
12.2.3 例程执行流程
app_main 函数代码如下所示:
void app_main(void)
{
// 初始化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 );
bsp_i2c_init(); // I2C初始化
pca9557_init(); // IO扩展芯片初始化
bsp_lvgl_start(); // 初始化液晶屏lvgl接口
app_wifi_connect(); // 运行wifi连接程序
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
第 4~9 行,就初始化 nvs 的代码,你只需要记住,使用 wifi,必须初始化 NVS,就可以了。关于 NVS 的详细介绍,看下面的链接:
第 11~13 行,前面章节介绍过。
第 14 行,运行 wifi 连接程序,这个就是我们本例程运行的主要程序,下面我们看它是怎么实现的。
app_wifi_connect()函数
// wifi连接
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, &lv_font_montserrat_20, 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连接任务
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
这个函数分为 4 部分。
第 4~21 行为第 1 部分,创建页面并显示 WLAN 正在扫描的提示。
第 23~26 行为第 2 部分,执行 WLAN 扫描。
第 28~46 行为第 3 部分,把页面上的 WLAN 正在扫描的提示删除,并创建列表显示扫描到的 WIFI 名称。
第 48~50 行为第 4 部分,创建 wifi 连接任务。进入这个任务后,会一直等待密码队列消息,当输入密码后,这个任务才会执行完毕。
使用 esp_lvgl_port 组件初始化的 lvgl,在使用 lvgl 函数时,必须以 lvgl_port_lock(0)
作为开头,以 lvgl_port_unlock()
作为结尾,确保 lvgl 画面正常运行。
关于 lvgl 图形库的使用,不是本教程重点,这里不做介绍,大家可以去找资源学习。
wifi_scan()扫描函数是参考官方 examples\wifi\scan 例程修改的。
wifi_connect()任务函数是参考官方 examples\wifi\getting_started\station 例程修改的。
显示 wifi 列表的时候,使用 lv_obj_add_event_cb()
函数声明了一个列表事件处理函数 list_btn_cb()
。当点击列表中的任意一个 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); // 设置圆角
// 创建返回按钮
lv_obj_t *btn_back = lv_btn_create(wifi_password_page);
lv_obj_align(btn_back, LV_ALIGN_TOP_LEFT, 0, 0);
lv_obj_set_size(btn_back, 60, 40);
lv_obj_set_style_border_width(btn_back, 0, 0); // 设置边框宽度
lv_obj_set_style_pad_all(btn_back, 0, 0); // 设置间隙
lv_obj_set_style_bg_opa(btn_back, LV_OPA_TRANSP, LV_PART_MAIN); // 背景透明
lv_obj_set_style_shadow_opa(btn_back, LV_OPA_TRANSP, LV_PART_MAIN); // 阴影透明
lv_obj_add_event_cb(btn_back, btn_back_cb, LV_EVENT_ALL, NULL); // 添加按键处理函数
lv_obj_t *label_back = lv_label_create(btn_back);
lv_label_set_text(label_back, LV_SYMBOL_LEFT); // 按键上显示左箭头符号
lv_obj_set_style_text_color(label_back, lv_color_hex(0x000000), 0);
lv_obj_align(label_back, LV_ALIGN_TOP_LEFT, 10, 10);
// 显示选中的wifi名称
label_wifi_name = lv_label_create(wifi_password_page);
lv_obj_set_style_text_font(label_wifi_name, &lv_font_montserrat_20, 0);
lv_label_set_text(label_wifi_name, wifi_name);
lv_obj_align(label_wifi_name, LV_ALIGN_TOP_MID, 0, 10);
// 创建密码输入框
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); // 显示光标
// 创建“连接按钮”
lv_obj_t *btn_connect = lv_btn_create(wifi_password_page);
lv_obj_align(btn_connect, LV_ALIGN_TOP_LEFT, 170, 40);
lv_obj_set_width(btn_connect, 65); // 宽度
lv_obj_add_event_cb(btn_connect, btn_connect_cb, LV_EVENT_ALL, NULL); // 事件处理函数
lv_obj_t *label_ok = lv_label_create(btn_connect);
lv_label_set_text(label_ok, "OK");
lv_obj_set_style_text_font(label_ok, &lv_font_montserrat_20, 0);
lv_obj_center(label_ok);
// 创建“删除按钮”
lv_obj_t *btn_del = lv_btn_create(wifi_password_page);
lv_obj_align(btn_del, LV_ALIGN_TOP_LEFT, 245, 40);
lv_obj_set_width(btn_del, 65); // 宽度
lv_obj_add_event_cb(btn_del, btn_del_cb, LV_EVENT_ALL, NULL); // 事件处理函数
lv_obj_t *label_del = lv_label_create(btn_del);
lv_label_set_text(label_del, LV_SYMBOL_BACKSPACE);
lv_obj_set_style_text_font(label_del, &lv_font_montserrat_20, 0);
lv_obj_center(label_del);
// 创建roller样式
static lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_color_black());
lv_style_set_text_color(&style, lv_color_white());
lv_style_set_border_width(&style, 0);
lv_style_set_pad_all(&style, 0);
lv_style_set_radius(&style, 0);
// 创建"数字"roller
const char * opts_num = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9";
roller_num = lv_roller_create(wifi_password_page);
lv_obj_add_style(roller_num, &style, 0);
lv_obj_set_style_bg_opa(roller_num, LV_OPA_50, LV_PART_SELECTED);
lv_roller_set_options(roller_num, opts_num, LV_ROLLER_MODE_INFINITE);
lv_roller_set_visible_row_count(roller_num, 3); // 显示3行
lv_roller_set_selected(roller_num, 5, LV_ANIM_OFF); // 默认选择
lv_obj_set_width(roller_num, 90);
lv_obj_set_style_text_font(roller_num, &lv_font_montserrat_20, 0);
lv_obj_align(roller_num, LV_ALIGN_BOTTOM_LEFT, 15, -53);
lv_obj_add_event_cb(roller_num, mask_event_cb, LV_EVENT_ALL, NULL); // 事件处理函数
// 创建"数字"roller 的确认键
lv_obj_t *btn_num_ok = lv_btn_create(wifi_password_page);
lv_obj_align(btn_num_ok, LV_ALIGN_BOTTOM_LEFT, 15, -10); // 位置
lv_obj_set_width(btn_num_ok, 90); // 宽度
lv_obj_add_event_cb(btn_num_ok, btn_num_cb, LV_EVENT_ALL, NULL); // 事件处理函数
lv_obj_t *label_num_ok = lv_label_create(btn_num_ok);
lv_label_set_text(label_num_ok, LV_SYMBOL_OK);
lv_obj_set_style_text_font(label_num_ok, &lv_font_montserrat_20, 0);
lv_obj_center(label_num_ok);
// 创建"小写字母"roller
const char * opts_letter_low = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz";
roller_letter_low = lv_roller_create(wifi_password_page);
lv_obj_add_style(roller_letter_low, &style, 0);
lv_obj_set_style_bg_opa(roller_letter_low, LV_OPA_50, LV_PART_SELECTED); // 设置选中项的透明度
lv_roller_set_options(roller_letter_low, opts_letter_low, LV_ROLLER_MODE_INFINITE); // 循环滚动模式
lv_roller_set_visible_row_count(roller_letter_low, 3);
lv_roller_set_selected(roller_letter_low, 15, LV_ANIM_OFF); //
lv_obj_set_width(roller_letter_low, 90);
lv_obj_set_style_text_font(roller_letter_low, &lv_font_montserrat_20, 0);
lv_obj_align(roller_letter_low, LV_ALIGN_BOTTOM_LEFT, 115, -53);
lv_obj_add_event_cb(roller_letter_low, mask_event_cb, LV_EVENT_ALL, NULL); // 事件处理函数
// 创建"小写字母"roller的确认键
lv_obj_t *btn_letter_low_ok = lv_btn_create(wifi_password_page);
lv_obj_align(btn_letter_low_ok, LV_ALIGN_BOTTOM_LEFT, 115, -10);
lv_obj_set_width(btn_letter_low_ok, 90); // 宽度
lv_obj_add_event_cb(btn_letter_low_ok, btn_letter_low_cb, LV_EVENT_ALL, NULL); // 事件处理函数
lv_obj_t *label_letter_low_ok = lv_label_create(btn_letter_low_ok);
lv_label_set_text(label_letter_low_ok, LV_SYMBOL_OK);
lv_obj_set_style_text_font(label_letter_low_ok, &lv_font_montserrat_20, 0);
lv_obj_center(label_letter_low_ok);
// 创建"大写字母"roller
const char * opts_letter_up = "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ";
roller_letter_up = lv_roller_create(wifi_password_page);
lv_obj_add_style(roller_letter_up, &style, 0);
lv_obj_set_style_bg_opa(roller_letter_up, LV_OPA_50, LV_PART_SELECTED); // 设置选中项的透明度
lv_roller_set_options(roller_letter_up, opts_letter_up, LV_ROLLER_MODE_INFINITE); // 循环滚动模式
lv_roller_set_visible_row_count(roller_letter_up, 3);
lv_roller_set_selected(roller_letter_up, 15, LV_ANIM_OFF);
lv_obj_set_width(roller_letter_up, 90);
lv_obj_set_style_text_font(roller_letter_up, &lv_font_montserrat_20, 0);
lv_obj_align(roller_letter_up, LV_ALIGN_BOTTOM_LEFT, 215, -53);
lv_obj_add_event_cb(roller_letter_up, mask_event_cb, LV_EVENT_ALL, NULL); // 事件处理函数
// 创建"大写字母"roller的确认键
lv_obj_t *btn_letter_up_ok = lv_btn_create(wifi_password_page);
lv_obj_align(btn_letter_up_ok, LV_ALIGN_BOTTOM_LEFT, 215, -10);
lv_obj_set_width(btn_letter_up_ok, 90);
lv_obj_add_event_cb(btn_letter_up_ok, btn_letter_up_cb, LV_EVENT_ALL, NULL); // 事件处理函数
lv_obj_t *label_letter_up_ok = lv_label_create(btn_letter_up_ok);
lv_label_set_text(label_letter_up_ok, LV_SYMBOL_OK);
lv_obj_set_style_text_font(label_letter_up_ok, &lv_font_montserrat_20, 0);
lv_obj_center(label_letter_up_ok);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
这个函数中,主要是创建密码输入界面,以及创建面板上各个控件的事件处理函数。
显示的界面看开发板液晶屏实际效果。
创建的事件处理函数有这些:
mask_event_cb()
:这个函数是 roller 的事件处理函数,只是为了 roller 的显示效果。
btn_num_cb()
:这个函数是数字 roller 下面的确认键(√)的事件处理函数,每按一次,进入一次这个函数。函数中,会把数字 roller 当前的数字输入到密码输入框。
btn_letter_low_cb()
:这个函数是小写字母 roller 下面的确认键(√)的事件处理函数,每按一次,进入一次这个函数。函数中,会把小写字母 roller 当前的数字输入到密码输入框。
btn_letter_up_cb()
:这个函数是大写字母 roller 下面的确认键(√)的事件处理函数,每按一次,进入一次这个函数。函数中,会把大写字母 roller 当前的数字输入到密码输入框。
btn_connect_cb()
:这个函数是 OK 按键的事件处理函数,用于 wifi 连接。
btn_del_cb()
:这个函数是删除按键的事件处理函数,每按一次,删除一个密码输入框的符号。
btn_back_cb()
:这个函数是返回按键的事件处理函数,这个函数会删除密码输入界面,进入 wifi 列表界面。
以上函数就不对源码做介绍了。
12.3 例程制作过程
本章例程,复制第 11 章的【08-lcd_lvgl】例程后修改而来。 本章例程,参考了 IDF 例程中的 wifi scan 和 wifi station 例程。
Wifi scan 例程路径:\examples\wifi\scan Wifi station 例程路径:\examples\wifi\getting_started\station
scan 例程实现了扫描附近的 WLAN 信息,station 例程实现了连接到指定的 WLAN。
这两个官方例程,结果都是在终端显示,没有加入液晶屏显示。
我们将 scan 和 station 例程结合起来,并将其可视化显示到开发板的液晶屏上。
在第 11 章,我们已经做好了 lvgl 的接口,并且演示了 lvgl 的几个 demo。本章,我们自己做几个 lvgl 的页面,用来显示 WLAN 信息,以及配置密码等。
12.3.1 中文字库制作与使用
前面小节提到,因为我们扫描到的 wifi 名称,有可能带中文字符,所以需要做中文字库。
免费商用的阿里普惠字体,可以从下面的网站下载到。
阿里巴巴字体网站:https://www.alibabafonts.com/
里面针对字体粗细有好几个版本,我下载的是 Alibaba PuHuiTi 3.0 - 45 Light。
字体准备好以后,打开 lvgl 在线字库制作网站:
lvgl 字体制作网站:https://lvgl.io/tools/fontconverter
如上图所示,名称(Name)自己自定义一个,只要满足 C 语言变量定义要求就行。大小(Size)指的是像素大小,这里我填的是 20,20 像素大小的字体,在开发板的液晶屏上看起来刚刚好。Bpp 表示每个像素点多少个位,这里有 1、2、4、8 四个选项,数字越大,字体显示效果越好,但是生成的字库体积越大,综合考虑,我填了 4。Fallback 表示“如果字库里面找不到需要显示的字符,用哪个字库代替”,这里不需要,所以空着不要填,你看到的灰色不是我填进去的,是系统默认显示的空字符。Output format 输出格式,选择 C 文件。后面 3 个不需要勾选。
点击 Browse 选择刚才下载好的 ttf 字体文件。
在 Range 一栏输入:0x20-0x2FA1F。这个范围,包含了全世界的所有文字的 Unicode 码。
Unicode 字符范围查询网站:https://jrgraphix.net/research/unicode.php
显示 wifi 名称用的函数是 lv_list_add_btn()
这个函数,它可以指定一个 ICON 的同时,显示文字。这里要显示的文字就是 wifi 名称,指定的 ICON 就是 LV_SYMBOL_WIFI 图标。阿里普惠字体中,并不包含这个图标,所以我们需要把这个图标也添加到我们要生成的字体文件中,这个符号是 awesome 字体。
awesome 字体的下载链接如下。
下载 For The Web 中的 Free 版本,在下载好的文件夹中有个 webfonts 文件夹,字体文件就在这里面。
我们点击 Include another font,就可以继续添加字体,打开如下图所示。
点击 Browse 选择刚才下载好的 fa-soild-900.ttf 文件,Range 填入 0xf1eb,这个就是 wifi 图标的 Unicode 码。图标的 Unicode 码,可以在图标详情页面看到,如下图所示:
最后点击提交按钮,因为字库内容比较多,需要等待一段时间,字库文件才能做好,做好后浏览器自动下载。
字体制作过程中,如果浏览器提示“页面无响应,是否等待”,选择等待。
下载的文件名称,就是你刚才定义的 Name.c 文件,我这里就是 font_alipuhui20.c。
到这里,字库文件就制作好了,下面看怎么使用。
把字库文件复制粘贴到 main 文件夹下。
在 VSCode 软件中,点击打开它。文件源文件最前面关于怎么包含头文件 lvgl.h 的条件编译已经删除,修改为直接包含 lvgl.h 文件。
原来的:
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
2
3
4
5
修改后的:
#include "lvgl.h"
在 main 下面的 CMakeLists.txt 文件里面把字体文件添加进去。
idf_component_register(SRCS "font_alipuhui20.c" 后面省略...
使用这个字体,还需要在使用字体的文件中,声明一下。
LV_FONT_DECLARE(font_alipuhui20);
然后就可以写程序使用了,使用 lv_obj_set_style_text_font()
函数指定字体,使用 lv_label_set_text()
函数设置显示的文字内容。
12.3.2 添加分区表
在上一个小节中,简单介绍了一下分区表。
在 main 文件夹下,新建 partitions.csv 文件,然后复制下面内容填入。
# 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,
2
3
4
5
这个是从默认的分区表修改而来,只把 factory 大小增加到了 7M,因为字库文件就需要占用大概 3M 左右,这里的大小,只要比编译后的 bin 文件大小大就可以。
12.3.3 添加应用程序文件
在 main 文件夹下,添加 app_ui.c 和 app_ui.h 两个文件,用来存放 lvgl 制作的应用程序界面。并且把 app_ui.c 文件名称添加到 main 下面的 CMakeLists.txt 文件中,如下所示:
idf_component_register(SRCS "app_ui.c" "esp32_s3_szp.c" "main.c" "font_alipuhui20.c"
INCLUDE_DIRS ".")
2
依据例程要实现的目标,我们首先要在液晶屏显示“WLAN 扫描中...”,扫描完成后显示扫描到的 wifi 列表。这两个使用一个页面实现,页面使用 obj,文字提示使用 label,列表使用 list。
除了显示页面之外,还需要把 wifi 扫描和 wifi 连接任务创建好。
如下代码所示:
// wifi连接
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连接任务
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
上面代码中,第 6~15 行创建页面 obj,第 17~20 行创建提示 label,第 24~26 行执行 wifi 扫描。扫描成功后,退出 wifi_scan()
函数,继续向下执行。第 30 行删除刚才的提示 label,第 32~ 45 行创建列表 list,并列出扫描到的 wifi 名称。
这里已经获取到了每个 wifi 名称的信号质量值 rssi,但是没有像手机一样在页面上通过 wifi 图标显示质量,感兴趣的可以自己做一下,通过 rssi 的范围,选择相应的 SYMBOL 即可完成。
第 50 行,创建 wifi 连接任务。wifi_connect()
任务会等待用户输入密码后才会正式连接。
wifi_scan()
函数和 wifi_connect()
函数,之前说过,是参考乐鑫官方的 wifi scan 和 wifi station 例程,稍微修改一下就能用。
第 49 行,创建了一个队列,队列消息存放 wifi 账号和密码,wifi_connect()
函数收到这个队列消息,就会进行 wifi 连接。
第 44 行,添加了 list 的事件处理函数 list_btn_cb()
,用户点击 wifi 名称后进入这个函数。这个函数会再创建一个新的页面,在页面上添加各个按钮和密码输入框。添加各种控件的同时,也会创建对应的事件处理函数。具体的代码,大家看例程源码,没有什么难度,一看就懂,这里就不说了。