ESP32-S3 (ESP-IDF) 动态修改BLE设备名称 完整实现方案
你需要在ESP32-S3的ESP-IDF开发环境中实现BLE设备名称的动态修改 ,核心思路是通过BLE主机/从机架构的esp_ble_gap_set_device_name()接口,结合事件回调机制,在运行时重新设置设备名称并重启广播(使新名称生效)。以下是包含详细代码、注释、解析的完整实现方案。
一、核心原理说明
-
基础接口 :ESP-IDF中
esp_ble_gap_set_device_name(const char *name)是修改BLE设备名称的核心API,该接口可在BLE初始化后任意时机调用; -
生效条件 :修改名称后需停止当前广播 → 重新配置广播参数 → 重启广播,否则外设扫描到的仍为旧名称;
-
动态触发:可通过按键、定时器、串口指令等方式触发名称修改(示例中用定时器+串口指令双触发演示);
-
约束限制:BLE设备名称长度建议≤16字节(蓝牙规范推荐),超出部分会被截断。
二、环境准备
-
ESP-IDF版本:v5.0+(兼容ESP32-S3,低版本接口略有差异)
-
开发板:ESP32-S3 DevKitC
-
依赖组件:
bt(蓝牙)、driver(GPIO/定时器)、uart(串口,可选)
三、完整代码实现(含详细注释)
C
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"
#include "esp_bt_device.h"
#include "esp_gatt_common_api.h"
#include "driver/uart.h"
#include "driver/gpio.h"
// ===================== 宏定义 =====================
#define TAG "BLE_DYNAMIC_NAME"
#define BLE_DEFAULT_NAME "ESP32S3-BLE-INIT" // 默认设备名称
#define MAX_NAME_LEN 16 // 最大设备名称长度
#define UART_PORT_NUM UART_NUM_0 // 串口端口号(用于指令触发)
#define UART_BAUD_RATE 115200 // 串口波特率
#define TIMER_PERIOD_MS 10000 // 定时器周期(10秒,用于自动修改名称)
#define KEY_GPIO_NUM GPIO_NUM_0 // 按键GPIO(BOOT键,可选触发方式)
// ===================== 全局变量 =====================
static TimerHandle_t ble_name_timer = NULL; // 定时器句柄
static uint8_t name_index = 0; // 名称计数器(用于动态生成不同名称)
static bool ble_init_ok = false; // BLE初始化完成标志
// ===================== BLE GAP事件回调函数 =====================
// 处理BLE GAP层事件(广播启动/停止、名称设置结果等)
static void esp_gap_ble_event_handler(esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t *param)
{
switch (event) {
// BLE GAP初始化完成事件
case ESP_GAP_BLE_INIT_EVT:
if (param->gap_init.status == ESP_OK) {
ESP_LOGI(TAG, "BLE GAP初始化成功");
// 第一步:设置默认设备名称
esp_err_t ret = esp_ble_gap_set_device_name(BLE_DEFAULT_NAME);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "默认名称设置成功: %s", BLE_DEFAULT_NAME);
} else {
ESP_LOGE(TAG, "默认名称设置失败,错误码: %d", ret);
return;
}
// 第二步:配置广播参数
esp_ble_gap_cb_param_t ble_adv_params;
memset(&ble_adv_params, 0, sizeof(ble_adv_params));
// 广播类型:非连接可扫描广播(适合从机)
ble_adv_params.adv_params.adv_type = ADV_TYPE_NONCONN_IND;
ble_adv_params.adv_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; // 公有地址
ble_adv_params.adv_params.channel_map = ADV_CHNL_ALL; // 所有广播通道
ble_adv_params.adv_params.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; // 允许任意扫描/连接
ble_adv_params.adv_params.adv_interval = 0x20; // 广播间隔(单位:0.625ms → 0x20=20ms)
// 设置广播参数
ret = esp_ble_gap_set_advertising_params(&ble_adv_params.adv_params);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "广播参数配置成功");
} else {
ESP_LOGE(TAG, "广播参数配置失败,错误码: %d", ret);
return;
}
// 第三步:启动广播
ret = esp_ble_gap_start_advertising();
if (ret == ESP_OK) {
ESP_LOGI(TAG, "BLE广播启动成功");
ble_init_ok = true; // 标记BLE初始化完成
} else {
ESP_LOGE(TAG, "BLE广播启动失败,错误码: %d", ret);
}
} else {
ESP_LOGE(TAG, "BLE GAP初始化失败,错误码: %d", param->gap_init.status);
}
break;
// 广播启动完成事件
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
if (param->adv_start_cmpl.status == ESP_OK) {
ESP_LOGI(TAG, "广播启动完成,当前设备名称: %s", esp_bt_dev_get_device_name());
} else {
ESP_LOGE(TAG, "广播启动失败,错误码: %d", param->adv_start_cmpl.status);
}
break;
// 广播停止完成事件
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if (param->adv_stop_cmpl.status == ESP_OK) {
ESP_LOGI(TAG, "广播停止完成");
} else {
ESP_LOGE(TAG, "广播停止失败,错误码: %d", param->adv_stop_cmpl.status);
}
break;
// 设备名称设置完成事件(可选监听)
case ESP_GAP_BLE_SET_DEV_NAME_COMPLETE_EVT:
if (param->set_dev_name_cmpl.status == ESP_OK) {
ESP_LOGI(TAG, "设备名称修改完成,新名称: %s", esp_bt_dev_get_device_name());
} else {
ESP_LOGE(TAG, "设备名称修改失败,错误码: %d", param->set_dev_name_cmpl.status);
}
break;
// 其他未处理事件
default:
ESP_LOGI(TAG, "未处理的BLE GAP事件: %d", event);
break;
}
}
// ===================== 动态修改BLE名称核心函数 =====================
/**
* @brief 动态修改BLE设备名称并重启广播
* @param new_name 新的设备名称(长度≤MAX_NAME_LEN)
* @return ESP_OK: 成功,其他: 失败
*/
esp_err_t ble_dynamic_set_name(const char *new_name)
{
// 1. 参数校验
if (!ble_init_ok) {
ESP_LOGE(TAG, "BLE未初始化完成,无法修改名称");
return ESP_ERR_INVALID_STATE;
}
if (new_name == NULL || strlen(new_name) == 0 || strlen(new_name) > MAX_NAME_LEN) {
ESP_LOGE(TAG, "名称非法(空或长度超过%d)", MAX_NAME_LEN);
return ESP_ERR_INVALID_ARG;
}
// 2. 停止当前广播(必须先停止,否则新名称不生效)
esp_err_t ret = esp_ble_gap_stop_advertising();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "停止广播失败,错误码: %d", ret);
return ret;
}
vTaskDelay(pdMS_TO_TICKS(100)); // 短暂延时,确保广播停止完成
// 3. 设置新的设备名称
ret = esp_ble_gap_set_device_name(new_name);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "设置新名称失败,错误码: %d", ret);
return ret;
}
vTaskDelay(pdMS_TO_TICKS(100)); // 短暂延时,确保名称设置完成
// 4. 重新启动广播(使新名称生效)
ret = esp_ble_gap_start_advertising();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "重启广播失败,错误码: %d", ret);
return ret;
}
ESP_LOGI(TAG, "动态修改名称成功,新名称: %s", new_name);
return ESP_OK;
}
// ===================== 定时器回调函数(自动修改名称) =====================
static void ble_name_timer_callback(TimerHandle_t xTimer)
{
// 生成动态名称(示例:ESP32S3-BLE-0/1/2...)
char new_name[MAX_NAME_LEN + 1] = {0};
snprintf(new_name, sizeof(new_name), "ESP32S3-BLE-%d", name_index++);
// 调用动态修改名称函数
ble_dynamic_set_name(new_name);
}
// ===================== 串口初始化(用于指令触发修改名称) =====================
static void uart_init_config(void)
{
uart_config_t uart_config = {
.baud_rate = UART_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_APB,
};
// 配置串口参数
uart_param_config(UART_PORT_NUM, &uart_config);
// 设置串口引脚(TX: GPIO1, RX: GPIO3,ESP32-S3默认)
uart_set_pin(UART_PORT_NUM, GPIO_NUM_1, GPIO_NUM_3, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
// 安装串口驱动
uart_driver_install(UART_PORT_NUM, 1024, 0, 0, NULL, 0);
}
// ===================== 串口接收任务(解析指令修改名称) =====================
static void uart_receive_task(void *pvParameters)
{
uint8_t uart_buf[128] = {0};
while (1) {
// 读取串口数据
int len = uart_read_bytes(UART_PORT_NUM, uart_buf, sizeof(uart_buf) - 1, pdMS_TO_TICKS(100));
if (len > 0) {
uart_buf[len] = '\0'; // 字符串结束符
ESP_LOGI(TAG, "串口接收数据: %s", uart_buf);
// 解析指令:格式为 "NAME:新名称"(例如 NAME:MyBLE-001)
char *name_prefix = strstr((char *)uart_buf, "NAME:");
if (name_prefix != NULL) {
char *new_name = name_prefix + 5; // 跳过"NAME:"
// 调用动态修改名称函数
ble_dynamic_set_name(new_name);
} else {
ESP_LOGW(TAG, "无效指令,格式应为:NAME:新名称");
}
// 清空缓冲区
memset(uart_buf, 0, sizeof(uart_buf));
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// ===================== 按键中断回调(可选触发方式) =====================
static void key_isr_handler(void *arg)
{
// 消抖处理
static uint32_t last_irq_time = 0;
uint32_t now = xTaskGetTickCountFromISR();
if (now - last_irq_time < pdMS_TO_TICKS(50)) {
return;
}
last_irq_time = now;
// 生成动态名称
char new_name[MAX_NAME_LEN + 1] = {0};
snprintf(new_name, sizeof(new_name), "ESP32S3-KEY-%d", name_index++);
// 调用动态修改名称函数(注意:ISR中不能直接调用,需用任务通知)
xTaskNotifyGiveFromISR(xTaskGetHandle("uart_receive_task"), NULL);
// 此处简化处理,实际建议用队列/任务通知触发修改
ESP_LOGI(TAG, "按键触发修改名称: %s", new_name);
// 注意:ISR中避免耗时操作,实际项目中应通过队列发送名称到任务中处理
}
// ===================== 按键初始化 =====================
static void key_init_config(void)
{
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_NEGEDGE, // 下降沿中断(按键按下)
.mode = GPIO_MODE_INPUT, // 输入模式
.pin_bit_mask = (1ULL << KEY_GPIO_NUM), // BOOT键(GPIO0)
.pull_up_en = GPIO_PULLUP_ENABLE, // 上拉使能
.pull_down_en = GPIO_PULLDOWN_DISABLE,
};
gpio_config(&io_conf);
// 安装GPIO中断服务
gpio_install_isr_service(0);
// 注册中断回调
gpio_isr_handler_add(KEY_GPIO_NUM, key_isr_handler, NULL);
}
// ===================== 主函数/初始化 =====================
void app_main(void)
{
ESP_LOGI(TAG, "BLE动态修改名称示例启动");
// 1. 初始化NVS(蓝牙必须依赖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);
// 2. 初始化蓝牙控制器
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "蓝牙控制器初始化失败,错误码: %d", ret);
return;
}
// 3. 启用蓝牙控制器(模式:BLE_ONLY,仅BLE)
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "蓝牙控制器启用失败,错误码: %d", ret);
return;
}
// 4. 初始化BLE主机/从机(GAP层)
ret = esp_bluedroid_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Bluedroid初始化失败,错误码: %d", ret);
return;
}
// 5. 启用Bluedroid
ret = esp_bluedroid_enable();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Bluedroid启用失败,错误码: %d", ret);
return;
}
// 6. 注册BLE GAP事件回调函数
ret = esp_ble_gap_register_callback(esp_gap_ble_event_handler);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "注册GAP回调失败,错误码: %d", ret);
return;
}
// 7. 初始化串口(用于指令触发)
uart_init_config();
// 创建串口接收任务
xTaskCreate(uart_receive_task, "uart_receive_task", 2048, NULL, 5, NULL);
// 8. 初始化按键(可选触发方式)
key_init_config();
// 9. 创建定时器(10秒自动修改名称)
ble_name_timer = xTimerCreate("ble_name_timer",
pdMS_TO_TICKS(TIMER_PERIOD_MS),
pdTRUE, // 周期性触发
NULL,
ble_name_timer_callback);
if (ble_name_timer != NULL) {
xTimerStart(ble_name_timer, 0); // 启动定时器
ESP_LOGI(TAG, "定时器启动成功,周期: %dms", TIMER_PERIOD_MS);
} else {
ESP_LOGE(TAG, "定时器创建失败");
}
// 主循环
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
四、代码解析与关键说明
1. 核心流程梳理
BLE初始化
设置默认名称
配置广播参数
启动广播
触发修改名称
停止当前广播
设置新名称
重启广播
新名称生效
2. 关键函数解析
| 函数名 | 作用 | 注意事项 |
|---|---|---|
esp_ble_gap_set_device_name() |
设置BLE设备名称 | 需在广播停止后调用,否则不生效 |
esp_ble_gap_start_advertising() |
启动BLE广播 | 名称修改后必须重启广播才能生效 |
esp_ble_gap_stop_advertising() |
停止BLE广播 | 修改名称前必须先停止广播 |
esp_bt_dev_get_device_name() |
获取当前设备名称 | 用于验证名称是否修改成功 |
ble_dynamic_set_name() |
封装的动态修改名称函数 | 包含参数校验、停止广播、设置名称、重启广播全流程 |
3. 触发方式说明
方式1:定时器自动触发(示例中10秒一次)
-
定时器回调函数
ble_name_timer_callback中生成动态名称(如ESP32S3-BLE-0、ESP32S3-BLE-1); -
调用
ble_dynamic_set_name()完成修改。
方式2:串口指令触发
-
串口初始化后,创建
uart_receive_task任务监听串口数据; -
指令格式:
NAME:新名称(例如发送NAME:MyBLE-001,设备名称会改为MyBLE-001); -
解析指令后调用
ble_dynamic_set_name()完成修改。
方式3:按键触发(BOOT键)
-
配置GPIO0(BOOT键)为下降沿中断;
-
按键按下后触发中断,生成动态名称并修改(示例中简化处理,实际建议用队列/任务通知)。
4. 编译与烧录
-
将代码保存为
main.c,替换ESP-IDF工程的main目录下的默认文件; -
配置工程:
idf.py menuconfig→Component config→Bluetooth→ 确保Bluetooth和BLE功能开启; -
编译烧录:
idf.py -p /dev/ttyUSB0 flash monitor(替换串口端口号)。
5. 验证方法
-
打开手机BLE扫描工具(如"nRF Connect");
-
观察设备名称变化:
-
初始名称:
ESP32S3-BLE-INIT; -
10秒后自动变为
ESP32S3-BLE-0,20秒后变为ESP32S3-BLE-1,以此类推; -
串口发送
NAME:TestBLE,设备名称会立即变为TestBLE; -
按下BOOT键,名称会变为
ESP32S3-KEY-X(X为计数器值)。
-
五、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 修改名称后扫描到的还是旧名称 | 未停止/重启广播 | 确保调用esp_ble_gap_stop_advertising()后再设置名称,最后重启广播 |
esp_ble_gap_set_device_name()返回失败 |
名称长度超限或BLE未初始化 | 名称长度≤16字节,确保ble_init_ok为true后再调用 |
| 广播启动失败 | 广播参数配置错误 | 检查adv_params参数(如广播间隔、类型)是否符合蓝牙规范 |
| 串口指令无响应 | 串口引脚/波特率配置错误 | 确认串口引脚为GPIO1(TX)/GPIO3(RX),波特率115200 |
六、总结
-
核心步骤:动态修改BLE名称的关键是"停止广播→设置新名称→重启广播"三步;
-
灵活性 :可通过定时器、串口、按键、网络等任意方式触发
ble_dynamic_set_name()函数实现名称修改; -
兼容性:该方案适配ESP-IDF v5.0+,低版本需调整部分接口(如广播参数配置);
-
扩展建议:实际项目中可将名称存储在NVS中,实现掉电保存;或通过BLE GATT服务接收外部设备指令修改名称。