ESP-IDF学习笔记-WIFI连接
使用ESP32的WIFI需要使用3个库的API,涉及NVS_FLASH,ESP_NETIF,ESP_WIFI,ESP_EVENT。nvs保存配置,netif提供tcp/ip操作接口,wifi库提供wiif的配置接口。
首先提供一下官方文档:
还有描述整个WiFi编程结构的指南(非常关键的指南,放在了API指南里,要不是我搜一个函数我还找不到):
一,配置步骤概览
初始化NVS
NVS(非易失存储库)是ESP32分区中用来储存少量需要掉电保存的数据的分区(如果数据量很大,需要储存在文件系统的分区中)。在WIFI的配置中,该区存储了WIFI的设置,包扩上次连接的WIFI信息,上次开启的热点信息等配置信息(在设置wifi时自动储存)。因此,为了实现WiFi记忆功能,在使用WIFI前,需要先初始化NVS。如果不初始化,需要在每次运行时重新配置WiFi。
创建ESP-NETIF工作
ESP-NETIF库**提供了tcp/ip操作的相关接口(Lwip)**,与wifi驱动接口绑定后,可以处理tcp/ip的各种事务,包括DHCP等各种操作。为了使我们的WIFI能够正常联网,我们需要创建一个ESP-NETIF工作。
设置WIFI
WIFI库提供了操作WIFI的驱动,包括设置站点模式,配置WiFi信息,连接WIFI等操作。
事件循环
事件循环是ESPIDF提供的用于不同组件交流操作的库。一个组件可以创建事件,另外一个组件可以通过注册事件函数响应相应的事件。通过事件循环,各个库可以协同运行。其典型例子就是使用与WIFI库有关的组件时,比如使用DHCP获取IP地址,需要在WIFI库成功连接到WIFI之后,而DHCP的实现是高层库做的,因此就需要通过事件来通知其他组件WIFI已经连接,DHCP库通过将自己的函数注册成相应事件的处理函数,就可以实现协同运行。
二,具体配置流程
初始化NVS
首先在设置菜单中将WIFI选项中的WiFi NVS flash
选中,使能WiFi_NVS_FLASH。让wifi设置能保存在NVS中。
这里直接给出代码,感兴趣可以看官方文档。
1 |
|
初始化ESP-NETIF和创建事件循环
首先调用函数初始化netif。
1 | esp_err_t esp_netif_init(void) |
这个函数在应用中需要被调用一次。(This function should be called exactly once from application code, when the application starts up.)
然后创建默认事件循环。
1 | esp_err_t esp_event_loop_create_default(void); |
该函数创建了一个默认参数的事件循环,开始处理各个组件的事件。默认事件循环是一个特殊的,包含了各种系统事件处理(如wifi事件)。默认循环使用的事件注册API与用户的事件循环有一点不同,以下使用的API都是默认循环的API。如果想知道用户API是什么,可以参考官方手册。
创建完成后,需要将netif的处理函数放入对应的事件中,比如在STA开启后自动开启DHCP客户端获取IP。
这里NETIF库已经写好了默认配置,直接调用函数即可。
1 | esp_netif_t *esp_netif_create_default_wifi_sta(void);//sta模式 |
同样,如果开启了AP模式,则调用下面的函数。
1 | esp_netif_t *esp_netif_create_default_wifi_ap(void); |
sta/ap模式需要同时调用两个函数。
使用例子:
1 |
|
注册处理函数
在事件循环中加入了netif中默认的处理函数后,我们还需要注册自己的处理函数,用来实现一些功能,比如在启动wifi后开始连接ap,或者在断开连接后进行重连等。
一个事件处理函数的模板如下:
1 | /* |
创建好处理函数后,使用以下函数进行注册:
1 | esp_err_t esp_event_handler_instance_register(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg, esp_event_handler_instance_t *instance); |
参数:
- event_base – [in] 基事件
- event_id – [in] 具体事件id
- event_handler – [in] 要注册的处理函数
- event_handler_arg – [in] 要传递参数的指针,该函数不保留备份,因此需要确保其在被使用时有效
- instance – [out] 输出的句柄,可以用来删除(unregister)这个事件函数,如果不需要可以填NULL。
同时,如果不需要句柄,调用以下函数即可:
1 | esp_err_t esp_event_handler_register(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg); |
上面两个函数在底层都调用了同一个函数进行注册,因此功能是一样的。
注意,对于event_base
存在一个ESP_EVENT_ANY_BASE
匹配全部的基事件;对于event_id
存在ESP_EVENT_ANY_ID
对应基事件的全部事件。即,ESP_EVENT_ANY_BASE
+ESP_EVENT_ANY_ID
==全部事件都响应。
对于同一事件多个处理函数,遵守先注册先调用,后注册后调用原则。(参考)
由于该规则,自己的事件处理函数注册必须要在esp_netif_create_default_wifi_sta()
之后。
esp_netif_create_default_wifi_sta()
中注册了该组件的事件函数,为保证用户正常上网,该事件函数需在用户函数之前执行。
一部分WiFi相关事件在后文附上,也可以直接参考官方手册
示例,这里来自官方例程的一部分,调用的FreeRTOS的eventGroupAPI通知IP事件的完成。
1 |
|
设置WiFi
处理好上层要响应的事件后,可以开始配置底层的WiFi驱动。
首先初始化WiFi驱动的资源和参数:
1 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//获取WIFI默认配置。这样可以给每个值都赋上初值 |
使用WIFI_INIT_CONFIG_DEFAULT()
获得每个变量的初值,这样可以防止某个参数忘记或设错导致的初始化失败。
在WiFi库的API被调用之前,esp_wifi_init()
必须被调用。
之后设置WiFi的模式和相应模式的配置:
1 | esp_err_t esp_wifi_set_mode(wifi_mode_t mode);//设置模式 |
mode有以下几种:
- WIFI_MODE_STA:sta模式
- WIFI_MODE_AP:ap模式
- WIFI_MODE_APSTA:sta/ap模式
默认模式是STA。
设置相应模式的参数
1 | esp_err_t esp_wifi_set_config(wifi_interface_t interface, wifi_config_t *conf); |
其中wifi_interface_t
如下:
1 | typedef enum { |
wifi_config_t
结构体定义如下:
1 | typedef union { |
这是一个联合,设置ap和sta时分别设置不同的部分。
对于wifi_sta_config_t
需要关注的成员如下:
成员名 | 类型 | 作用 |
---|---|---|
ssid[32] | uint8_t | WiFi的SSID |
password[64] | uint8_t | WiFi的密码 |
scan_method | wifi_scan_method_t | 选择扫描模式 |
bssid_set | bool | 1:检查AP的MAC 0:不检查 |
bssid | uint8_t | 目的AP的MAC |
channel | uint8_t | 0:通道未知 ;1-13 |
sort_method | wifi_sort_method_t | 扫描排序顺序设置 |
threshold | wifi_scan_threshold_t | 安全性强于或信号强度强于该设置的AP可被使用 |
wifi_scan_method_t
:
WIFI_FAST_SCAN
快速扫描模式,找到匹配的SSID后停止WIFI_ALL_CHANNEL_SCAN
扫描完全部通道为止
wifi_sort_method_t
:
WIFI_CONNECT_AP_BY_SIGNAL
按信号强弱整理WIFI_CONNECT_AP_BY_SECURITY
按加密等级整理
wifi_scan_threshold_t
:
int8_t rssi
信号的强度wifi_auth_mode_t authmode
最低信号的认证方式,如果想连接开放WiFi及以上,需设置为WIFI_AUTH_OPEN
wifi_auth_mode_t
:
1 | typedef enum { |
一个最小设置的例子:
1 | wifi_config_t wifi_config = { |
对于wifi_ap_config_t
需要关注的成员如下:
成员 | 类型 | 作用 |
---|---|---|
ssid[32] | uint8_t | SSID |
password[64] | uint8_t | 密码 |
ssid_len | uint8_t | strlen(WIFI_SSID) |
channel | uint8_t | 选择通道 |
authmode | wifi_auth_mode_t | 选择认证方式 |
ssid_hidden | uint8_t | 是否广播SSID(SSID是否可见) |
max_connection | uint8_t | 最大连接数 |
一个最小的config如下:
1 | wifi_config_t wifi_config = { |
如果开启了nvs,esp_wifi_set_config()
的设置会被储存在nvs中,下次不用配置,可以使用esp_wifi_get_config()
获取设置。
当一切都配置好后,可以开启WiFi:
1 | esp_err_t esp_wifi_start(void); |
开启后,可以在事件循环中使用esp_err_t esp_wifi_connect(void)
去连接WiFi,参考上面事件函数中的代码。
对于已经连接的AP,使用以下API获得信息:
1 | esp_err_t esp_wifi_sta_get_ap_info(wifi_ap_record_t *ap_info); |
注意:在获得IP前,禁止一切socket操作。
三,扫描WiFi
在通常的应用中,我们不可能将WiFi信息固定。因此,在连接WiFi前先扫描WiFi是十分必要的,扫描仅在sta或sta/ap模式使用。
扫描使用以下API:
1 | esp_err_t esp_wifi_scan_start(const wifi_scan_config_t *config, bool block); |
其中,block
用来确定是否阻塞式扫描。如果为1,将会阻塞直到扫描完成。否则,会立刻返回,结果一般在事件循环中处理。
config
用来配置扫描设置,具有以下字段:
uint8_t *ssid
:AP的SSID,如果不为NULL,则仅扫描相同NULLuint8_t *bssid
:AP的MAC,如果不为NULL,则仅扫描相同MACuint8_t channel
:如果该字段值为 0,将进行全信道扫描;反之,将针对特定信道进行扫描。bool show_hidden
:如果该字段值为 0,本次扫描将忽略具有隐藏 SSID 的 AP;反之,这些 AP 也会在扫描时被视为正常 AP。wifi_scan_type_t scan_type
:WIFI_SCAN_TYPE_ACTIVE主动扫描; WIFI_SCAN_TYPE_PASSIVE 被动扫描wifi_scan_time_t scan_time
:用来控制扫描时间,不用关心。
将该参数设为NULL则使用默认扫描。
该函数扫描出的结果会储存在内存中,直到调用esp_wifi_scan_get_ap_records()
或者esp_err_t esp_wifi_clear_ap_list(void)
释放。
主动扫描:通过发送 probe request 进行扫描。该模式为默认的扫描模式。
被动扫描:不发送 probe request。跳至某一特定信道并等待 beacon。应用程序可通过 wifi_scan_config_t 中的 scan_type 字段使能被动扫描。
调用 API sp_wifi_set_config()
可全局配置一些扫描属性,请参阅 station的配置。
如果国家信息有误,调用函数 esp_wifi_set_country() 进行配置。
当全部扫描完成后,会产生WIFI_EVENT_SCAN_DONE
事件,在该事件函数中,使用以下API获得扫描得到的AP数量:
1 | esp_err_t esp_wifi_scan_get_ap_num(uint16_t *number); |
获得AP数量后,使用以下API获得具体信息:
1 | esp_err_t esp_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records); |
参数:
number
:该参数是一个输入输出参数。但作为输入参数,它指示了ap_records
最多储存的数量。作为输出参数,它则与esp_err_t esp_wifi_scan_get_ap_num(uint16_t *number);
输出一样。ap_records
:储存扫描到AP信息的参数
ap_records
:
1 | typedef struct { |
一个例子:
1 |
|
附录-WiFi事件描述
该部分来自官方文档,在这里做参考。以下为具体事件名,头两个单词同时也是基事件名。
WIFI_EVENT_WIFI_READY
Wi-Fi 驱动程序永远不会生成此事件,因此,应用程序的事件回调函数可忽略此事件。在未来的版本中,此事件可能会被移除。WIFI_EVENT_SCAN_DONE
扫描完成事件,由 esp_wifi_scan_start() 函数触发,将在以下情况下产生:扫描已完成,例如:Wi-Fi 已成功找到目标 AP 或已扫描所有信道。
当前扫描因函数 esp_wifi_scan_stop() 而终止。
在当前扫描完成之前调用了函数 esp_wifi_scan_start()。此时,新的扫描将覆盖当前扫描过程,并生成一个扫描完成事件。
以下情况下将不会产生扫描完成事件:
当前扫描被阻止。
当前扫描是由函数 esp_wifi_connect() 触发的。
接收到此事件后,事件任务暂不做任何响应。首先,应用程序的事件回调函数需调用 esp_wifi_scan_get_ap_num() 和 esp_wifi_scan_get_ap_records() 获取已扫描的 AP 列表,然后触发 Wi-Fi 驱动程序释放在扫描过程中占用的内存空间(切记该步骤)。 更多详细信息,请参阅 ESP32-S3 Wi-Fi 扫描。
WIFI_EVENT_STA_START
如果调用函数 esp_wifi_start() 后接收到返回值 ESP_OK,且当前 Wi-Fi 处于 station 或 station/AP 共存模式,则将产生此事件。接收到此事件后,事件任务将初始化 LwIP 网络接口 (netif)。通常,应用程序的事件回调函数需调用 esp_wifi_connect() 来连接已配置的 AP。WIFI_EVENT_STA_STOP
如果调用函数 esp_wifi_stop() 后接收到返回值 ESP_OK,且当前 Wi-Fi 处于 station 或 station/AP 共存模式,则将产生此事件。接收到此事件后,事件任务将进行释放 station IP 地址、终止 DHCP 客户端服务、移除 TCP/UDP 相关连接并清除 LwIP station netif 等动作。此时,应用程序的事件回调函数通常不需做任何响应。WIFI_EVENT_STA_CONNECTED
如果调用函数 esp_wifi_connect() 后接收到返回值 ESP_OK,且 station 已成功连接目标 AP,则将产生此连接事件。接收到此事件后,事件任务将启动 DHCP 客户端服务并开始获取 IP 地址。此时,Wi-Fi 驱动程序已准备就绪,可发送和接收数据。如果您的应用程序不依赖于 LwIP(即 IP 地址),则此刻便可以开始应用程序开发工作。但是,如果您的应用程序需基于 LwIP 进行,则还需等待 got ip 事件发生后才可开始。WIFI_EVENT_STA_DISCONNECTED
此事件将在以下情况下产生: 1. 调用了函数 esp_wifi_disconnect() 或 esp_wifi_stop(),且 Wi-Fi station 已成功连接至 AP。
2. 调用了函数 esp_wifi_connect(),但 Wi-Fi 驱动程序因为某些原因未能成功连接至 AP,例如:未扫描到目标 AP、验证超时等。 或存在多个 SSID 相同的 AP,station 无法连接所有已找到的 AP,也将产生该事件。
3. Wi-Fi 连接因为某些原因而中断,例如:station 连续多次丢失 N beacon、AP 踢掉 station、AP 认证模式改变等。
接收到此事件后,事件任务的默认动作为:
1. 关闭 station 的 LwIP netif。
2. 通知 LwIP 任务清除导致所有套接字状态错误的 UDP/TCP 连接。针对基于套接字编写的应用程序,其回调函数可以在接收到此 事件时(如有必要)关闭并重新创建所有套接字。
应用程序处理此事件最常用的方法为:调用函数 esp_wifi_connect() 重新连接 Wi-Fi。但是,如果此事件是由函数 esp_wifi_disconnect() 引发的,则应用程序不应调用 esp_wifi_connect() 来重新连接。应用程序须明确区分此事件的引发原因,因为某些情况下应使用其它更好的方式进行重新连接。请参阅 Wi-Fi 重新连接 和 连接 Wi-Fi 时扫描。
需要注意的另一点是:接收到此事件后,LwIP 的默认动作是终止所有 TCP 套接字连接。大多数情况下,该动作不会造成影响。但对某些特殊应用程序可能除外。例如:
应用程序创建一个了 TCP 连接,以维护每 60 秒发送一次的应用程序级、保持活动状态的数据。
由于某些原因,Wi-Fi 连接被切断并引发了 WIFI_EVENT_STA_DISCONNECTED 事件。根据当前实现,此时所有 TCP 连接都将被移除,且保持活动的套接字将处于错误的状态中。但是,由于应用程序设计者认为网络层 不应 考虑这个 Wi-Fi 层的错误,因此应用程序不会关闭套接字。
5 秒后,因为在应用程序的事件回调函数中调用了 esp_wifi_connect(),Wi-Fi 连接恢复。同时,station 连接至同一个 AP 并获得与之前相同的 IPV4 地址。
60 秒后,当应用程序发送具有保持活动状态的套接字的数据时,套接字将返回错误,应用程序将关闭套接字并在必要时重新创建。
在上述场景中,理想状态下应用程序套接字和网络层将不会受到影响,因为在此过程中 Wi-Fi 连接只是短暂地断开然后快速恢复。应用程序可通过 LwIP menuconfig 启动“IP 改变时保持 TCP 连接”的功能。
IP_EVENT_STA_GOT_IP
当 DHCP 客户端成功从 DHCP 服务器获取 IPV4 地址或 IPV4 地址发生改变时,将引发此事件。此事件意味着应用程序一切就绪,可以开始任务(如:创建套接字)。IPV4 地址可能由于以下原因而发生改变:
1. DHCP 客户端无法重新获取/绑定 IPV4 地址,且 station 的 IPV4 重置为 0。
2. DHCP 客户端重新绑定了其它地址。
3. 静态配置的 IPV4 地址已发生改变。
函数 ip_event_got_ip_t 中的字段 ip_change 说明了 IPV4 地址是否发生改变。
套接字的状态是基于 IPV4 地址的,这意味着,如果 IPV4 地址发生改变,则所有与此 IPV4 相关的套接字都将变为异常。接收到此事件后,应用程序需关闭所有套接字,并在 IPV4 变为有效地址时重新创建应用程序。
IP_EVENT_GOT_IP6
当 IPV6 SLAAC 支持自动为 ESP32-S3 配置一个地址,或 ESP32-S3 地址发生改变时,将引发此事件。此事件意味着应用程序一切就绪,可以开始任务(如:创建套接字)。IP_EVENT_STA_LOST_IP
当 IPV4 地址失效时,将引发此事件。此事件不会在 Wi-Fi 断连后立刻出现。Wi-Fi 连接断开后,首先将启动一个 IPV4 地址丢失计时器,如果 station 在该计时器超时之前成功获取了 IPV4 地址,则不会发生此事件。否则,此事件将在计时器超时时发生。
一般来说,应用程序可忽略此事件。这只是一个调试事件,主要使应用程序获知 IPV4 地址已丢失。
WIFI_EVENT_AP_START
与 WIFI_EVENT_STA_START 事件相似。WIFI_EVENT_AP_STOP
与 WIFI_EVENT_STA_STOP 事件相似。WIFI_EVENT_AP_STACONNECTED
每当有一个 station 成功连接 ESP32-S3 AP 时,将引发此事件。接收到此事件后,事件任务将不做任何响应,应用程序的回调函数也可忽略这一事件。但是,您可以在此时进行一些操作,例如:获取已连接 station 的信息等。WIFI_EVENT_AP_STADISCONNECTED
此事件将在以下情况下发生: 1. 应用程序通过调用函数 esp_wifi_disconnect() 或 esp_wifi_deauth_sta() 手动断开 station 连接。
2. Wi-Fi 驱动程序出于某些原因断开 station 连接,例如:AP 在过去 5 分钟(可通过函数 esp_wifi_set_inactive_time() 修改该时间)内未接收到任何数据包等。
3. station 断开与 AP 之间的连接。
发生此事件时,事件任务将不做任何响应,但应用程序的事件回调函数需执行一些操作,例如:关闭与此 station 相关的套接字等。
WIFI_EVENT_AP_PROBEREQRECVED
默认情况下,此事件处于禁用状态,应用程序可以通过调用 API esp_wifi_set_event_mask() 启用。 启用后,每当 AP 接收到 probe request 时都将引发此事件。WIFI_EVENT_STA_BEACON_TIMEOUT
如果 station 在 inactive 时间内未收到所连接 AP 的 beacon,将发生 beacon 超时,将引发此事件。inactive 时间通过调用函数 esp_wifi_set_inactive_time() 设置。WIFI_EVENT_CONNECTIONLESS_MODULE_WAKE_INTERVAL_START
非连接模块在 Interval 开始时触发此事件。 请参考 非连接模块功耗管理 。