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

【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支持以下扫描模式:

  1. 主动扫描。通过发送 probe request 进行扫描。
  2. 被动扫描。不发送 probe request,而是跳至某一特定信道并等待 beacon
  3. 前端扫描。station 模式下 Wi-Fi 未连接时,可进行前端扫描。
  4. 后端扫描。station 模式或 station/AP 共存模式下 Wi-Fi 已连接时,可进行后端扫描。
  5. 全信道扫描。扫描所有信道。
  6. 特定信道扫描。仅扫描特定的信道。

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是按信号强度排列的,所以一般来说取出前几个就基本满足需求了。


http://www.kler.cn/a/500646.html

相关文章:

  • 计算机网络之---网络层的基本功能
  • [创业之路-242]:《华为双向指挥系统》-1-组织再造-企业普遍采用的5种组织结构形式
  • 用vscode写latex-1
  • UML系列之Rational Rose笔记三:活动图(泳道图)
  • iOS实际开发中使用Alamofire实现多文件上传(以个人相册为例)
  • Kafka 主题管理
  • Java面试核心知识4
  • 系统架构设计师考点—信息安全和网络安全
  • 若依前后端分离项目部署
  • Vite源码学习分享(一)
  • 阿里巴巴新零售模式下的创新实践:结合开源AI智能名片2+1链动模式S2B2C商城小程序的应用探索
  • 公众号如何通过openid获取unionid
  • 探索 C++ Insights: 理解编译器背后的 C++ 实现
  • 基于Python实现的通用小规模搜索引擎
  • Scala语言的计算机基础
  • vue2+element侧边栏menu图片自定义设置
  • 记一次sealos部署k8s集群之delete了第一台master如何恢复
  • 【redis初阶】浅谈分布式系统
  • piexl 手机刷装机包,以及使用面具root手机
  • [读书日志]从零开始学习Chisel 第十二篇:Scala的抽象成员(敏捷硬件开发语言Chisel与数字系统设计)
  • 攻防靶场(34):隐蔽的计划任务提权 Funbox1
  • 青少年编程与数学 02-006 前端开发框架VUE 20课题、工具链
  • 39_Lua选择结构语句
  • Pathview包:整合表达谱数据可视化KEGG通路