【ESP32】ESP-IDF开发 | WiFi开发 | 基站模式 + AP扫描和AP连接例程
1. 简介
1.1 硬件规格
ESP32带有一个2.4GHz的射频发射器和接收器;内部带有WiFi MAC协议栈,支持IEEE 802.11b/g/n标准,即现在所说的WiFi1/2/3标准;数据速率最高可达150MHz。
1.2 ESP-NETIF
这个是ESP-IDF独有的一个TCP/IP协议栈抽象层,与Linux系统下的netif组件类似。用户可以基于ESP-NETIF实现不同的TCP/IP协议栈,ESP-IDF默认实现的是经典的lwIP TCP/IP协议栈。
对于日常的开发,了解这个组件的基本功能即可。
1.3 非易失性储存库(NVS)
ESP32上的非易失性存储 (NVS) 库主要用于在flash中存储键值格式的数据,NVS的其中一个分区会被用来WiFi协议栈的相关运行参数。一般的WiFi应用,我们只需要学会初始化NVS即可,相关的增删查改是由ESP-IDF完成的。
2. 基站模式
2.1 代码流程
基站(station)模式是我们日常用得最多的,设备通过该模式连接到因特网中。
一般WiFi基站的编程可以简化成以下:
1. 初始化阶段
- 调用esp_event_loop_create() 创建一个系统事件任务,并初始化应用程序事件的回调函数;
- 调用esp_netif_init() 创建一个 LwIP 核心任务,并初始化 LwIP 相关工作;
- 调用esp_netif_create_default_wifi_sta() 创建有TCP/IP堆栈的默认网络接口实例绑定station;
- 调用函数esp_wifi_init() 创建 Wi-Fi 驱动程序任务,并初始化 Wi-Fi 驱动程序;
2. 配置阶段
-
调用函数esp_wifi_set_mode() 将 Wi-Fi 模式配置为 station 模式;
-
调用esp_wifi_set_protocol() 设置 WiFi 支持的协议(可选,默认是全开);
-
调用esp_wifi_set_bandwidth() 设置 WiFi 支持的带宽(可选);
3. 启动阶段
- 调用esp_wifi_start() 启动 Wi-Fi 驱动程序;
- 应用程序的事件回调函数将事件WIFI_EVENT_STA_START 中继到应用程序任务中。此时,推荐调用函数esp_wifi_connect() 进行 Wi-Fi 连接;
4. 连接阶段
- 调用函数esp_wifi_connect() 后,Wi-Fi 驱动程序将启动内部扫描/连接过程;
-
如果内部扫描/连接过程成功,将产生WIFI_EVENT_STA_CONNECTED 事件。然后,事件任务将启动 DHCP 客户端服务,最终触发 DHCP 程序;
5. 获取IP阶段
-
如果 Wi-Fi 成功从 DHCP 服务器接收到 IP 地址,则将引发IP_EVENT_STA_GOT_IP 事件,在此之后用户就可以创建TCP/UDP套接字,执行业务逻辑。
2.2 例程
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include <string.h>
#define TAG "app"
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static EventGroupHandle_t wifi_event_group;
static int retry_num = 0;
static void event_handler(
void* arg,
esp_event_base_t event_base,
int32_t event_id,
void* event_data)
{
if (event_base == WIFI_EVENT) {
if (event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (retry_num++ < 3) {
esp_wifi_connect();
ESP_LOGI(TAG, "Try connecting to the AP, try %d", retry_num);
} else {
xEventGroupSetBits(wifi_event_group, WIFI_FAIL_BIT);
ESP_LOGI(TAG, "connect to the AP fail");
}
}
} else if (event_base == IP_EVENT) {
if (event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
retry_num = 0;
xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
}
}
}
int app_main()
{
/* 初始化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());
ESP_ERROR_CHECK(nvs_flash_init());
}
wifi_event_group = xEventGroupCreate();
/* 初始化WiFi协议栈 */
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
/* 注册事件处理 */
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));
/* 初始化WiFi基站模式 */
wifi_config_t wifi_config = {
.sta = {
.ssid = "your wifi name", // 填写WiFi名
.password = "your wifi password", // 填写密码
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
/* 等待连接完成 */
EventBits_t bits = xEventGroupWaitBits(
wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to AP");
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to AP");
}
return 0;
}
代码基本遵循上面的描述进行编写,这里对用户应用部分说明一下。
ESP的WiFi驱动把事件分成了几个组,用户可以选择注册哪个组下的哪些事件。我这里注册了WIFI_EVENT组的所有事件和IP_EVENT组下的IP获取事件。
我这里使用了EventGroup来等待WiFi连接成功,这个FreeRTOS的组件类似于寄存器的标志位。我设置了WIFI_CONNECTED_BIT和WIFI_FAIL_BIT两个标志位,分别代表WiFi连接成功和WiFi连接失败;只要两个标志位的其中一个置1程序就能继续执行。之后就可以写其他的业务代码了。
WiFi相关的log比较多,下面展示最后的几行。
3. AP扫描
在开发一些终端设备时, 常常需要设计WiFi连接界面,其中就要求设备能扫描周围的WiFi节点。ESP32支持以下扫描模式:
- 主动扫描。通过发送 probe request 进行扫描。
- 被动扫描。不发送 probe request,而是跳至某一特定信道并等待 beacon。
- 前端扫描。在 station 模式下 Wi-Fi 未连接时,可进行前端扫描。
- 后端扫描。在 station 模式或 station/AP 共存模式下 Wi-Fi 已连接时,可进行后端扫描。
- 全信道扫描。扫描所有信道。
- 特定信道扫描。仅扫描特定的信道。
3.1 例程
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include <string.h>
#define TAG "app"
static wifi_ap_record_t ap_info[64];
int app_main()
{
/* 初始化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());
ESP_ERROR_CHECK(nvs_flash_init());
}
/* 初始化WiFi协议栈 */
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
uint16_t number = 64;
uint16_t ap_count = 0;
esp_wifi_scan_start(NULL, true);
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info));
ESP_LOGI(TAG, "Total APs scanned = %u, actual AP number ap_info holds = %u", ap_count, number);
for (uint16_t i = 0; i < number; i++) {
ESP_LOGI(TAG, "%d. SSID: %s\t RSSI: %d\t Channel: %d", i, ap_info[i].ssid, ap_info[i].rssi, ap_info[i].primary);
}
return 0;
}
AP扫描的初始化流程和前面是差不多的,在调用esp_wifi_start后,调用esp_wifi_scan_start即可开始扫描,第一个参数可以传入一些扫描参数。
typedef struct {
uint8_t *ssid;
uint8_t *bssid;
uint8_t channel;
bool show_hidden;
wifi_scan_type_t scan_type;
wifi_scan_time_t scan_time;
uint8_t home_chan_dwell_time;
wifi_scan_channel_bitmap_t channel_bitmap;
} wifi_scan_config_t;
- ssid:仅扫描对应的SSID;
- bssid:仅扫描对应的MAC地址;
- channel:仅扫描对应信道;
- show_hidden:扫描隐藏SSID;
- scan_type:扫描模式;
- scan_time:扫描时间,可分别设置最小和最大扫描时间,默认扫描120ms;
- home_chen_dwell_time:在home信道和连续信道之间的扫描时间;
- channel_bitmap:信道遮罩,选择需要扫描的信道。
第二个参数指定是否阻塞,如果非阻塞的话就要像上面例程那样,通过注册回调函数来接收扫描结果。
esp_wifi_scan_get_ap_num函数获取一共扫描到的AP;esp_wifi_scan_get_ap_records函数获取AP信息,第一个参数传入要取出的数量,扫描到的AP是按信号强度排列的,所以一般来说取出前几个就基本满足需求了。