下面的示例展示了一个 ESP32-C3 上 Wi-Fi 和 BLE Mesh 同时工作的方案,以及示例代码和相关注意事项。由于 BLE Mesh 和 Wi-Fi 都要占用 2.4 GHz 射频,需要进行**协议栈共存(coexistence)**的配置。本文将尽可能给出详细的思路和代码示例,以便读者在 ESP-IDF 环境下进行编译和二次开发。
说明
- ESP32-C3 不支持经典蓝牙(BR/EDR),只能使用 BLE(蓝牙低功耗),因此我们使用 ESP BLE Mesh(基于 BLE)来实现蓝牙 Mesh 功能。
- 下面的示例基于 ESP-IDF v4.4 及以上 并启用了
CONFIG_IDF_TARGET_ESP32C3
。- 为了演示,我们会写一个简单的 BLE Mesh 节点和Wi-Fi STA 模式同时运行的示例,供大家参考。
- 真正商用前,请根据项目需要对 BLE Mesh 功能(如模型、配置、消息处理等)进行完善。
一、总体设计思路
-
BLE Mesh 初始化
- ESP BLE Mesh 和普通 BLE 应用略有不同,需要初始化 BLE Mesh 专用的 Host 和 Controller,并注册相应的回调函数、配置各个 Model(Generic OnOff、Vendor Model 等)。
- Mesh 节点需要进行网络层的配置(Provisioning),要么作为普通 Node,要么作为 Provisioner。这里演示 Node 的简化流程。
-
Wi-Fi 初始化
- 使用 STA(station)模式连入路由器,也可以视需求变成 AP 或双模(AP+STA)。
- 需要配置好 Wi-Fi 共存:ESP-IDF 默认在硬件层面支持 BT/Wi-Fi 共存,SDK 会在运行时调度射频。
- 建议只在 BLE 模式下使用(
CONFIG_BTDM_CTRL_MODE_BLE_ONLY
),因为 ESP32-C3 只有 BLE 控制器,没有经典蓝牙的硬件支持。
-
任务和事件处理
- BLE Mesh 使用事件回调机制(
esp_ble_mesh_register_prov_callback
、esp_ble_mesh_register_config_server_callback
等)处理各类事件,如完成配网、接收到消息等。 - Wi-Fi 使用
esp_event_loop_create_default()
+esp_event_handler_register()
来监听 Wi-Fi 连接、断开事件。 - 两套事件都在各自的回调中执行,互不干扰,但需要注意内存和实时性,以免过度占用资源。
- BLE Mesh 使用事件回调机制(
-
通信示例
- BLE Mesh 端可以通过 Mesh 网络进行节点间的消息收发;
- Wi-Fi 端可以通过 TCP/HTTP/MQTT 等方式与服务器通信;
- 在应用层,可根据需要编写一个“网关”或“桥接”逻辑,把 BLE Mesh 消息透传到服务器,或从服务器下发命令到 Mesh 节点。
二、sdkconfig 重点配置
在 menuconfig
中需要关注以下几点(仅举例):
- Target: 选择
ESP32C3
(如果使用的是 ESP32-C3)。 - Component config -> Bluetooth:
- 勾选
[*] Bluetooth
- 勾选
[*] Bluetooth LE SPP
,[*] BLE Mesh support
(或类似选项) Bluetooth controller
中选择BLE only
- 勾选
- Component config -> ESP BLE Mesh:
- 根据需求启用
BLE Mesh Node
或BLE Mesh Provisioner
功能; - 如果需要打开日志,可以设置调试等级。
- 根据需求启用
- Component config -> Wi-Fi:
- 确保
Wi-Fi
驱动已启用 - 根据需求配置 STA 或 AP 的最大连接数、事件回调等
- 确保
- PSRAM(如无则忽略): 如果是 ESP32-C3 没有外接 PSRAM,那么无需启用。
完成后保存并退出 menuconfig
。
三、示例代码详解
下面是一份简化的示例,实现了:
- 初始化 Wi-Fi (STA 模式) 并连接到路由器
- 初始化 BLE Mesh 节点 (Generic OnOff Server)
- 处理 BLE Mesh 事件回调、Wi-Fi 事件回调
- 两套协议同时工作
注意:示例中省略了大量“正式 BLE Mesh Node”所需的模型定义和事件处理,仅作演示。如果你要实现真正可用的 BLE Mesh 功能,请参考 Espressif 官方示例:ESP BLE Mesh Examples。
1. 头文件引用与全局变量
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
// Wi-Fi 相关
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "lwip/sockets.h"
// BLE Mesh 相关
#include "esp_ble_mesh_defs.h"
#include "esp_ble_mesh_common_api.h"
#include "esp_ble_mesh_networking_api.h"
#include "esp_ble_mesh_provisioning_api.h"
static const char *TAG = "BLE_MESH_WIFI_C3";
#define EXAMPLE_WIFI_SSID "YOUR_SSID"
#define EXAMPLE_WIFI_PASS "YOUR_PASS"
// provision 数据,可根据需求修改
static esp_ble_mesh_prov_t prov = {
.uuid = { 0x32, 0xC3, 0xAB, 0xCD, 0x00, 0x11, 0x22, 0x33, // 随便写
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B },
// 其他回调在 init 里注册
};
// 简单示例: 定义一个 Generic OnOff Server 模型
ESP_BLE_MESH_MODEL_PUB_DEFINE(gen_onoff_pub, 2 + 3, ROLE_NODE);
static esp_ble_mesh_model_t sig_models[] = {
ESP_BLE_MESH_MODEL_CFG_SRV(), // 配置服务器模型
ESP_BLE_MESH_MODEL_GEN_ONOFF_SRV(&gen_onoff_pub, NULL) // Generic OnOff
};
// 元素
static esp_ble_mesh_elem_t elements[] = {
ESP_BLE_MESH_ELEMENT(0, sig_models, ESP_BLE_MESH_MODEL_NONE),
};
// 完整的组合
static esp_ble_mesh_comp_t composition = {
.cid = 0x02E5, // 这里是 Espressif 的公司 ID
.elements = elements,
.element_count = ARRAY_SIZE(elements),
};
上面定义了一个最基本的 BLE Mesh 节点:
- 包含了配置服务器模型
CFG_SRV
; - 一个通用 OnOff 服务器模型;
- 有 1 个 element;
prov
中保存了设备的 UUID,实际项目中要根据自己的硬件信息来设置。
2. BLE Mesh 事件回调
下面是一个示例性的事件回调函数,用于处理 BLE Mesh Provisioning 事件和配置服务器事件(如成功配网、收到配置命令等)。根据需要还可增加更多模型的回调处理(如通用 OnOff Server 回调等)。
static void ble_mesh_prov_callback(esp_ble_mesh_prov_cb_event_t event,
esp_ble_mesh_prov_cb_param_t *param)
{
switch (event) {
case ESP_BLE_MESH_PROV_REGISTER_COMP_EVT:
ESP_LOGI(TAG, "BLE Mesh Provisioning注册完成");
break;
case ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT:
ESP_LOGI(TAG, "BLE Mesh Node 可以被绑定进行配网");
break;
case ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT:
ESP_LOGI(TAG, "配网链接已打开");
break;
case ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT:
ESP_LOGI(TAG, "配网链接已关闭");
break;
case ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT:
ESP_LOGI(TAG, "BLE Mesh 配网完成, 得到 NetKeyIdx=%d, Addr=0x%04X",
param->node_prov_complete.net_idx,
param->node_prov_complete.addr);
break;
case ESP_BLE_MESH_NODE_PROV_RESET_EVT:
ESP_LOGI(TAG, "设备被重置,需要重新配网");
break;
default:
ESP_LOGW(TAG, "Unhandled provisioning event: %d", event);
break;
}
}
// 配置服务器回调(这里只演示简单打印,实际需处理AppKey绑定等)
static void ble_mesh_config_server_cb(esp_ble_mesh_cfg_server_cb_event_t event,
esp_ble_mesh_cfg_server_cb_param_t *param)
{
switch (event) {
case ESP_BLE_MESH_CFG_SERVER_STATE_CHANGE_EVT:
ESP_LOGI(TAG, "配置服务器 State 改变");
break;
default:
ESP_LOGW(TAG, "Unhandled config server event: %d", event);
break;
}
}
若需要处理 Generic OnOff Server 的事件,还需注册
esp_ble_mesh_register_generic_server_callback()
并实现对应的回调。
3. 初始化 BLE Mesh
在主函数或单独封装的初始化函数中,执行 BLE Mesh 的初始化:
static void ble_mesh_init(void)
{
esp_err_t err;
// 1. 初始化 BLE Mesh 核心栈
esp_ble_mesh_init_param_t init_param = {
.prov = &prov,
.comp = &composition,
};
// 注册回调
err = esp_ble_mesh_register_prov_callback(ble_mesh_prov_callback);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to register prov callback");
return;
}
err = esp_ble_mesh_register_config_server_callback(ble_mesh_config_server_cb);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to register config server callback");
return;
}
// 如果有 Generic Server,则还要 register_generic_server_callback(...)
// ...
// 2. 初始化 BLE Mesh
err = esp_ble_mesh_init(&init_param);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_mesh_init failed (err %d)", err);
return;
}
// 3. Enable Provisioning (使能节点可被配网)
err = esp_ble_mesh_node_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable mesh node prov");
return;
}
// 4. 打开蓝牙低功耗子系统
// ESP-IDF 中,esp_ble_mesh_init() 已经做了大部分 BT 控制器/主机初始化
// 不过可以根据需要选择性地控制广播功率等
ESP_LOGI(TAG, "BLE Mesh Node init done, waiting for provisioning...");
}
4. 初始化 Wi-Fi 并连接
4.1 Wi-Fi 事件回调
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_LOGI(TAG, "Wi-Fi STA start, connecting...");
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGW(TAG, "Wi-Fi STA disconnected, retry...");
esp_wifi_connect(); // 断线重连
} 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: %s",
ip4addr_ntoa(&event->ip_info.ip));
}
}
4.2 Wi-Fi STA 初始化
static void wifi_init_sta(void)
{
// 初始化NVS,这样才能读写Wi-Fi配置
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_ERROR_CHECK(esp_event_loop_create_default());
// 初始化 Wi-Fi
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 注册事件回调
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&event_handler, NULL));
// 设置 Wi-Fi 模式
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// 配置 STA 的 SSID/PASS
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_WIFI_SSID,
.password = EXAMPLE_WIFI_PASS,
.scan_method = WIFI_FAST_SCAN
},
};
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
// 启动 Wi-Fi
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_sta finished.");
}
注意:
- 如果需要在应用中动态配置 Wi-Fi 参数,可以通过 SmartConfig 或命令行等方式获取 SSID、密码,然后再调用
esp_wifi_set_config()
。- 如果使用
menuconfig
预先配置,也可以在 Kconfig 中让用户填写 Wi-Fi 信息。
5. 入口函数 app_main()
一个最简单的“先启动 Wi-Fi,再启动 BLE Mesh”的主函数如下所示:
void app_main(void)
{
ESP_LOGI(TAG, "==== ESP32-C3 BLE Mesh + Wi-Fi demo start ====");
// 1. 初始化并连接 Wi-Fi
wifi_init_sta();
// 2. 初始化 BLE Mesh 核心
ble_mesh_init();
// 后面可创建任务,或者直接在这里做些业务逻辑
// 例如循环监测一些传感器数据,通过 BLE Mesh / Wi-Fi 上报等
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "Main loop running, Wi-Fi + BLE Mesh coexisting...");
}
}
- 这样一来,设备上电后会先连接 Wi-Fi,然后开启 BLE Mesh 处于可配网状态;
- 如果手机使用 BLE Mesh App(如 nRF Mesh 或 Esp BLE Mesh APP)对设备进行 Mesh 配网,就会触发前述
ble_mesh_prov_callback()
中的一系列事件。- 同时,通过 Wi-Fi 也可以对设备进行 OTA、MQTT 通信、HTTP 请求等操作。
四、注意事项和可能的扩展
-
内存使用
- ESP32-C3 的内存较紧张,同时跑 Wi-Fi STA 和 BLE Mesh 时,一定要注意堆剩余情况;
- 如果 BLE Mesh 的网络规模较大,或 Wi-Fi 需要加载 TLS/HTTPD 等,可能要对堆进行优化,也可以在
menuconfig
中把日志等级调低、精简组件。
-
BLE Mesh 的模型扩展
- 如果你要实现灯控(通用 OnOff)、传感器模型、Vendor 模型等,需要在
elements[]
里添加更多模型,并在回调中处理对应的事件; - Espressif 官方有比较完整的例程,比如
examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/
,可参考其处理流程。
- 如果你要实现灯控(通用 OnOff)、传感器模型、Vendor 模型等,需要在
-
并发通信
- 当 BLE Mesh 处于激烈的组网或数据广播时,Wi-Fi 的吞吐可能会降低,反之亦然(2.4GHz 共存);
- ESP-IDF 的硬件共存机制(Coexistence)会在后台调度射频资源,通常无需开发者手动干预,但要预留足够 CPU/内存,提高 RTOS 优先级等。
- 如果对实时性要求特别高,需要在
menuconfig -> Component config -> Bluetooth -> Bluetooth controller -> Coexistence configuration
里做相关设置。
-
安全与加密
- BLE Mesh 默认使用加密的网钥、应用钥等,但需要正确的 Provisioning 流程和数据保护。
- Wi-Fi 端也需注意启用 WPA2/WPA3,或者在上层数据加上 SSL/TLS 等,防止窃听。
-
功耗
- 同时开启 Wi-Fi + BLE Mesh 会加大功耗,对低功耗设备来说不一定合适;
- 可以在非必须时段关闭 Wi-Fi 或降低 BLE Mesh 广播频率、进入低功耗模式。
-
调试与日志
- 在
menuconfig
可以打开ESP_BLE_MESH_LOG_LEVEL_DEBUG
或更高等级,以便查看更详细的 BLE Mesh 调试信息; - 也要注意大量日志会占用 CPU 和内存,最终产品中可设置到 INFO 或 WARN 等更低等级。
- 在
五、总结
- 硬件层面:ESP32-C3 内部有射频共存调度,可以支持 BLE 和 Wi-Fi 在 2.4GHz 上同时工作;
- 软件层面:在 ESP-IDF 中分别初始化 Wi-Fi 和 ESP BLE Mesh 协议栈,并注册各自的事件回调;
- 资源管理:需要关注内存、CPU、功耗等问题,合理设置日志等级、模型数量,以及尽量精简不必要的功能;
- 扩展:在此基础上可以开发更高级的功能,例如在 Wi-Fi 侧做网关,把 BLE Mesh 网络的数据上报到云端,或从云端下发命令到 Mesh 节点。
以上就是一个在 ESP32-C3 上同时启用 BLE Mesh(节点)和 Wi-Fi(STA) 的整体方案与代码示例,涵盖从 menuconfig
配置,到事件回调处理,再到并发注意事项。希望能帮助大家快速上手并根据自身需求进行定制。祝开发顺利!