对于物联网实战来说,wifi模块当属重中之重。今天有了学习的新思路,既然是入门学习,从整体入手,开始主要是按照模块化的角度去学习,从整体上认识每个模块的功能,然后在回顾部分再去梳理一遍流程、每个API调用的原理以及相关函数的知识。
目录
学习ESP32,少了wifi怎么行?这一篇先从扫描wifi信息开始光速入门,再一步步学习如何配置和连接wifi,到如何打开热点模式,最后进阶多种多样的配网方式,好好研究一下wifi板块的功能。
前两篇中涉及到的一些概念和用法可能对学习WIFI以及理解一些函数的用法有帮助,也记录了我过程中esp32学习方法的变化,链接指路→从点灯开始,学习定时器和PWM控制和从按键入手,学习中断回调以及freertos的线程概念
从本章起,我也将采用从模块化分析到API接口的方式,从整体到局部,去学习ESP32的WIFI功能(注意:ESP32只支持2.4GHz WiFi,还不支持5GHz WiFi)
level1:从wifi scan模式入门wifi模块
最简单的方式实现wifi扫描
先从扫描wifi开始认识ESP32的wifi模块,这一部分实现起来还是比较容易的,调用的接口也不多,源码以及模块化总结如下。
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#define DEFAULT_SCAN_LIST_SIZE 16
static const char *TAG = "scan";
void wifi_scan_result(void)
{
uint16_t number = DEFAULT_SCAN_LIST_SIZE;
uint16_t ap_count = 0;
wifi_ap_record_t ap_info[DEFAULT_SCAN_LIST_SIZE];
memset(ap_info, 0, sizeof(ap_info));
esp_wifi_scan_get_ap_records(&number, ap_info);
esp_wifi_scan_get_ap_num(&ap_count);
ESP_LOGI(TAG, "Total APs scanned = %d", ap_count);
for (int i = 0; (i < DEFAULT_SCAN_LIST_SIZE) && (i < ap_count); 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, "Channel \t\t%d\n", ap_info[i].primary);
}
}
void app_main(void)
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
nvs_flash_erase();
ret = nvs_flash_init();
}
esp_netif_init();
esp_event_loop_create_default();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_start();
esp_wifi_scan_start(NULL, true);
wifi_scan_result();
}
运行结果如下:
加入回调,采用扫描的方式
在按键模块我就写过,将想要实现功能函数放到主函数中,虽然很好理解,除非只实现几个功能,不然对于一个复杂事件来说还是太笨了,功能实现的过程和顺序都会相互干扰。所以我们需要更进一步加入中断的学习。
加入中断其实也比较容易实现,在前两篇文章中我有详细分析和讨论过,所以这里就不展开叙述了,源码以及模块化注释如下:
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#define DEFAULT_SCAN_LIST_SIZE 16
static const char *TAG = "scan";
uint16_t number = DEFAULT_SCAN_LIST_SIZE;
uint16_t ap_count = 0;
wifi_ap_record_t ap_info[DEFAULT_SCAN_LIST_SIZE];
void wifi_scan_result(void)
{
memset(ap_info, 0, sizeof(ap_info));
esp_wifi_scan_get_ap_records(&number, ap_info);
esp_wifi_scan_get_ap_num(&ap_count);
ESP_LOGI(TAG, "Total APs scanned = %d", ap_count);
for (int i = 0; (i < DEFAULT_SCAN_LIST_SIZE) && (i < ap_count); 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, "Channel \t\t%d\n", ap_info[i].primary);
}
}
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_SCAN_DONE) {
wifi_event_sta_scan_done_t* scan_done = (wifi_event_sta_scan_done_t*) event_data;
ESP_LOGE(TAG, "wifi scan done status = %d, number = %d", scan_done->status, scan_done->number);
wifi_scan_result();
}
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));
}
}
void app_main(void)
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
nvs_flash_erase();
ret = nvs_flash_init();
}
esp_netif_init();
esp_event_loop_create_default();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, NULL);
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, NULL);
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_start();
esp_wifi_scan_start(NULL, false);
}
level2:打开热点&连接路由器
level1可以算是小试牛刀,有了WiFi的相关概念之后,我们可以更近一步,学习如何配置wifi模块,更近一步打开物联网的大门。
sta模式和ap模式ESP官网都给了例程,上手就可以直接用,所以这两部分的代码我就直接引用了,只需要在vscode中,contorl+shift+p,输入example,在里面找到wifi模块的get-start,选择创建文件夹,然后编译烧录就可以看到结果了~
ap模式
上面的SCAN部分理解透了,AP部分直接光速拿下,由于是从被动扫描模式切换到了主动发射模式,所以需要自己DIY一些部分,总的来看还是非常好理解的,烧录好后,可以打开手机连接ESP的热点(虽然没有网络)。
sta模式
sta模式其实和ap模式实现过程大体相似,只是模式选择和个别调用的接口不同,但是因为需要考虑的事件和情况更多,所以代码量会比ap模式多一些,在官方的例程中,可以直接调用和烧录,因为显示范围问题,我把截图分成了两部分。
这里引入了事件的概念,下面有个方框没有备注上,是对上面事件信号的调用和监测,可以和上面那张图片对比着看。
经过了按键和点灯的学习,我们可以举一反三,比较轻松的去学习WiFi环节的配置,但是想要联网,我们必须要在源代码中就要设置好网络的ID和密码,这样多少还是比较麻烦的。那么怎么样去做到可以实现随时随地就更换网络呢?esp提供了一个方案——smart config,我们只需要连接手机,就可以随时更换所想要的WiFi和密码。
level3:一键智能配置smart config
例程使用
samrt config官方也给出了例程,我们只需要按照上述的方法即可创立例程。
在使用这个例程之前,我们需要先进行一个APP的下载,链接指路→手机端的网络配置软件,会跳转到github,选择.apk的文件下载,然后我们只需要进行配置就好了。
注意:ESP32只支持2.4GHz WiFi,还不支持5GHz WiFi,如果家里的网络多次配置失败的话,可以使用手机的热点,更容易成功。
实现原理
一键配网连接过程:
- ESP32-C3 开启混杂模式监听所有网络数据包。
- 手机连上 WiFi,开启 APP 软件,输入手机所在 WiFi 密码,请求配网。
- 手机通过广播、组播循环发送路由 SSID 和 PASSWD 数据包,按一定格式加密。
- ESP32 通过 UDP 包(长度)获取配置信息捕捉到路由 SSID 和 PASSWD,连接路由器。
Smart Config 采用的是 UDP 广播模式 (UDP 接收 IP 地址是 255.255.255.255) 。 WiFi 设备先 scan 环境下 AP, 得到 AP 的相关信息,如工作的 channel。然后配置 WiFi 芯片工作 scan 到的 channel 上去接收 UDP 包,如果没有接收到,继续配置工作在另外的 channel 上,如此循环,直到收到 UDP 包为止。
这种办法的致命缺点是成功率只有 70%,而且有些路由器不支持;但优点是能一键完成配网
回顾——esp32的WIFI实现流程
主程序
- nvs_flash_init 初始化默认NVS分区
- esp_netif_init 初始化底层TCP/IP堆栈
- esp_event_1oop_create_default 创建默认事件循环
- esp_netif_create_default_wifi_sta 使用默认WiFi Station配置创建esp_netif对象,将netif连 接到WiFi并注册默认WiFi处理程序 esp_wifi_init,为WiFi驱动初始化WiFi分配资源,如WiFi控制结构、RX/TX缓冲区、WiFi NVS结构等,这个WiFi也启动WiFi任务。必须先调用此API,然后才能调用所有其他WiFiAPI
- esp_event_handler_instance_register 监听WIFI_EVENTWiFi任意事件,触发事件后,进入回 调函数
- esp_event_handler_instance_register 监听IP_EVENT从连接的AP获得IP的事件,触发事件 后,进入回调函数
- esp_event_handler_instance_register 监听SC_EVENT从SmartConfig任意事件,触发事件后, 进入回调函数
- esp_wifi_set_mode 设置WiFi工作模式为station、soft-AP或station+soft-AP,默认模式为soft- AP模式。本程序设置为
- station esp_wifi_start 根据配置,启动WiFi
回调函数
- WIFI_EVENT_STA_START WiFi station模式启动时
- 【创建smartconfig_example_task 线程,开始SmartConfig】
- WIFI_EVENT_STA_DISCONNECTED WiFi station模式失去连接。
- 【清除CONNECTED_BIT标志位】
- IP_EVENT_STA_GOT_IP WiFi station模式从连接的AP那获得IP
- 【设置CONNECTED_BIT标志位】
- SC_EVENT_SCAN_DONE SmartConfig扫描AP列表结束
- SC_EVENT_FOUND_CHANNEL SmartConfig 从目标AP找到频道
- SC_EVENT_GOT_SSID_PSWD SmartConfig 获得WiFi信息(SSID和密码)时,
- 【解析出WiFi的SSID和密码】
- 【esp_wifi_disconnect断开当前WiFi连接】
- 【esp_wifi_set_config WiFI配置,设置WIFI_IF_STA模式,设置WiFi的SSID和密码】
- 【esp_wifi_connect WiFi连接】
- SC_EVENT_SEND_ACK_DONE SmartConfig 给App发送完成ACK
- 【设置ESPTOUCH_DONE_BIT标志位】
- 【设置ESPTOUCH_DONE_BIT标志位】
流程图:
小结
WiFi部分是esp的优势所在,也是其在今天的物联网市场如此受欢迎的原因。官方给的例程和RAINMAKER以及ESP TOUCH的接口都非常方便,我们可以很轻易的在它的基础上拓展自己需要的功能,很多接口都可以直接调用,大大简化了我们需要的开发时间和步骤。
还有一些部分因为时间安排原因,暂时没有展开写,比如像蓝牙部分、NVS实现过程,本地控制...但是从实战角度出发,这三篇穿插讲到的这些内容已经足够去让我们做一个比较小的实验demo了,还没有写到的内容会不断完善。
当然,从实战的角度出发,光实现一个WiFi的连接,只是搭好了一座桥,怎么样让人们在桥上有秩序地行走,也就是数据如何在MCU和互联网之间传输信息,还需要更进一步。下一章咱们聊聊如何从本地控制到接入rainmaker,实现一个小的物联网控制模型。