Skip to main content

使用 Seeed Studio XIAO ESP32-C5 的 WiFi 功能

Seeed Studio XIAO ESP32-C5

Seeed Studio XIAO ESP32-C5 支持双频 2.4 GHz 和 5 GHz Wi-Fi,配备双频 Wi-Fi 6(802.11ax)射频,并向下兼容 802.11a/b/g/n/ac 标准。此外,该开发板支持 U.FL 天线连接,旨在增强 XIAO ESP32-C5 的无线连接性能。在本教程中,我们将介绍如何利用 XIAO ESP32-C5 的 Wi-Fi 功能连接到 Wi-Fi 网络并执行基本的网络任务。
以下教程使用 Arduino IDE 进行编译和烧录。如果你还没有使用 Arduino IDE 的经验,请访问 Getting Started with Seeed Studio XIAO ESP32-C5

入门指南

天线安装

在 Seeed Studio XIAO ESP32-C5 的包装内,配有一个专用的 Wi-Fi/BT 天线连接器。为了获得最佳的 WiFi/Bluetooth 信号强度,你需要取出包装中附带的天线并将其连接到该连接器上。

WiFi 库的常用接口

  • XIAO ESP32-C5 提供了丰富的 Wi-Fi 网络功能。通常,我们可以查看 ESP32 内置包中的 WiFi 库函数,并选择相应的函数来实现所需的功能。接下来,我们将列出一些常用接口并介绍它们的用法。
tip

如果你想直接开始 Wi-Fi 使用教程,可以跳转到 WiFi 使用示例

通用 WiFi 函数

  • WiFiGenericClass::getHostname() -- 是 ESP32 WiFi 库中的一个函数,用于以字符串形式返回设备的主机名。主机名是在网络中标识设备的唯一名称。该函数会获取之前通过 WiFiGenericClass::setHostname() 设置的主机名。如果尚未设置主机名,则会返回默认主机名。

  • WiFiGenericClass::persistent(bool persistent) -- 是一个用于启用或禁用 ESP32 WiFi 库持久化模式的方法。当启用持久化模式时,Wi-Fi 配置信息会存储在非易失性存储器(NVM)中,即使断电或复位也会保留。当禁用持久化模式时,配置信息存储在 RAM 中,在断电或复位后会丢失。

    • 输入参数
      • persistent: 如果参数为 true,则启用持久化模式;如果参数为 false,则禁用持久化模式。
  • WiFiGenericClass::enableLongRange(bool enable) -- 该函数用于启用或禁用 WiFi 模块的 Long Range(LR,长距离)特性。启用后,LR 功能允许模块连接到比平常更远距离的 WiFi 网络,但数据速率会降低。

    • 输入参数
      • enable: 将该参数设置为 true 以启用该特性,设置为 false 以禁用。
  • WiFiGenericClass::mode(wifi_mode_t m) -- 该函数用于设置设备的 WiFi 模式。

    • 输入参数
      • m: 参数 m 指定要设置的模式,可以是 wifi_mode_t 枚举中定义的以下常量之一:
        • WIFI_MODE_NULL: 禁用 WiFi station 和接入点模式。
        • WIFI_MODE_STA: 启用 WiFi station 模式,用于连接到现有 WiFi 网络。
        • WIFI_MODE_AP: 启用接入点模式,用于创建新的 WiFi 网络。
        • WIFI_MODE_APSTA: 同时启用 WiFi station 和接入点模式。
  • WiFiGenericClass::setSleep(wifi_ps_type_t sleepType) -- 该函数用于为 WiFi 模块设置省电模式。

    • 输入参数
      • sleepType: sleepType 参数是一个枚举类型,用于指定要使用的省电模式类型。有三种可能的睡眠类型:
        • WIFI_PS_NONE: 默认睡眠模式,WiFi 模块不会进入省电模式。
        • WIFI_PS_MIN_MODEM: 在此模式下,WiFi 模块会关闭其调制解调器,同时保持与接入点(AP)的连接。
        • WIFI_PS_MAX_MODEM: 启用最激进的 Wi-Fi 省电模式。它可以显著增加休眠时间,但可能会带来更高的延迟和更低的吞吐量,并且在某些网络中可能影响连接稳定性。

STA 函数

  • WiFiSTAClass::status() -- 返回连接状态。

    • 输出: wl_status_t 中定义的值之一。
      • WL_NO_SHIELD: 表示 Wi-Fi 模块不存在。
    • WL_IDLE_STATUS: 表示 Wi-Fi 模块当前未执行任何操作。
    • WL_NO_SSID_AVAIL: 表示在扫描过程中未找到任何 Wi-Fi 网络。
    • WL_SCAN_COMPLETED: 表示 Wi-Fi 扫描已成功完成。
    • WL_CONNECTED: 表示 ESP32 已成功连接到某个 Wi-Fi 网络。
    • WL_CONNECT_FAILED: 表示连接到 Wi-Fi 网络失败。
    • WL_CONNECTION_LOST: 表示与 Wi-Fi 网络的连接已丢失。
    • WL_DISCONNECTED: 表示 ESP32 之前连接过某个 Wi-Fi 网络,但当前未连接到任何网络。
  • WiFiSTAClass::begin(const char* wpa2_ssid, wpa2_auth_method_t method, const char* wpa2_identity, const char* wpa2_username, const char *wpa2_password, const char* ca_pem, const char* client_crt, const char* client_key, int32_t channel, const uint8_t* bssid, bool connect) -- 使用 WPA2 Enterprise AP 启动 Wifi 连接。

    • 输入参数(可选)
      • ssid: 指向 SSID 字符串的指针。
      • method: WPA2 的认证方法(WPA2_AUTH_TLS, WPA2_AUTH_PEAP, WPA2_AUTH_TTLS)
      • wpa2_identity: 指向实体的指针
      • wpa2_username: 指向用户名的指针
      • wpa2_password: 指向密码的指针。
      • ca_pem: 指向包含 CA 证书的 .pem 文件内容字符串的指针
      • client_crt: 指向包含客户端证书的 .crt 文件内容字符串的指针
      • client_key: 指向包含客户端密钥的 .key 文件内容字符串的指针
      • channel: 可选。AP 的信道
      • bssid: 可选。AP 的 BSSID / MAC
      • connect: 可选。是否调用连接
  • WiFiSTAClass::reconnect() -- 将强制断开连接,然后开始重新连接到 AP。

    • 输出: True/False。
  • WiFiSTAClass::disconnect(bool wifioff, bool eraseap) -- 从网络断开连接。

    • 输入参数

      • wifioff: wifioff 为 true 时关闭 Wi-Fi 射频。
      • eraseap: eraseap 为 true 时从 NVS 存储器中擦除 AP 配置。
    • 输出: True/False。

  • WiFiSTAClass::config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1, IPAddress dns2) -- 更改 IP 配置设置并禁用 DHCP 客户端。

    • 输入参数
      • local_ip: 静态 IP 配置。
      • gateway: 静态网关配置。
      • subnet: 静态子网掩码。
      • dns1: 静态 DNS 服务器 1。
      • dns2: 静态 DNS 服务器 2。
  • WiFiSTAClass::setAutoConnect(bool autoConnect) -- 已弃用。用于设置 ESP32 station 在上电时是否自动连接到已记录的 AP。默认启用自动连接。

    • 输入参数

      • autoConnect: autoConnect 布尔值。
    • 输出: False。

  • WiFiSTAClass::waitForConnectResult(unsigned long timeoutLength) -- 等待 WiFi 连接得到结果。

    • 输入参数

      • timeoutLength: 该参数指定等待建立连接的最长时间,单位为毫秒。
    • 输出: wl_status_t 中定义的值之一。

  • WiFiSTAClass::localIP() -- 获取 station 接口的 IP 地址。

    • 输出: IPAddress station IP。
  • WiFiSTAClass::macAddress(uint8_t* mac) -- 获取 station 接口的 MAC 地址。

    • 输入参数

      • mac(可选): 指向长度为 WL_MAC_ADDR_LENGTH 的 uint8_t 数组的指针。
    • 输出: 指向 uint8_t * 的指针。

  • WiFiSTAClass::SSID() -- 返回当前与网络关联的 SSID。

    • 输出: SSID。
  • WiFiSTAClass::RSSI(void) -- 返回当前网络的 RSSI。

    • 输出: RSSI。

AP 函数

  • WiFiAPClass::softAP(const char* ssid, const char* passphrase, int channel, int ssid_hidden, int max_connection, bool ftm_responder) -- 这是 XIAO ESP32-C5 的 WiFi 库中的一个函数,用于建立 SoftAP(程序接入点),使其他设备可以连接到 XIAO ESP32-C5 并访问其资源。

    • 输入参数

      • ssid: 指向 SSID 的指针(最多 63 个字符)。
    • passphrase: (对于 WPA2,最少 8 个字符;若为开放网络则使用 NULL)。

    • channel: WiFi 信道号,1 - 13。

    • ssid_hidden: 网络隐藏(0 = 广播 SSID,1 = 隐藏 SSID)。

    • max_connection: 最大同时连接的客户端数量,1 - 4。

    • 输出: True/False。

  • WiFiAPClass::softAPgetStationNum() -- 获取连接到 softAP 接口的 Station / 客户端数量。

    • 输出: Station 数量。
  • WiFiAPClass::softAPConfig(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dhcp_lease_start) -- 用于配置 SoftAP 的函数。

    • 输入参数

      • local_ip: 接入点 IP。
      • gateway: 网关 IP。
      • subnet: 子网掩码。
    • 输出: True/False。

  • WiFiAPClass::softAPIP() -- 获取 softAP 接口的 IP 地址。

    • 输出: IPAddress softAP IP。
  • WiFiAPClass::softAPmacAddress(uint8_t* mac) -- 获取 softAP 接口的 MAC 地址。

    • 输入参数

      • mac (可选): 指向长度为 WL_MAC_ADDR_LENGTH 的 uint8_t 数组的指针。
    • 输出: 指向 uint8_t* 的指针或 String 类型的 mac。

WiFi 扫描函数

  • WiFiScanClass::scanNetworks(bool async, bool show_hidden, bool passive, uint32_t max_ms_per_chan, uint8_t channel, const char * ssid, const uint8_t * bssid) -- 开始扫描可用的 WiFi 网络。

    • 输入参数

      • async: 该参数是一个布尔值,用于决定是否以异步方式执行扫描。若设为 true,函数会立即返回,之后可以通过调用 getScanResults() 函数获取扫描结果。若设为 false,函数会阻塞直到扫描完成。
      • show_hidden: 该参数是一个布尔值,用于决定函数是否在扫描结果中包含隐藏网络。
      • passive: 该参数是一个布尔值,用于决定函数是否执行被动扫描。若设为 true,函数在扫描期间不会发送任何数据包,扫描可能耗时更长,但在某些场景下会更有用。
      • max_ms_per_chan: 该参数是每个信道上用于扫描的最长时间(毫秒)。
      • channel: 该参数是要扫描的 Wi-Fi 信道。如果设为 0,函数将扫描所有可用信道。
      • ssid: 该参数是一个指向以空字符结尾的字符串的指针,包含要扫描的网络的 SSID。如果设为 nullptr,函数将扫描所有可用网络。
      • bssid: 该参数是一个指向 6 字节数组的指针,包含要扫描的接入点的 MAC 地址。如果设为 nullptr,函数将扫描所有接入点。
    • 输出: 此函数的返回值是一个整数,表示扫描到的网络数量。

  • WiFiScanClass::getNetworkInfo(uint8_t i, String &ssid, uint8_t &encType, int32_t &rssi, uint8_t* &bssid, int32_t &channel) -- 将某个已扫描 WiFi 的所有信息加载到指针参数中。

    • 输入参数

      • i: 该函数用于获取指定索引 i 处的已扫描网络的信息。
      • ssid: 参数 ssid 是一个 String 变量的引用,函数会在其中存储网络的 SSID。
      • encType: 参数 encType 是一个 uint8_t 变量的引用,函数会在其中存储网络的加密类型(0 = open,1 = WEP,2 = WPA_PSK,3 = WPA2_PSK,4 = WPA_WPA2_PSK)。
      • rssi: 参数 rssi 是一个 int32_t 变量的引用,函数会在其中存储网络的接收信号强度指示(RSSI)。
      • bssid: 参数 bssid 是一个 uint8_t* 指针的引用,函数会在其中存储网络的 BSSID(MAC 地址)。
      • channel: 参数 channel 是一个 int32_t 变量的引用,函数会在其中存储网络的信道号。
    • 输出: True/False。

  • WiFiScanClass::SSID(uint8_t i) -- 返回在网络扫描过程中发现的 SSID。

    • 输入参数

      • i: 指定要从哪个网络条目获取信息。
    • 输出: 已扫描网络列表中指定条目的 SSID 字符串。

  • WiFiScanClass::RSSI(uint8_t i) -- 返回在 scanNetworks 过程中发现的网络的 RSSI。

    • 输入参数

      • i: 指定要从哪个网络条目获取信息。
    • 输出: 已扫描网络列表中指定条目的 RSSI 有符号值。

WiFi 客户端函数

  • WiFiClient::connect(IPAddress ip, uint16_t port, int32_t timeout) -- 此函数用于 WiFiClient 库中,以指定的超时时间连接到远程 IP 地址和端口。

    • 输入参数
      • ip: 要连接的服务器 IP 地址。
      • port: 要连接的服务器端口号。
      • timeout (可选): 建立连接所允许等待的最长时间(毫秒)。如果在该时间内未建立连接,函数将返回错误。如果 timeout 设为 0,函数将无限期等待连接建立。
  • WiFiClient::stop() -- 该函数用于将客户端与服务器断开连接,并释放客户端使用的套接字/端口。一旦调用该函数,客户端将无法再发送或接收数据。

  • WiFiClient::setTimeout(uint32_t timeout_ms) -- 该函数设置客户端在等待连接建立或接收数据时的最长等待毫秒数。如果连接或数据传输时间超过指定的超时时间,连接将被关闭。

    • 输入参数
      • timeout_ms: 超时时间的毫秒数。
  • WiFiClient::write(uint8_t data) -- 通过 WiFiClient 实例向已连接的服务器写入单字节数据。或者使用 WiFiClient::write(const uint8_t *buf, size_t size)

    • 输入参数
      • data: 需要通过已建立的网络连接发送的单字节数据。
  • WiFiClient::read() -- 该函数从已连接的服务器读取一个字节的传入数据。它以整数形式返回读取到的字节。如果没有可用数据,则返回 -1。或者使用 read(uint8_t *buf, size_t size)

    • 输出: 一个整数值,表示接收到的字节数。如果返回值为 0,表示服务器已关闭连接。
  • WiFiClient::peek() -- 该函数用于在不实际读取数据的情况下,检查服务器是否有可读数据。

    • 输出: 返回下一个传入数据字节,但不会将其从接收缓冲区中移除。如果没有可用数据,则返回 -1。
  • WiFiClient::available() -- 该函数用于检查服务器端有多少字节数据可供读取。

    • 输出: 返回一个整数值,表示可读取的字节数。

WiFi 服务器函数

  • WiFiServer::stopAll() -- 该函数是 Arduino WiFi 库中 WiFiServer 类的方法。此方法会停止所有使用 WiFiServer 类创建的服务器实例。当你希望一次性停止所有服务器,而不是对每个实例单独调用 stop() 方法时,这个函数非常有用。

  • WiFiServer::begin(uint16_t port, int enable) -- 该函数用于在指定端口上启动服务器。服务器将监听传入的客户端连接。

    • 输入参数
      • port: 要监听的端口号。
      • enable (可选): 一个标志,用于指示服务器在启动后是否应立即启用。该标志默认设为 true。
  • WiFiServer::hasClient() -- 该函数用于检查服务器上是否有任何传入的客户端连接可用。可以在循环中使用此函数以持续检查新连接。

    • 输出: 如果有客户端已连接,则返回一个 WiFiClient 对象;如果没有等待连接的客户端,则返回 NULL 指针。
  • WiFiServer::end() -- 该函数用于停止服务器并释放相关资源。一旦调用,服务器将不再接受新的客户端连接。任何现有的客户端连接将保持打开状态,直到由客户端或服务器一方关闭。WiFiServer::close()WiFiServer::stop() 具有相同的功能。

WiFi 多路连接函数

  • WiFiMulti::addAP(const char* ssid, const char *passphrase) -- 该函数用于向 WiFiMulti 对象将尝试连接的可用接入点(AP)列表中添加一个新的接入点。

    • 输入参数

      • ssid: 指向 SSID 的指针(最多 63 个字符)。
      • passphrase: (对于 WPA2,最少 8 个字符;若为开放网络则使用 NULL)。
    • 输出: True/False

  • WiFiMulti::run(uint32_t connectTimeout) -- 该函数会按顺序尝试连接已保存的接入点之一,直到成功连接为止。

    • 输入参数

      • connectTimeout: 该参数指定等待连接的最长时间(毫秒)。如果 connectTimeout 设为 0,函数将不会超时,并会一直尝试连接。
    • 输出: 状态

WiFi 使用示例

接下来,我们将使用 XIAO ESP32-C5 演示如何使用一些基本的 Wi-Fi 功能。

扫描网络

XIAO ESP32-C5 支持双频 2.4 GHz 和 5 GHz Wi-Fi,并且可以在扫描模式下扫描周围的 2.4 GHz 和 5 GHz Wi-Fi 网络及其信号强度。

程序

下面是一个示例程序,向你展示 XIAO ESP32-C5 如何扫描周围的 Wi-Fi 网络。

  • 参考代码
WiFiScan.ino
/*
* This sketch demonstrates how to scan WiFi networks. For chips that support 5GHz band, separate scans are done for all bands.
* The API is based on the Arduino WiFi Shield library, but has significant changes as newer WiFi functions are supported.
* E.g. the return value of `encryptionType()` different because more modern encryption is supported.
*/
#include "WiFi.h"

void setup() {
Serial.begin(115200);
// Enable Station Interface
WiFi.STA.begin();
Serial.println("Setup done");
}

void ScanWiFi() {
Serial.println("Scan start");
// WiFi.scanNetworks will return the number of networks found.
int n = WiFi.scanNetworks();
Serial.println("Scan done");
if (n == 0) {
Serial.println("no networks found");
} else {
Serial.print(n);
Serial.println(" networks found");
Serial.println("Nr | SSID | RSSI | CH | Encryption");
for (int i = 0; i < n; ++i) {
// Print SSID and RSSI for each network found
Serial.printf("%2d", i + 1);
Serial.print(" | ");
Serial.printf("%-32.32s", WiFi.SSID(i).c_str());
Serial.print(" | ");
Serial.printf("%4ld", WiFi.RSSI(i));
Serial.print(" | ");
Serial.printf("%2ld", WiFi.channel(i));
Serial.print(" | ");
switch (WiFi.encryptionType(i)) {
case WIFI_AUTH_OPEN: Serial.print("open"); break;
case WIFI_AUTH_WEP: Serial.print("WEP"); break;
case WIFI_AUTH_WPA_PSK: Serial.print("WPA"); break;
case WIFI_AUTH_WPA2_PSK: Serial.print("WPA2"); break;
case WIFI_AUTH_WPA_WPA2_PSK: Serial.print("WPA+WPA2"); break;
case WIFI_AUTH_WPA2_ENTERPRISE: Serial.print("WPA2-EAP"); break;
case WIFI_AUTH_WPA3_PSK: Serial.print("WPA3"); break;
case WIFI_AUTH_WPA2_WPA3_PSK: Serial.print("WPA2+WPA3"); break;
case WIFI_AUTH_WAPI_PSK: Serial.print("WAPI"); break;
default: Serial.print("unknown");
}
Serial.println();
delay(10);
}
}

// Delete the scan result to free memory for code below.
WiFi.scanDelete();
Serial.println("-------------------------------------");
}
void loop() {
Serial.println("-------------------------------------");
Serial.println("Default wifi band mode scan:");
Serial.println("-------------------------------------");
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
WiFi.setBandMode(WIFI_BAND_MODE_AUTO);
#endif
ScanWiFi();
#if CONFIG_SOC_WIFI_SUPPORT_5G
// Wait a bit before scanning again.
delay(1000);
Serial.println("-------------------------------------");
Serial.println("2.4 Ghz wifi band mode scan:");
Serial.println("-------------------------------------");
WiFi.setBandMode(WIFI_BAND_MODE_2G_ONLY);
ScanWiFi();
// Wait a bit before scanning again.
delay(1000);
Serial.println("-------------------------------------");
Serial.println("5 Ghz wifi band mode scan:");
Serial.println("-------------------------------------");
WiFi.setBandMode(WIFI_BAND_MODE_5G_ONLY);
ScanWiFi();
#endif
// Wait a bit before scanning again.
delay(10000);
}

效果展示

  • 上传程序后,打开 Arduino IDE 的串口监视器,即可输出扫描到的 Wi-Fi 信息。

连接到 Wi-Fi 网络

在 Wi-Fi 覆盖范围内,如果你知道目标 Wi-Fi 网络的 SSID 和 PASSWORD,就可以通过 XIAO ESP32-C5 支持的 STA 模式连接到指定的 Wi-Fi 网络。

程序

接下来提供一个示例程序,向你展示 XIAO ESP32-C5 如何连接到指定的 Wi-Fi 网络。

tip

XIAO ESP32-C5 支持双频 Wi-Fi(2.4 GHz 和 5 GHz),允许你根据家庭网络情况选择连接方式。

  • 参考代码
#include <WiFi.h>

// Replace with your network credentials
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

void setup() {
Serial.begin(115200);

// Explicitly set mode to Station
WiFi.mode(WIFI_STA);

Serial.printf("Connecting to %s ", ssid);
WiFi.begin(ssid, password);

// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}

Serial.println("\nCONNECTED!");

// Print connection details
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Subnet Mask: ");
Serial.println(WiFi.subnetMask());
Serial.print("Gateway IP: ");
Serial.println(WiFi.gatewayIP());
Serial.print("DNS IP: ");
Serial.println(WiFi.dnsIP());

// C5 Specific: Check which band and channel we are on
Serial.print("Channel: ");
Serial.println(WiFi.channel());
Serial.print("RSSI (Signal Strength): ");
Serial.println(WiFi.RSSI());
}

void loop() {
// Check if WiFi is still connected
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi lost. Reconnecting...");
WiFi.disconnect();
WiFi.reconnect();
}
delay(5000);
}

效果展示

  • 上传程序后,打开 Arduino IDE 的串口监视器,即可输出已连接 Wi-Fi 网络的 IP 地址等信息。
  • 在我的环境中,我连接的是 5 GHz 网络。

AP 模式

XIAO ESP32-C5 可以作为热点供其他设备连接。换句话说,你可以使用支持 Wi-Fi 的设备连接到 XIAO ESP32-C5,而无需连接到你的路由器。

简单来说,当你将 XIAO ESP32-C5 配置为接入点时,就创建了它自己的独立 Wi-Fi 网络,附近的 Wi-Fi 设备(站点)可以连接到该网络(例如你的智能手机或电脑)。

程序

接下来提供一个示例程序,向你展示 XIAO ESP32-C5 如何创建热点并允许其他设备连接到它。

  • 参考代码
#include <WiFi.h>

const char* ap_ssid = "XIAO ESP32-C5";
const char* ap_password = "password1234";

int previous_station_count = 0;

void setup() {
Serial.begin(115200);

// Initialize built-in LED pin
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW); // Initial state: LED off

// Set mode to Access Point
WiFi.mode(WIFI_AP);

// Configure AP
// Arguments: SSID, Password, Channel (1-13 for 2.4G), Hidden (0/1), Max Connections
// Note: Forcing 5GHz AP usually requires lower-level IDF calls in current Arduino Core,
// so this will likely default to 2.4GHz.
bool result = WiFi.softAP(ap_ssid, ap_password, 1, 0, 4);

if (result) {
Serial.println("AP Started Successfully");
} else {
Serial.println("AP Start Failed");
}

// Print IP Address of the AP (Default is usually 192.168.4.1)
Serial.print("AP IP Address: ");
Serial.println(WiFi.softAPIP());
}

void loop() {
// Get current number of connected stations
int current_station_count = WiFi.softAPgetStationNum();
Serial.printf("Stations connected: %d\n", current_station_count);

// Check if any device has connected or disconnected
if (current_station_count > 0 && previous_station_count == 0) {
// New device connected, turn on LED
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("Device connected - LED ON");
} else if (current_station_count == 0 && previous_station_count > 0) {
// All devices disconnected, turn off LED
digitalWrite(LED_BUILTIN, LOW);
Serial.println("All devices disconnected - LED OFF");
}

previous_station_count = current_station_count;
delay(2000);
}

效果展示

  • 编译并上传程序后,你可以通过 WLAN 功能发现该 AP 热点。
  • 连接成功后,串口监视器会打印 IP 地址,同时板载 USER LED 会点亮。

WiFi 与 MQTT 的使用

MQTT 协议在物联网设备中被广泛使用,而 XIAO ESP32-C5 支持该协议——这意味着你可以使用 XIAO ESP32-C5 开发许多有趣的物联网项目。

程序

接下来将提供一个参考程序,向你展示如何在 XIAO ESP32-C5 上使用 MQTT 协议。

  • 安装 PubSubClient
  • 参考代码
#include <WiFi.h>
#include <PubSubClient.h>

const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

// Using a public MQTT broker for demonstration
const char* mqtt_server = "broker.emqx.io";
const int mqtt_port = 1883;

WiFiClient espClient;
PubSubClient client(espClient);

// Callback function: Executed when a message is received
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}

void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32C5Client-";
clientId += String(random(0xffff), HEX);

// Attempt to connect
if (client.connect(clientId.c_str())) {
Serial.println("connected");
// Once connected, subscribe to a topic
client.subscribe("esp32c5/test/topic");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}

void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi Connected");

// Configure MQTT Server
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}

void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();

// Publish a message every 2 seconds
static unsigned long lastMsg = 0;
unsigned long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
Serial.println("Publishing message...");
client.publish("esp32c5/test/topic", "Hello from XIAO ESP32-C5");
}
}

效果展示

  • 下载任意支持 MQTTX broker 客户端的软件,这里使用 MQTTX MQTTx

  • 打开 MQTTX 并添加客户端信息。此处使用默认客户端地址,因此你只需添加客户端 ID ESP32C5Client 并设置测试客户端名称,其余配置保持默认即可。


  • 上传代码并使用 MQTTX 进行连接。连接成功后,点击左侧的 New Subscription 来添加订阅。

  • 配置订阅信息:输入 esp32c5/test/topic 并为 QoS 选择 0 或 1。

  • 配置成功后,你可以看到 XIAO ESP32-C5(作为客户端)每 2 秒发布的消息。你也可以在选择主题后向客户端发送消息,客户端在接收到消息后会将其发布。

tip

broker.emqx.io 是一个所有用户共享的公共 broker。任何订阅相同主题的人都可以查看你的消息。它仅用于测试目的,不适合传输敏感数据。

WiFi & HTTP /HTTPS

程序

  • 安装 HTTPClient

  • 参考代码
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h> // Required for HTTPS

const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

// HTTPS Endpoint
const char* serverUrl = "https://jsonplaceholder.typicode.com/todos/1";

// Root CA Certificate (Optional for simple testing if setInsecure is used)
// To be secure, you should use the actual root CA of the website.
// For this example, we will use setInsecure().

void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected");
}

void loop() {
if ((WiFi.status() == WL_CONNECTED)) {

// 1. Create WiFiClientSecure object
WiFiClientSecure client;

// IGNORE SSL certificate validation (Good for testing, NOT for production)
client.setInsecure();

// 2. Create HTTPClient object
HTTPClient http;

Serial.print("[HTTPS] begin...\n");

// 3. Initialize connection
if (http.begin(client, serverUrl)) {
Serial.print("[HTTPS] GET...\n");

// 4. Send GET request
int httpCode = http.GET();

// 5. Check return code
if (httpCode > 0) {
Serial.printf("[HTTPS] GET... code: %d\n", httpCode);

if (httpCode == HTTP_CODE_OK || httpCode == 301 || httpCode == 302) {
String payload = http.getString();
Serial.println(payload);
}
} else {
Serial.printf("[HTTPS] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}

// 6. Close connection
http.end();
} else {
Serial.printf("[HTTPS] Unable to connect\n");
}
}

delay(10000);
}

效果展示

tip

https://jsonplaceholder.typicode.com 是一个免费的模拟 REST API 测试服务,专为开发和测试目的而设计。它不支持对真实数据进行永久修改或持久化。

  • 上传代码,然后你可以打开 https://jsonplaceholder.typicode.com/todos/1 和 Arduino IDE 中的串口监视器工具,检查信息是否一致。

WiFi-Mesh

以下是乐鑫官方对 ESP-WIFI-MESH 的介绍:

ESP-WIFI-MESH 是一种无线通信网络,其节点在乐鑫 SoC 上利用 AP-STA 同时工作特性,以 Mesh 拓扑结构进行组织。它提供了自组网和自愈能力,易于部署。ESP-WIFI-MESH 的网络拓扑可以在大范围内扩展到多达 1000 个节点,而无需任何特定的 Wi-Fi 基础设施支持。ESP-WIFI-MESH 还可用于在家庭部署场景中覆盖 Wi-Fi 盲区,即 Wi-Fi 信号无法覆盖的区域。

如需了解更详细的信息,请参考乐鑫官方链接:

接下来,我将向你展示一个 ESP-Mesh 的示例,对于该示例,你需要至少准备两块 ESP32 设备。

程序

  • 安装 Alteriom PainlessMesh
  • 安装 AsyncTCP
  • 参考代码
#include "painlessMesh.h"

#define MESH_PREFIX "ESP32_C5_MESH"
#define MESH_PASSWORD "mesh_password123"
#define MESH_PORT 5555

painlessMesh mesh;
Scheduler userScheduler; // to control your personal task

void sendMessage(); // Prototype

// Define a task to send messages every 1 second
Task taskSendMessage(1000, TASK_FOREVER, &sendMessage);

void sendMessage() {
String msg = "Hello from node ";
msg += mesh.getNodeId();
mesh.sendBroadcast(msg);
Serial.printf("Sent broadcast: %s\n", msg.c_str());
}

// Callback: When a message is received
void receivedCallback( uint32_t from, String &msg ) {
Serial.printf("Received from %u msg=%s\n", from, msg.c_str());
}

// Callback: When a new connection is established
void newConnectionCallback(uint32_t nodeId) {
Serial.printf("New Connection, nodeId = %u\n", nodeId);
}

// Callback: When connection changes
void changedConnectionCallback() {
Serial.printf("Changed connections\n");
}

// Callback: Time adjustment
void nodeTimeAdjustedCallback(int32_t offset) {
Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(), offset);
}

void setup() {
Serial.begin(115200);

// Debug messages
mesh.setDebugMsgTypes( ERROR | STARTUP );

// Initialize Mesh
mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );

// Register Callbacks
mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);

// Add task to scheduler
userScheduler.addTask( taskSendMessage );
taskSendMessage.enable();
}

void loop() {
// Keep the mesh network running
mesh.update();
}

效果展示

  • 将代码上传到两块 XIAO ESP32-C5 设备上,并打开任意串口工具查看结果。

XIAO ESP32-C5 设备 1 开始组网并发送消息。


XIAO ESP32-C5 设备 2 加入网络并接收来自设备 1 发送的消息。

示例:IoT 智能家居中控

通过以上 Wi-Fi 示例,你一定已经掌握了如何在 XIAO ESP32-C5 上使用 Wi-Fi。接下来将通过一个示例向你展示如何使用 Wi-Fi AP 模式和 HTTP 网络服务实现智能家居控制中枢——具体来说,就是如何使用 XIAO ESP32-C5 作为智能家居控制中枢来监控你家中的状态。

  • 安装 ESPAsyncWebServer

  • 上传代码并连接到 XIAO ESP32-C5 热点。
参考代码
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <DNSServer.h>

const char* ssid = "XIAO ESP32-C5"; // Hotspot name
const char* password = "12345678"; // Hotspot password (empty for no password, configurable)

DNSServer dnsServer;
AsyncWebServer server(80);

// HTML content for temporary website (You can modify the content here, e.g., add redirect to external website)
// const char index_html[] PROGMEM = R"rawliteral(
// <!DOCTYPE html>
// <html>
// <head>
// <title>Temporary Website</title>
// <meta charset="UTF-8">
// <!-- Optional: Add automatic redirect to external website -->
// <!-- <meta http-equiv="refresh" content="5;url=https://example.com"> -->
// </head>
// <body>
// <h1>Welcome to XIAO ESP32-C5 Temporary Website!</h1>
// <p>You have connected to XIAO-ESP32-C5 hotspot. This is custom content.</p>
// <form action="/" method="POST">
// <input type="submit" value="Continue">
// </form>
// </body>
// </html>
// )rawliteral";

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>XIAO ESP32-C5 Control Center</title>
<style>
:root {
--bg-color: #121212;
--card-bg: #1e1e1e;
--text-main: #e0e0e0;
--text-sub: #a0a0a0;
--accent: #00e5ff; /* Neon Blue */
--active: #00ff9d; /* Active Green */
--danger: #ff4081; /* Warning Red */
}

body {
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-main);
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}

h1 {
font-weight: 300;
letter-spacing: 1.5px;
margin-bottom: 10px;
text-align: center;
}

p.subtitle {
color: var(--text-sub);
font-size: 0.9rem;
margin-top: 0;
margin-bottom: 30px;
text-align: center;
}

/* Container Layout */
.container {
width: 100%;
max-width: 400px; /* Suitable width for mobile devices */
display: flex;
flex-direction: column;
gap: 20px;
}

/* Status Panel */
.status-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}

.card {
background-color: var(--card-bg);
border-radius: 12px;
padding: 15px;
text-align: center;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
border: 1px solid #333;
transition: transform 0.2s;
}

.card:active {
transform: scale(0.98);
}

.card-icon {
font-size: 24px;
margin-bottom: 5px;
display: block;
}

.card-value {
font-size: 1.2rem;
font-weight: bold;
color: var(--accent);
}

.card-label {
font-size: 0.8rem;
color: var(--text-sub);
}

/* Control Button Area */
.control-group {
background-color: var(--card-bg);
border-radius: 16px;
padding: 20px;
border: 1px solid #333;
}

.control-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #333;
}

.control-item:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}

/* Switch Style */
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 26px;
}

.switch input {
opacity: 0;
width: 0;
height: 0;
}

.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #333;
transition: .4s;
border-radius: 34px;
}

.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}

input:checked + .slider {
background-color: var(--accent);
box-shadow: 0 0 10px var(--accent);
}

input:checked + .slider:before {
transform: translateX(24px);
}

/* Main Button */
.main-btn {
width: 100%;
padding: 15px;
background: linear-gradient(45deg, var(--accent), #00b8d4);
border: none;
border-radius: 30px;
color: #000;
font-weight: bold;
font-size: 1rem;
cursor: pointer;
margin-top: 20px;
text-transform: uppercase;
letter-spacing: 1px;
}

.main-btn:active {
opacity: 0.9;
transform: scale(0.98);
}

</style>
</head>
<body>

<div class="container">
<header>
<h1>XIAO ESP32-C5</h1>
<p class="subtitle">IoT Smart Home Hub</p>
</header>

<div class="status-grid">
<div class="card">
<span class="card-icon">📡</span>
<div class="card-value">Online</div>
<div class="card-label">System Status</div>
</div>
<div class="card">
<span class="card-icon">🌡️</span>
<div class="card-value" id="temp-val">26.5°C</div>
<div class="card-label">Indoor Temperature</div>
</div>
<div class="card">
<span class="card-icon">💧</span>
<div class="card-value">58%</div>
<div class="card-label">Air Humidity</div>
</div>
<div class="card">
<span class="card-icon">⏱️</span>
<div class="card-value">12ms</div>
<div class="card-label">Latency</div>
</div>
</div>

<div class="control-group">
<div class="control-item">
<span>Living Room Main Light</span>
<label class="switch">
<input type="checkbox" id="btn-light" onchange="toggleDevice('light', this)">
<span class="slider"></span>
</label>
</div>

<div class="control-item">
<span>Air Purifier</span>
<label class="switch">
<input type="checkbox" id="btn-fan" onchange="toggleDevice('fan', this)">
<span class="slider"></span>
</label>
</div>

<div class="control-item">
<span>Auto Mode</span>
<label class="switch">
<input type="checkbox" checked onchange="toggleDevice('auto', this)">
<span class="slider"></span>
</label>
</div>
</div>

<form action="/" method="POST">
<button type="submit" class="main-btn">Enter Advanced Settings</button>
</form>

</div>

<script>
// Simple JavaScript for demo interaction
function toggleDevice(device, element) {
var state = element.checked ? "ON" : "OFF";
console.log(device + " is now " + state);

// You can add fetch request to send to ESP32 here
// fetch('/toggle?device=' + device + '&state=' + state);
}

// Simulate dynamic data fluctuation effect
setInterval(() => {
const temp = 26 + (Math.random() * 0.5);
document.getElementById('temp-val').innerText = temp.toFixed(1) + "°C";
}, 3000);
</script>
</body>
</html>
)rawliteral";

// Handle all requests and redirect to temporary page
class CaptiveRequestHandler : public AsyncWebHandler {
public:
CaptiveRequestHandler() {}
virtual ~CaptiveRequestHandler() {}

bool canHandle(AsyncWebServerRequest *request) {
return true; // Handle all requests
}

void handleRequest(AsyncWebServerRequest *request) {
request->send_P(200, "text/html", index_html); // Return HTML content
}
};

void setup() {
Serial.begin(115200);
delay(10);

// Set to AP (Access Point) mode
WiFi.mode(WIFI_AP);
WiFi.softAP(ssid, password);

Serial.println("Hotspot created: " + String(ssid));
Serial.print("IP Address: ");
Serial.println(WiFi.softAPIP());

// Start DNS server, redirect all domains to local IP
dnsServer.start(53, "*", WiFi.softAPIP());

// Configure Web Server: redirect all not found requests to temporary page
server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER); // Only in AP mode
server.onNotFound([](AsyncWebServerRequest *request) {
request->send_P(200, "text/html", index_html);
});

server.begin();
Serial.println("Web Server started");

server.on("/toggle", HTTP_GET, [](AsyncWebServerRequest *request){
String device = request->getParam("device")->value();
String state = request->getParam("state")->value();

Serial.print("Device: ");
Serial.print(device);
Serial.print(" State: ");
Serial.println(state);

if(device == "light" && state == "ON") {
digitalWrite(LED_BUILTIN, LOW); // Assume LOW level turns on the LED
} else {
digitalWrite(LED_BUILTIN, HIGH);
}

request->send(200, "text/plain", "OK");
});
}

void loop() {
dnsServer.processNextRequest(); // Process DNS requests
delay(10);
}

  • 输入 IP 地址以跳转到网页。

tip

网页上显示的控件和传感器是可自定义的。您可以根据自己的具体需求添加新的控件,并修改生成的网页。参考:ESPAsyncWebServer

tip

XIAO ESP32-C5 生成的 AP 热点默认无法连接互联网。如果需要连接互联网,可以采用以下两种方法之一:
方法一:将 XIAO ESP32-C5 连接到 Home Assistant。参考:Connecting XIAO ESP32-C5 to Home Assistant
方法二:使用 MQTT 服务将数据上传到云服务器。请注意,此方法可能需要购买一定数量的服务点数。参考:Goole Cloud

技术支持与产品讨论

感谢您选择我们的产品!我们将为您提供多种支持,确保您在使用我们产品的过程中尽可能顺利。我们提供多种沟通渠道,以满足不同的偏好和需求。

Loading Comments...