0


ESP32学习入门:WiFi连接网络

前言

为了开发一款亚马逊物联网产品,开始入手ESP32模块。为了能够记录自己的学习过程,特记录如下操作过程。


一、ESP32简单介绍

ESP32 是一套 Wi-Fi (2.4 GHz) 和蓝牙 (4.2) 双模解决方案,集成了高性能的 CPU 内核、超低功耗协处理器和丰富的外设。ESP32 采用 40 nm 工艺制成,具有最佳的功耗性能、射频性能、稳定性、通用性和可靠性,适用于各种应用和不同功耗需求。


二、ESP32 Wi-Fi模块介绍

乐鑫为用户提供完整的软、硬件资源进行 ESP32 设备的开发。乐鑫所研发的软件开发环境 ESP-IDF 能够帮助用户快速开发物联网 (IoT) 应用,满足用户对于 Wi-Fi、蓝牙、低功耗等性能的需求。

Wi-Fi 库支持配置及监控 ESP32 Wi-Fi 连网功能。

  • 基站模式(即 STA 模式或 Wi-Fi 客户端模式),此时 ESP32 连接到接入点 (AP)。
  • AP 模式(即 Soft-AP 模式或接入点模式),此时基站连接到 ESP32。
  • AP-STA 共存模式(ESP32 既是接入点,同时又作为基站连接到另外一个接入点)。
  • 上述模式的各种安全模式(WPA、WPA2 及 WEP 等)。
  • 扫描接入点(包括主动扫描及被动扫描)。
  • 使用混杂模式监控 IEEE802.11 Wi-Fi 数据包。

WiFi是在TCP/IP协议的基础之上实现的2.4GHz的一种通信方式,不同的实现标准对应的的频带和最大速率不一样。

标准

频率

最大速率

802.11b

2.4GHz

11Mbps

802.11a

5GHz

54Mbps

802.11g

2.4GHz

54Mbps

802.11n

2.4GHz,5GHz

450Mbps

802.11ac

5GHz

1300Mbps


三、ESP32 Wi-Fi 编程模型

Wi-Fi 驱动程序(WiFi Driver)可以看作是一个无法感知上层代码(如 TCP/IP 堆栈、应用程序任务、事件任务等)的黑匣子。通常,应用程序任务(Application task)负责调用 Wi-Fi 驱动程序 APIs 来初始化 Wi-Fi,并在必要时处理 Wi-Fi 事件。然后,Wi-Fi 驱动程序(WiFi Driver)接收并处理 API 数据,并在应用程序(Application task)中插入事件。

Wi-Fi 事件处理是在 esp_event 库 的基础上进行的。Wi-Fi 驱动程序(WiFi Driver)将事件发送至 默认事件循环,应用程序(Application task)便可以使用 esp_event_handler_register() 中的回调函数处理这些事件。除此之外,esp_netif 组件 也负责处理 Wi-Fi 事件,并产生一系列默认行为。例如,当 Wi-Fi station 连接至一个 AP 时,esp_netif 将自动开启 DHCP 客户端服务(系统默认)。

因此,整个WiFi的连接过程,其实是一个事件驱动和处理的过程。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_CONNECTED

如果调用函数 esp_wifi_connect() 后接收到返回值 ESP_OK,且 station 已成功连接目标 AP,则将产生此连接事件。接收到此事件后,事件任务将启动 DHCP 客户端服务并开始获取 IP 地址。此时,Wi-Fi 驱动程序已准备就绪,可发送和接收数据。如果您的应用程序不依赖于 LwIP(即 IP 地址),则此刻便可以开始应用程序开发工作。但是,如果您的应用程序需基于 LwIP 进行,则还需等待 got ip 事件发生后才可开始。

IP_EVENT_STA_GOT_IP

当 DHCP 客户端成功从 DHCP 服务器获取 IPV4 地址或 IPV4 地址发生改变时,将引发此事件。此事件意味着应用程序一切就绪,可以开始任务(如:创建套接字)。

具体这些事件什么时候触发,已经属于比较底层的原理。这里不做研究。我们只需要了解,WiFi驱动会触发那些事件,收到这些事件之后,应用程序做什么样的处理。


四、ESP32 Wi-Fi 事件处理流程


五、ESP32 Wi-Fi开发环境

进入网址:https://dl.espressif.cn/dl/esp-idf/,下载开发软件。界面如下:

推荐下载Espressif-IDE 2.6.0 with ESP-IDF v4.4.2,Windows 10, 11,Size: 1 GB,即代码编辑界面Espressif-IDE 2.6.0,并且带有ESP-IDF v4.4.2(Espressif IoT Development Framework,即乐鑫物联网开发框架)。有了Espressif-IDE 2.6.0 和ESP-IDF v4.4.2,就可以配置、编译、下载固件到开发板上。安装之后,有三个快捷方式,推荐采用IDE的方式。

打开软件之后,操作界面如下:

新建一个Espressif IDF项目

给项目命名:

使用模板wifi->getting started->station来初始化项目。

创建完项目后,界面如下。

不管三七二十一,先编译看下,是否通过。


六、ESP32 Wi-Fi具体代码

为了放置ESP32示例代码会更新,此处将所有代码贴上。

/* WiFi station Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.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"

/* The examples use WiFi configuration that you can set via project configuration menu

   If you'd rather not, just change the below entries to strings with
   the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_ESP_WIFI_SSID      CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS      CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_MAXIMUM_RETRY  CONFIG_ESP_MAXIMUM_RETRY

#if CONFIG_ESP_WIFI_AUTH_OPEN
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
#elif CONFIG_ESP_WIFI_AUTH_WEP
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
#elif CONFIG_ESP_WIFI_AUTH_WPA_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WAPI_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
#endif

/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;

/* The event group allows multiple bits for each event, but we only care about two events:
 * - we are connected to the AP with an IP
 * - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1

static const char *TAG = "wifi station";

static int s_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 && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } else if (event_base == IP_EVENT && 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));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();

    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_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .password = EXAMPLE_ESP_WIFI_PASS,
            /* Setting a password implies station will connect to all security modes including WEP/WPA.
             * However these modes are deprecated and not advisable to be used. Incase your Access point
             * doesn't support WPA2, these mode can be enabled by commenting below line */
         .threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
        },
    };
    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() );

    ESP_LOGI(TAG, "wifi_init_sta finished.");

    /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
     * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
            pdFALSE,
            pdFALSE,
            portMAX_DELAY);

    /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
     * happened. */
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
    } else {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }
}

void app_main(void)
{
    //Initialize 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);

    ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
    wifi_init_sta();
}

七、ESP32 Wi-Fi代码解读

7.1 主程序app_main

首先对nvs进行初始化,如果初始化不成功,则进行擦除,然后再执行初始化,以便可以将数据存储再nvs当中。nvs初始化完成之后,就可以执行用户自定义的WiFi初始化和连接代码。主函数中涉及到的三个基本的代码以及一些宏定义解释如下:
代码注释
nvs_flash_init()

对nvs进行初始化,nvs是flash中用来保存WiFi通信的数据,具体原理参考ESP32-S3 NVS入门。

nvs_flash_erase()

在nvs没有多余空间存储页面或者nvs有新版本的时候,对nvs进行擦除,随后还是要执行nvs初始化nvs_flash_init()

wifi_init_sta()

用户自定义的WiFi初始化代码

ESP_ERROR_CHECK()

打印出当前函数执行错误对应的相关信息;

ESP_LOGI()

再ESP IDE的串口界面以绿色的方式显示相关格式化信息,与此相关的还有ESP_LOGE、ESP_LOGW等。

7.2 自定义代码wifi_init_sta()

初始化流程:

  1. 初始化事件组,其中包括系统默认的一个任务。
  2. 初始化TCP/IP协议栈,以便解析和通知网络相关的事件;
  3. 初始化WiFi底层驱动,以便调用驱动API;
  4. 注册用户层的事件以及相关事件处理函数;
  5. 配置好WiFi的工作模式,是AP模式还是STA模式;
  6. 启动WiFi;
  7. 等待网络连接结果,并告知用户。


函数注释
xEventGroupCreate();

初始化事件组

esp_netif_init()

初始化TCP/IP协议堆栈

esp_event_loop_create_default()

创建默认任务

esp_netif_create_default_wifi_sta()

初始化STA模式下TCP/IP协议堆栈相关参数

esp_wifi_init(&cfg)

初始化STA模式下TCP/IP适配器及处理函数

esp_event_handler_instance_register(WIFI_EVENT);

正对WiFi事件WIFI_EVENT注册一个事件处理函数event_handler

esp_event_handler_instance_register(IP_EVENT)

正对IP事件IP_EVENT注册一个事件处理函数event_handler

esp_wifi_set_mode(WIFI_MODE_STA)

设置WiFi工作模式为STA

esp_wifi_set_config(WIFI_IF_STA, &wifi_config)

设置WiFi要连接的AP账号和密码

esp_wifi_start()

启动WiFi

xEventGroupWaitBits(s_wifi_event_group)

等待连接成功事件(对应的位标志:WIFI_CONNECTED_BIT)

等待连接失败事件(对应的位标志:WIFI_FAIL_BIT)

这里面有一个难点是事件WIFI_EVENT_STA_START、IP_EVENT_STA_GOT_IP是如何产生,并最终驱动WIFI_CONNECTED_BIT注入到事件标注当中,以告诉用户WiFi连接成功。具体实现过程如下。

在调用函数esp_wifi_start()之后,Wi-Fi驱动程序将

WIFI_EVENT_STA_START

发布到事件任务(event task),事件任务(event task)调用应用程序事件回调函数(event_handler)执行esp_wifi_connect()函数。Wi-Fi驱动程序根据配置连接对应的AP,连接成功将 WIFI_EVENT_STA_CONNECTED 发布到事件任务(event task)。接收到此事件后,事件任务将启动 DHCP 客户端服务并开始获取 IP 地址。由于我们的案例中TCP/IP是基于LwIP 实现的,因此,还需等待IP_EVENT_STA_GOT_IP事件发生。

只有IP_EVENT_STA_GOT_IP事件发生,我们才能认为是连接上指定的AP,继而可以发送和接收数据。


八、ESP32 Wi-Fi连接验证

8.1 测试方法

ESP32连接到WiFi之后,与指定的服务器建立Socket连接。建立Socket连接之后,发送Hello, I am ESP32 Client,随后不停的侦听服务器发送的消息,收到消息后,在串口中打印出来。

8.2 服务器模拟工具sscom5

工具的下载地址:https://gitee.com/chanyiel/third-party-software/blob/master/sscom/

下载后,对软件进行设置,参考下图:

8.3 测试代码

新增如下include 文件

修改宏定义中的WiFi账号和密码:

主函数新增一句代码:

tcp_client_task任务代码如下:

static void tcp_client_task(void *pvParameters)
{
    char rx_buffer[128];
    char host_ip[] = "192.168.124.25";
    int  port = 5678;
    int  addr_family = 0;
    int  ip_protocol = 0;
    static const char *payload = "Hello, I am ESP32 Client.";
    while (1) {

        struct sockaddr_in dest_addr;
        dest_addr.sin_addr.s_addr = inet_addr(host_ip);
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(port);
        addr_family = AF_INET;
        ip_protocol = IPPROTO_IP;

        int sock =  socket(addr_family, SOCK_STREAM, ip_protocol);
        if (sock < 0) {
            ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, port);

        int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6));
        if (err != 0) {
            ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Successfully connected");

        err = send(sock, payload, strlen(payload), 0);
        if (err < 0) {
            ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
            break;
        }

        while (1) {

            int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
            // Error occurred during receiving
            if (len < 0) {
                ESP_LOGE(TAG, "recv failed: errno %d", errno);
                break;
            }
            // Data received
            else {
                rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
                ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
                ESP_LOGI(TAG, "%s", rx_buffer);
            }

            vTaskDelay(2000 / portTICK_PERIOD_MS);
        }

        if (sock != -1) {
            ESP_LOGE(TAG, "Shutting down socket and restarting...");
            shutdown(sock, 0);
            close(sock);
        }
    }
    vTaskDelete(NULL);
}

8.4 测试结果

设置好板子以及对应的串口

设置好板子型号以及窗口,点击锤子🔨按钮,编译代码。

点击绿色播放按钮,烧录编译后的代码。

设置好窗口,观察代码打印的内容

程序运行后,连接到WiFi,并且在服务器端成功实现通信。


完毕!

标签: 学习

本文转载自: https://blog.csdn.net/weixin_43880799/article/details/127128993
版权归原作者 PS_567 所有, 如有侵权,请联系我们删除。

“ESP32学习入门:WiFi连接网络”的评论:

还没有评论