前言
(1)如果有嵌入式企业需要招聘湖南区域日常实习生,任何区域的暑假Linux驱动/单片机/RTOS的实习岗位,可C站直接私聊,或者邮件:zhangyixu02@gmail.com,此消息至2025年1月1日前均有效
(2)学习本文之前,建议先学习:ESP32S3网络编程学习笔记(0)—— 计算机网络基础科普
实操
工程目录和CMakeList修改
(1)在
main
文件夹中创建APP
文件夹,并且在APP
文件夹中创建wifi.c
和wifi.h
两个文件。
(2)在
main/CMakeLists.txt
中修改为如下代码。
idf_component_register(SRC_DIRS "." "./app"
INCLUDE_DIRS "." "./app")
WIFI程序配置
wifi.c
(1)在
wifi.c
中补充如下代码。
/**
****************************************************************************************************
* @file led.c
* @brief 正点原子ESP32S3开发板的WIFI扫描实验
* @author zhangyixu
* @version 1.0.0
* @date 2024-04-06
* @Copyright (c) 仅供学习,如需商用,请邮件zhangyixu02@gmail.com
****************************************************************************************************
* @attention
*
* 个人博客:www.zyxbeyourself.blog.csdn.net
* 本人邮件:zhangyixu02@gmail.com
****************************************************************************************************
* @par Change Logs:
*
* Date Author Notes
* 2024-04-06 zhangyixu 初始版本
*/
#include "esp_log.h"
#include "esp_wifi.h"
#include "wifi.h"
/* 存储12个WIFI名称 */
#define DEFAULT_SCAN_LIST_SIZE 10
static const char *TAG = "WIFI";
void wifi_init(void)
{
/*-------------- 1、Wi-Fi/LwIP 初始化阶段 --------------*/
/* 1.1、网卡初始化,LWIP内核初始化 */
ESP_ERROR_CHECK(esp_netif_init());
/* 1.2、创建新的事件循环 */
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* 1.3、用户初始化STA模式 */
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
assert(sta_netif);
/* wifi配置初始化 */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
/*-------------- 2、Wi-Fi/LwIP 配置阶段 --------------*/
/* 设置WIFI为STA模式 */
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
/*-------------- 3、Wi-Fi/LwIP 启动阶段 --------------*/
/* 启动WIFI */
ESP_ERROR_CHECK(esp_wifi_start());
}
void wifi_scan(void)
{
uint16_t number = DEFAULT_SCAN_LIST_SIZE;
uint16_t ap_count = 0;
wifi_ap_record_t ap_info[DEFAULT_SCAN_LIST_SIZE] = {0};
/* 开始扫描附件的WIFI */
esp_wifi_scan_start(NULL, true);
/* 获取上次扫描中找到的AP数量 */
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));
ESP_LOGI(TAG, "Total APs scanned = %u", ap_count);
/* 获取上次扫描中找到的AP列表 */
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info));
/* 下面是打印附件的WIFI信息 */
for (int i = 0; (i < number) && (i < ap_count); i++)
{
ESP_LOGI(TAG, "NUM \t\t%d", i);
ESP_LOGI(TAG, "SSID \t\t%s", ap_info[i].ssid);
ESP_LOGI(TAG, "RSSI \t\t%d", ap_info[i].rssi);
ESP_LOGI(TAG, "MAC \t\t%02X-%02X-%02X-%02X-%02X-%02X\n", ap_info[i].bssid[0], ap_info[i].bssid[1], ap_info[i].bssid[2], ap_info[i].bssid[3], ap_info[i].bssid[4], ap_info[i].bssid[5]);
}
}
wifi.h
(1)在
wifi.h
中补充如下代码。
/**
****************************************************************************************************
* @file led.h
* @brief 正点原子ESP32S3开发板的WIFI扫描实验
* @author zhangyixu
* @version 1.0.0
* @date 2024-04-06
* @Copyright (c) 仅供学习,如需商用,请邮件zhangyixu02@gmail.com
****************************************************************************************************
* @attention
*
* 个人博客:www.zyxbeyourself.blog.csdn.net
* 本人邮件:zhangyixu02@gmail.com
****************************************************************************************************
* @par Change Logs:
* Date Author Notes
* 2024-04-06 zhangyixu 初始版本
*/
#ifndef __WIFI_H__
#define __WIFI_H__
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/*============================ INCLUDES ======================================*/
/*============================ MACROS ========================================*/
/*============================ TYPES =========================================*/
/*============================ GLOBAL VARIABLES ==============================*/
/*============================ PROTOTYPES ====================================*/
/**
* @brief 将Wi-Fi初始化为sta并设置扫描方法
*
* @param 无
*
* @return 无
*/
void wifi_init(void);
/**
* @brief 进行WIFI扫描
*
* @param 无
*
* @return 无
*/
void wifi_scan(void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __WIFI_H__ */
main.c
(1)在
main.c
中补充如下代码。
/**
****************************************************************************************************
* @file led.c
* @brief 正点原子ESP32S3开发板的WIFI扫描实验
* @author zhangyixu
* @version 1.0.0
* @date 2024-04-06
* @Copyright (c) 仅供学习,如需商用,请邮件zhangyixu02@gmail.com
****************************************************************************************************
* @attention
*
* 个人博客:www.zyxbeyourself.blog.csdn.net
* 本人邮件:zhangyixu02@gmail.com
****************************************************************************************************
* @par Change Logs:
*
* Date Author Notes
* 2024-04-06 zhangyixu 初始版本
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "led.h"
#include "nvs_flash.h"
#include "wifi.h"
void app_main(void)
{
esp_err_t ret;
ret = nvs_flash_init(); /* 初始化NVS */
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();
}
led_init(); /* 初始化LED */
wifi_init();
wifi_scan();
while(1)
{
led_toggle();
vTaskDelay(pdMS_TO_TICKS(1000)); /* 延时1s */
}
}
运行结果
(1)烧录运行之后,能够一共扫描到多少个
Wi-Fi
,并且显示扫描到的前10个Wi-Fi
名字,信号强度,MAC
地址。
解析
为什么需要初始化NVS
(1)在
esp_wifi_init()
函数中,将会要使用到NVS
相关内容,如果不先将NVS
初始化,将无法成功读取到对应的内容,因此会导致初始化失败。
(2)如果我们不想使用NVS
,可以打开menuconfig
,按照下图关闭下图这个选项。
Wi-Fi配置初始化流程介绍
(1)在前面的计算机网络科普一文中我解释了
AP
和STA
的区别,因为本次实验是Wi-Fi
扫描实验,因此我们需要将ESP32S3
配置为STA
模式。现在我们看看乐鑫科技官方给出来的STA
模式配置流程。
(2)下图为截取部分,我们只需要看红框里面的内容即可。这个时候有人会问了,为什么只要看前三步呢?原因很简单,因为我们这里只会进行WIFI
扫描,并不会进行连接操作,因此只需要看前三步骤。
1.初始化阶段
(1)上图,先看1.1步,我们知道需要初始化
LwIP
。LwIP
是一个开源的TCP/IP
协议栈,专门设计用于嵌入式系统。我们直接调用esp_netif_init()
函数就可以将网卡和LwIP
内核进行初始化。
/* 1.1、网卡初始化,LWIP内核初始化 */
ESP_ERROR_CHECK(esp_netif_init());
(2)创建事件循环有两个函数
esp_event_loop_create_default()
和esp_event_loop_create()
函数。esp_event_loop_create_default()
其实就是对esp_event_loop_create()
进行了一层封装,没有特殊需求,一般调用esp_event_loop_create_default()
。
这个函数的调用是必要的,因为WIFI
扫描实验需要在扫描过程中处理一些事件,例如扫描开始事件,扫描完成事件。如果没有创建事件循环,这些事件将无法被正确处理,导致Wi-Fi
扫描实验无法正常进行。
/* 1.2、创建新的事件循环 */
ESP_ERROR_CHECK(esp_event_loop_create_default());
(3.1)前面说了,
Wi-Fi
扫描实验需要在扫描过程中处理一些事件。而这些事件由esp_netif_create_default_wifi_sta()
函数创建。这个事件能够初始化Wi-Fi
为STA
模式,官方的解释是创建有TCP/IP
堆栈的默认网络接口实例绑定station
。
/* 1.3、创建有 TCP/IP 堆栈的默认网络接口实例绑定 station */
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
assert(sta_netif);
(3.2)这一步和上一步同属于1.3步骤,这里是 创建
Wi-Fi
驱动程序任务,并初始化Wi-Fi
驱动程序。
/* 1.3、创建 Wi-Fi 驱动程序任务,并初始化 Wi-Fi 驱动程序 */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
(4)这个时候肯定就有同学有疑问了,怎么没有1.4创建应用程序任务呢?原因很简单,我们这里只是进行
Wi-Fi
扫描,并不会进行连接,因此这一步进行省略。
1.2配置阶段
(1)这句话就是将
Wi-Fi
配置为STA模式。如果不是在中国,可能还需要调用esp_wifi_set_xxx ()
进行更多的配置,例如:协议模式、国家代码、带宽等。
/* 2、设置 Wi-Fi为STA模式 */
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
1.3启动阶段
(1)因为我们不需要连接
Wi-Fi
,因此无需关心WIFI_EVENT_STA_CONNECTED
事件。只需要调用如下代码即可。
/* 3.1、启动 Wi-Fi 驱动程序 */
ESP_ERROR_CHECK(esp_wifi_start());
关键API介绍
esp_wifi_scan_start()
(1)这个函数表示开始扫描附近的Wi-Fi设备,我们先讲解一下
esp_wifi_scan_start()
第一个参数的作用。
<1>如果ssid
不为NULL
,则只扫描SSID
与该值相同的AP
。否则扫描所有SSID
的AP
。
注:ssid就是Wi-Fi的名字。AP表示热点或者说是Wi-Fi。
<2>如果bssid
不为NULL
,则只扫描MAC
地址与该值相同的AP
。
注:不了解MAC地址是什么意思的,请看上一篇计算机网络科普。
<3>如果channel
为0,则进行全信道扫描。否则只扫描指定的信道。
<4>show_hidden
如果为true
则扫描时会包括隐藏SSID
的AP
。否则会忽略隐藏SSID
的AP
。
<5>scan_type
如果为WIFI_SCAN_TYPE_ACTIVE
,则进行主动扫描。如果是WIFI_SCAN_TYPE_PASSIVE
,则进行被动扫描。如下为主动扫描和被动扫描的区别。通常情况下,移动设备会优先使用主动扫描,而固定设备则更倾向于使用被动扫描。
- 主动扫描:客户端主动发送
Probe Request
帧,询问周围是否有AP
。AP
收到Probe Request
后,会立即回复Probe Response
帧,告知自己的信息。主动扫描速度更快,但会增加网络流量和功耗。- 被动扫描:客户端被动地监听
AP
周期性发送的Beacon
帧。AP
会定期广播Beacon
帧,包含自身的SSID
、安全性等信息。被动扫描速度较慢,但不会增加网络流量和功耗。- 区别:主动扫描可以发现隐藏
SSID
的AP
,被动扫描无法发现。主动扫描可以更快地发现AP
,但会增加功耗。被动扫描不会主动打扰AP
,但可能会漏掉某些AP
。<6>
scan_time
用于控制每个信道的扫描时间。对于被动描,scan_time.passive
字段指定每个信道的扫描时间。对于主动扫描,每个信道的扫描时间由scan_time.active.min
和scan_time.active.max
决定。
<7>home_chan_dwell_time
用于控制在主信道上停留的时间。当进行快速扫描时,会优先在主信道上停留较长时间,以提高扫描效率。
(2)第二个参数就比较容易理解,如果是true
那么需要等待Wi-Fi
扫描完成之后才会进行esp_wifi_scan_start()
下面的函数。如果是false
,那么无需等待Wi-Fi
扫描完成就会马上执行下面的任务。
(3)如果没有特殊需求,esp_wifi_scan_start()
函数第一个参数一般填写NULL
默认为主动扫描,扫描所有通道,扫描所有Wi-Fi,忽略隐藏Wi-Fi,主动扫描时间为0~120ms,主信道上停留的时间为360ms。
/**
* @brief 扫描所有可用的 AP
*
* @param config 扫描的配置设置,如果设置为 NULL 将使用默认设置,默认值为 show_hidden:false、scan_type:active、scan_time.active.min:0、scan_time.active.max:120 毫秒、scan_time.passive :360 毫秒
* -block 如果true,此API将阻止调用者直到扫描完成,否则将立即返回
*
* @return ESP_OK 配置成功
* - ESP_ERR_WIFI_NOT_INIT WiFi未由esp_wifi_init初始化
* - ESP_ERR_WIFI_NOT_STARTED esp_wifi_start未启动WiFi
* - ESP_ERR_WIFI_TIMEOUT 阻塞扫描超时
* - ESP_ERR_WIFI_STATE 调用esp_wifi_scan_start时wifi仍在连接
*/
esp_err_t esp_wifi_scan_start(const wifi_scan_config_t *config, bool block);
/** @brief 每个通道的活动扫描时间范围 */
typedef struct {
uint32_t min; /**< 每个通道的最小活动扫描时间,单位:毫秒 */
uint32_t max; /**< 每个通道的最大活动扫描时间,单位:毫秒,超过1500ms可能导致工作站与AP断开连接,不建议使用。 */
} wifi_active_scan_time_t;
/** @brief 每个通道的主动和被动扫描时间的总和 */
typedef struct {
wifi_active_scan_time_t active; /**< 每个通道的主动扫描时间,单位:毫秒 */
uint32_t passive; /**< 每通道被动扫描时间,单位:毫秒,超过1500ms可能导致工作站与AP断开连接,不建议使用 */
} wifi_scan_time_t;
/** @brief SSID扫描参数 */
typedef struct {
uint8_t *ssid; /**< SSID of AP */
uint8_t *bssid; /**< MAC address of AP */
uint8_t channel; /**< 通道,扫描特定的通道 */
bool show_hidden; /**< 使能扫描SSID隐藏的AP */
wifi_scan_type_t scan_type; /**< 扫描类型,主动或被动 */
wifi_scan_time_t scan_time; /**< 每通道扫描时间 */
uint8_t home_chan_dwell_time;/**< 在扫描连续通道之间花费的时间*/
} wifi_scan_config_t;
esp_wifi_scan_get_ap_num()
(1)当我们使用
esp_wifi_scan_start()
函数扫描完Wi-Fi
之后,我们可以利用这个函数获取道扫描道的Wi-Fi
数量。
/**
* @brief 获取上次扫描中找到的Wi-Fi数量
*
* @param number 存储上次扫描中找到的Wi-Fi数量
*
* @return ESP_OK 获取成功
* - ESP_ERR_WIFI_NOT_INIT Wi-Fi未由esp_wifi_init初始化
* - ESP_ERR_WIFI_NOT_STARTED esp_wifi_start未启动Wi-Fi
* - ESP_ERR_INVALID_ARG 无效参数
*/
esp_err_t esp_wifi_scan_get_ap_num(uint16_t *number);
esp_wifi_scan_get_ap_records()
(1)当我们调用
esp_wifi_scan_start()
函数
/**
* @brief 获取上次扫描中找到的Wi-Fi列表
*
* @param number 设置ap_records可以容纳的最大Wi-Fi数量,如果number为10,但是扫描到的Wi-Fi只有4个,此时number会变成4。
* - ap_records 保存找到的Wi-Fi
*
* @return ESP_OK 获取成功
* - ESP_ERR_WIFI_NOT_INIT Wi-Fi未由esp_wifi_init初始化
* - ESP_ERR_WIFI_NOT_STARTED esp_wifi_start未启动Wi-Fi
* - ESP_ERR_INVALID_ARG 无效参数
* - ESP_ERR_NO_MEM 内存不足
*/
esp_err_t esp_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records);
Wi-Fi扫描流程介绍
(1)进行
Wi-Fi
扫描实验的前提是将ESP32
设置为了STA
模式,并且成功启动了Wi-Fi
,否则无法进行Wi-Fi
扫描实验。
(2)我们初始化完ESP32的Wi-Fi之后,就能够直接调用esp_wifi_scan_get_ap_num()
函数进行扫描Wi-Fi
了,需要注意的是,esp_wifi_scan_get_ap_num()
的第二个参数需要配置为true
,我们需要扫描完Wi-Fi
之后再进行下一步操作。
(3)扫描到的Wi-Fi
信息存储在wifi_ap_record_t
结构体类型的数组里面,这个对于新手而言只需要关心三个参数:
- ssid:
Wi-Fi
名字- rssi:一般来说,它是一个负数。
RSSI
值越大,表示信号强度越强,接近0
的RSSI
值表示非常好的信号强度,而接近于-100
的RSSI
值表示非常弱的信号强度。在某些情况下,如果使用的是其他类型的无线技术或者有特殊配置,RSSI
值可能会是正数。但对于大多数情况下,尤其是WiFi
和蓝牙等常见的无线通信,RSSI
值通常是负数。- bssid:
Wi-Fi
的MAC
地址
参考
(1)乐鑫官方文档:ESP32-S3 Wi-Fi station 一般情况
(2)B站:WIFI扫描 - 乐鑫 ESP32 物联网开发框架 ESP-IDF 开发入门 - 孤独的二进制出品
(3)飞书:【立创·实战派ESP32-C3】开发板文档教程