ESP32-S3 (ESP-IDF) 动态修改BLE设备名称 完整实现方案

ESP32-S3 (ESP-IDF) 动态修改BLE设备名称 完整实现方案

你需要在ESP32-S3的ESP-IDF开发环境中实现BLE设备名称的动态修改 ,核心思路是通过BLE主机/从机架构的esp_ble_gap_set_device_name()接口,结合事件回调机制,在运行时重新设置设备名称并重启广播(使新名称生效)。以下是包含详细代码、注释、解析的完整实现方案。

一、核心原理说明

  1. 基础接口 :ESP-IDF中esp_ble_gap_set_device_name(const char *name)是修改BLE设备名称的核心API,该接口可在BLE初始化后任意时机调用;

  2. 生效条件 :修改名称后需停止当前广播重新配置广播参数重启广播,否则外设扫描到的仍为旧名称;

  3. 动态触发:可通过按键、定时器、串口指令等方式触发名称修改(示例中用定时器+串口指令双触发演示);

  4. 约束限制: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-0ESP32S3-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. 编译与烧录

  1. 将代码保存为main.c,替换ESP-IDF工程的main目录下的默认文件;

  2. 配置工程:idf.py menuconfigComponent configBluetooth → 确保BluetoothBLE功能开启;

  3. 编译烧录:idf.py -p /dev/ttyUSB0 flash monitor(替换串口端口号)。

5. 验证方法

  1. 打开手机BLE扫描工具(如"nRF Connect");

  2. 观察设备名称变化:

    • 初始名称: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

六、总结

  1. 核心步骤:动态修改BLE名称的关键是"停止广播→设置新名称→重启广播"三步;

  2. 灵活性 :可通过定时器、串口、按键、网络等任意方式触发ble_dynamic_set_name()函数实现名称修改;

  3. 兼容性:该方案适配ESP-IDF v5.0+,低版本需调整部分接口(如广播参数配置);

  4. 扩展建议:实际项目中可将名称存储在NVS中,实现掉电保存;或通过BLE GATT服务接收外部设备指令修改名称。

相关推荐
polarislove021420 小时前
10.1 [ADC] 逐次逼近型ADC-嵌入式铁头山羊STM32笔记
笔记·stm32·嵌入式硬件
谈思汽车20 小时前
TEE or HSM/SE?车载安全技术选型指南
嵌入式硬件·安全·智能汽车·可信执行环境·汽车信息安全
-曾牛21 小时前
【汇编语言入门】从第一个加法程序吃透汇编核心基础
汇编·单片机·嵌入式硬件·汇编语言·病毒分析·lcx·逆向开发
IT方大同21 小时前
ADC&DAC概述
嵌入式硬件
珠海西格电力1 天前
零碳园区如何优化能源结构?
运维·人工智能·物联网·架构·能源
三品吉他手会点灯1 天前
STM32F103 学习笔记-21-串口通信(第3节)-STM32串口初始化结构体和固件库讲解
笔记·stm32·单片机·嵌入式硬件·学习
Lester_11011 天前
单片机EEPROM写入数据之前为什么要先擦除?
单片机·嵌入式软件
点灯小铭1 天前
基于单片机的多功能LCD万年历时钟设计与温度显示系统
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
点灯小铭1 天前
基于单片机的玉米播种机漏播检测装置设计与实现
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
三佛科技-187366133971 天前
FT32F072KBBU7/C8AT7/CBAT7系列32位RISC内核MCU详细解析
单片机·嵌入式硬件