一、蓝牙广播的介绍


1.广播

        广播是两个设备之间建立联系的一个基础,广播设备周期性地发出广播,如果扫描设备接收到了广播且向广播设备发出了扫描请求,广播设备就会发出扫描响应(提供更多的信息),如果扫描者发起连接请求,二者就会建立BLE连接。

        就好比你一直给别人发餐厅传单(广播),但是一般没人接你的传单,有人接传单的话(扫描请求),你就会愿意和他介绍饭菜信息(扫描响应),最后他心动了,请你带他去你们餐厅吃饭(链接请求)。呵呵呵我真是举例子的天才!

        流程:设备广播时通过GAP层配置广播的参数和格式,L2CAP封装广播数据(转成LL层能识别的数据格式),HCI层主机给控制器下发广播命令,LL层生成一些广播帧,PHY层(即硬件层)将数字信号转为射频信号并发射出去。

了解一下:

        蓝牙总共有40个信道,其中有三个广播信道(37,38,39)和37个数据信道,频道范围从2404MHz到2484MHz。


2.广播包

广播设备一直广播的内容其实就是广播包,也就是你发的传单。

广播包的格式如下:



访问地址(Access Address)

固定为 0x8E89BED6(4 字节),用于标识这是一个 BLE 广播包,让扫描设备能识别。


报头(Header 1):控制广播的 “类型与身份”。

注:这报头对应后面代码esp_ble_adv_params_t 的配置

  • PDU Type(bit0~bit3):广播类型,例如:
    • 0000 = ADV_IND(可连接通用广播,对应代码中 ADV_TYPE_IND
    • 0010 = ADV_NONCONN_IND(不可连接广播)
  • TxAdd(bit6):发送地址类型(0 = 公有地址(厂商规定),1 = 随机地址(每次开机后可能会变))。
  • RxAdd(bit7):接收地址类型(广播时为 0,因为是向所有设备发送)。

长度字段(Header 2):控制包的大小

这 1 字节定义了 PDU 的总长度:

length(bit0~bit5):PDU 长度 = 自身地址(6 字节) + 有效载荷(0-31 字节),所以范围是 6-37 字节。(不包含CRC校验)


PDU(协议数据单元):广播的核心内容

PDU 由两部分组成:

  • 自身地址(6 字节):设备的 BLE MAC 地址 [ 公有或随机 ( 由TxAdd说明 ) ],扫描方通过这个地址识别设备。
  • 有效载荷(Payload,0-31 字节):这就是我们在代码中通过 esp_ble_adv_data_t 配置的 广播数据(Adv Data),也是最重要的数据

3. 广播数据

        广播数据里面包含若干个广播结构体,蓝牙规范里面称为AD Structure。如下

        每个结构体里面有三部分,分别是长度(一字节)、类型(一字节)、内容(n字节),其中,长度部分所标示的数据是类型的长度加上内容的长度,即n+1。

        举个例子(如下图),第一个结构体里面的长度是0x04,说明后面的类型加上数据总共有四个字节。

        广播数据总共最多能够有31个字节,如果所需的字节数不超过31个,后面会自动补零 。

        我们如果想知道广播结构体里面的数据的具体含义,需要知道它的类型是什么含义,下面是我们一些常用到的类型:

        这些类型在蓝牙协议规范的文档中都有说明,大家可以自行上网寻找文档查阅。

以下面为例:

        这样广播包就一定程度包含了广博设备的一些基础信息,但是有人就会问了:广播数据只有31字节,会不会不够用呢?那那一些放不下的数据咋办?

        别担心,只要思想不滑坡,办法总比困难多。前面提到 “ 如果扫描设备接收到了广播且向广播设备发出了扫描请求,广播设备就会发出扫描响应(提供更多的信息)”,这里的扫描响应就是用来提供在广播数据里放不下的数据和信息的,这里是广播的内容,具体事情我们后面在谈。

二、广播的代码实现

        在代码实现部分,我们主要是调用主机层的API进行驱动,而在广播流程中,在主机层中参与广播的有GAP层和L2CAP层,但是L2CAP层只负责广播数据的封装,不涉及广播参数的配置,所以我们需要学习的是GAP层的一些函数

        而开启蓝牙广播主要有下列步骤:初始化,配置广播参数和广播数据,GAP层操作

1.初始化

    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_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));


    // 初始化蓝牙控制器
    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(GAP_TAG, "初始化蓝牙控制器失败,错误码: 0x%x", ret);
        return;
    }

    // 启用 BLE 模式
    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
    if (ret != ESP_OK) {
        //  ESP_LOGE(GAP_TAG, "启用蓝牙控制器失败,错误码: 0x%x", ret);
        return;
    }

    // 初始化 Bluedroid 协议栈
    ret = esp_bluedroid_init();
    if (ret != ESP_OK) {
        //   ESP_LOGE(GAP_TAG, "初始化 Bluedroid 失败,错误码: 0x%x", ret);
        return;
    }

    // 启用 Bluedroid 协议栈
    ret = esp_bluedroid_enable();
    if (ret != ESP_OK) {
        //  ESP_LOGE(GAP_TAG, "启用 Bluedroid 失败,错误码: 0x%x", ret);
        return;
    }

主要流程是:NVS 初始化 → 蓝牙控制器初始化 → BLE 模式启用 → 协议栈初始化。这里学会调用这些函数就可以了。

2.配置广播相应参数和广播数据

​
// ========================== 基础配置项 ==========================
// 蓝牙设备名称(广播包中会包含该名称,手机扫描时可直接看到)
#define DEVICE_NAME "ESP32_BLE"

// 自定义服务UUID(2字节,BLE广播中用于标识设备提供的服务类型)
// 注意:此处为16位UUID,若需使用128位UUID需改为16字节数组
static uint8_t service_uuid[] = {0x00, 0xFF};

// 厂商自定义数据(广播包中携带的厂商私有数据,格式:厂商ID + 自定义数据)
// 组成说明:
// - 前2字节:厂商ID(0x1234,BLE要求小端存储,所以实际是0x34, 0x12)
// - 后1字节:自定义数据(0x01,示例表示版本1.0)
static uint8_t manufacturer_data[] = {0x34, 0x12, 0x01};

// ========================== 广播参数配置 ==========================
// BLE广播参数结构体(控制广播的行为:间隔、类型、通道等)
esp_ble_adv_params_t adv_params = {
    .adv_int_min = 0x20,    // 最小广播间隔(单位:0.625ms)→ 0x20*0.625ms = 20ms
    .adv_int_max = 0x40,    // 最大广播间隔(单位:0.625ms)→ 0x40*0.625ms = 40ms
                            // 说明:实际广播间隔会在min~max之间随机,降低同频干扰
    .adv_type = ADV_TYPE_IND,       // 广播类型:ADV_TYPE_IND(可连接的非定向广播)
                                    // 常用类型:
                                    // - ADV_TYPE_IND:可连接、非定向(手机可扫描+连接)
                                    // - ADV_TYPE_NONCONN_IND:不可连接(仅广播,无法连接)
    .channel_map = ADV_CHNL_ALL,    // 广播通道:ADV_CHNL_ALL(37/38/39全通道广播)
                                    // BLE广播固定使用3个通道,全通道保证兼容性
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY // 广播过滤策略
                                    // 此处表示:允许任意设备扫描、任意设备连接
};

// ========================== 广播数据配置 ==========================
// BLE广播数据包配置(控制广播包中携带的内容:设备名、UUID、厂商数据等)
// 触发:调用esp_ble_gap_config_adv_data()后,会触发ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT事件
esp_ble_adv_data_t adv_data = {
    .set_scan_rsp = false,  // 是否为扫描响应包:false(主广播包)、true(扫描响应包)
                            // 说明:主广播包长度有限(31字节),扫描响应包可补充更多数据
    .include_name = true,   // 是否在广播包中包含设备名称:true(包含)
    .include_txpower = NULL,// 是否包含发射功率:NULL(不包含)
    .min_interval = 0,      // 最小连接间隔(仅连接模式有效,广播模式下无意义)
    .max_interval = 0,      // 最大连接间隔(仅连接模式有效,广播模式下无意义)
    .appearance = 0,        // 设备外观(用于标识设备类型,如手环、耳机等)
                            // 0表示无特殊外观,可选值参考:https://www.bluetooth.com/specifications/assigned-numbers/appearance/
    // ---------------- 厂商数据配置(关键错误点) ----------------
    .manufacturer_len = 0,  // ★ 错误:厂商数据长度应设为sizeof(manufacturer_data)=3
                            // 此处设为0会导致厂商数据无法被广播
    .p_manufacturer_data = manufacturer_data, // 厂商数据指针(指向上面定义的数组)
    // ---------------- 服务数据配置(关键错误点) ----------------
    .service_data_len = sizeof(manufacturer_data), // ★ 错误:服务数据未使用,应设为0
    .p_service_data = NULL, // 服务数据指针(未使用,设为NULL)
    // ---------------- 服务UUID配置(关键错误点) ----------------
    .service_uuid_len = 16, // ★ 错误:service_uuid是2字节,应设为sizeof(service_uuid)=2
                            // 设为16会导致UUID解析异常
    .p_service_uuid = service_uuid, // 服务UUID指针(指向上面定义的数组)
    // ---------------- 广播标志位 ----------------
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
                            // 广播标志:
                            // - ESP_BLE_ADV_FLAG_GEN_DISC:通用可发现模式(手机能扫描到)
                            // - ESP_BLE_ADV_FLAG_BREDR_NOT_SPT:不支持经典蓝牙(仅BLE)
};

​
    adv_data定义广播包里面装什么数据(设备名、UUID等)
 adv_params定义广播包怎么发(间隔、类型、通道等)

3.GAP层

/**
 * @brief BLE GAP层事件回调函数
 * @param event 触发的GAP事件类型(如广播数据配置完成、广播启动完成等)
 * @param param 事件对应的参数结构体,包含事件结果、状态码等信息
 * @note ESP32 BLE的GAP操作均为异步触发,所有结果通过此回调函数返回
 */
void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
    // 存储函数调用返回值,用于判断操作是否成功
    esp_err_t ret;

    // 根据不同的GAP事件类型执行对应处理逻辑
    switch (event) {
        // 事件:广播数据配置完成(调用esp_ble_gap_config_adv_data后触发)
        case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
            // 配置完广播数据后,立即启动BLE广播
            ret = esp_ble_gap_start_advertising(&adv_params);
            break;

        // 事件:广播启动操作完成(调用esp_ble_gap_start_advertising后触发)
        case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
            // 检查广播启动状态:失败/成功分支处理
            if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
                // 广播启动失败,param->adv_start_cmpl.status为错误码
            } else {
                // 广播启动成功,设备开始对外发送BLE广播包
            }
            break;

        // 事件:广播停止操作完成(调用esp_ble_gap_stop_advertising后触发)
        case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
            // 检查广播停止状态:失败/成功分支处理
            if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
                // 广播停止失败,param->adv_stop_cmpl.status为错误码
            } else {
                // 广播已成功停止,设备不再发送广播包
            }
            break;

        // 其他未处理的GAP事件(如扫描请求、连接请求等)
        default:
            break;
    }
}

/**
 * @brief 初始化BLE GAP(通用访问配置文件)功能
 * @return esp_err_t 初始化结果:ESP_OK表示成功,其他值为错误码
 * @note 该函数完成两大核心操作:设置设备名称、配置广播数据包
 */
esp_err_t gap_init(void) {
    // 存储函数调用返回值,用于判断每一步操作是否成功
    esp_err_t ret;

    // 第一步:设置BLE设备名称(广播包中会携带此名称,手机扫描时可见)
    ret = esp_ble_gap_set_device_name(DEVICE_NAME);
    // 设备名称设置失败则直接返回错误码,终止初始化流程
    if (ret != ESP_OK) {
        return ret;
    }

    // 第二步:配置BLE广播数据包内容(如设备名、UUID、厂商数据等)
    // 调用后会异步触发ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT事件
    ret = esp_ble_gap_config_adv_data(&adv_data);
    // 广播数据配置失败仅记录状态,不终止流程(可根据需求修改为return ret)

    // GAP初始化核心步骤完成,返回成功
    return ESP_OK;
}

每调用一个 GAP 层 API(如配置广播、启动广播、设置设备名),系统完成该操作后就会触发对回调函数

完整代码如下:(这是完整的正确的代码,修改过的,前面的代码有小纰漏)

​
​
#include "ble_ad.h"
#include "freertos/FreeRTOS.h"
#include "sdkconfig.h"
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <inttypes.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_bt_main.h"
#include "esp_timer.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// GAP 相关日志标签
#define GAP_TAG "GAP_BLE_demo"

// ========================== 基础配置项 ==========================
// 蓝牙设备名称(广播包中会包含该名称,手机扫描时可直接看到)
#define DEVICE_NAME "ESP32_BLE"

// 自定义服务UUID(2字节,BLE广播中用于标识设备提供的服务类型)
// 注意:此处为16位UUID,若需使用128位UUID需改为16字节数组
static uint8_t service_uuid[] = {0x00, 0xFF};

// 厂商自定义数据(广播包中携带的厂商私有数据,格式:厂商ID + 自定义数据)
// 组成说明:
// - 前2字节:厂商ID(0x1234,BLE要求小端存储,所以实际是0x34, 0x12)
// - 后1字节:自定义数据(0x01,示例表示版本1.0)
static uint8_t manufacturer_data[] = {0x34, 0x12, 0x01};

// ========================== 广播参数配置 ==========================
// BLE广播参数结构体(控制广播的行为:间隔、类型、通道等)
esp_ble_adv_params_t adv_params = {
    .adv_int_min = 0x20,    // 最小广播间隔(单位:0.625ms)→ 0x20*0.625ms = 20ms
    .adv_int_max = 0x40,    // 最大广播间隔(单位:0.625ms)→ 0x40*0.625ms = 40ms
                            // 说明:实际广播间隔会在min~max之间随机,降低同频干扰
    .adv_type = ADV_TYPE_IND,       // 广播类型:ADV_TYPE_IND(可连接的非定向广播)
                                    // 常用类型:
                                    // - ADV_TYPE_IND:可连接、非定向(手机可扫描+连接)
                                    // - ADV_TYPE_NONCONN_IND:不可连接(仅广播,无法连接)
    .channel_map = ADV_CHNL_ALL,    // 广播通道:ADV_CHNL_ALL(37/38/39全通道广播)
                                    // BLE广播固定使用3个通道,全通道保证兼容性
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY // 广播过滤策略
                                    // 此处表示:允许任意设备扫描、任意设备连接
};

// ========================== 广播数据配置 ==========================
// BLE广播数据包配置(控制广播包中携带的内容:设备名、UUID、厂商数据等)
// 触发:调用esp_ble_gap_config_adv_data()后,会触发ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT事件
esp_ble_adv_data_t adv_data = {
    .set_scan_rsp = false,  // 是否为扫描响应包:false(主广播包)、true(扫描响应包)
                            // 说明:主广播包长度有限(31字节),扫描响应包可补充更多数据
    .include_name = true,   // 是否在广播包中包含设备名称:true(包含)
    .include_txpower = NULL,// 是否包含发射功率:NULL(不包含)
    .min_interval = 0,      // 最小连接间隔(仅连接模式有效,广播模式下无意义)
    .max_interval = 0,      // 最大连接间隔(仅连接模式有效,广播模式下无意义)
    .appearance = 0,        // 设备外观(用于标识设备类型,如手环、耳机等)
                            // 0表示无特殊外观,可选值参考:https://www.bluetooth.com/specifications/assigned-numbers/appearance/
    // ---------------- 厂商数据配置(关键错误点) ----------------
    .manufacturer_len = 0,  // ★ 错误:厂商数据长度应设为sizeof(manufacturer_data)=3
                            // 此处设为0会导致厂商数据无法被广播
    .p_manufacturer_data = manufacturer_data, // 厂商数据指针(指向上面定义的数组)
    // ---------------- 服务数据配置(关键错误点) ----------------
    .service_data_len = sizeof(manufacturer_data), // ★ 错误:服务数据未使用,应设为0
    .p_service_data = NULL, // 服务数据指针(未使用,设为NULL)
    // ---------------- 服务UUID配置(关键错误点) ----------------
    .service_uuid_len = 16, // ★ 错误:service_uuid是2字节,应设为sizeof(service_uuid)=2
                            // 设为16会导致UUID解析异常
    .p_service_uuid = service_uuid, // 服务UUID指针(指向上面定义的数组)
    // ---------------- 广播标志位 ----------------
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
                            // 广播标志:
                            // - ESP_BLE_ADV_FLAG_GEN_DISC:通用可发现模式(手机能扫描到)
                            // - ESP_BLE_ADV_FLAG_BREDR_NOT_SPT:不支持经典蓝牙(仅BLE)
};
/**
 * @brief BLE GAP层事件回调函数
 * @param event 触发的GAP事件类型(如广播数据配置完成、广播启动完成等)
 * @param param 事件对应的参数结构体,包含事件结果、状态码等信息
 * @note ESP32 BLE的GAP操作均为异步触发,所有结果通过此回调函数返回
 */
void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
    // 存储函数调用返回值,用于判断操作是否成功
    esp_err_t ret;

    // 根据不同的GAP事件类型执行对应处理逻辑
    switch (event) {
        // 事件:广播数据配置完成(调用esp_ble_gap_config_adv_data后触发)
        case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
            // 配置完广播数据后,立即启动BLE广播
            ret = esp_ble_gap_start_advertising(&adv_params);
            break;

            // 事件:广播启动操作完成(调用esp_ble_gap_start_advertising后触发)
        case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
            // 检查广播启动状态:失败/成功分支处理
            if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
                // 广播启动失败,param->adv_start_cmpl.status为错误码
            } else {
                // 广播启动成功,设备开始对外发送BLE广播包
            }
            break;

            // 事件:广播停止操作完成(调用esp_ble_gap_stop_advertising后触发)
        case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
            // 检查广播停止状态:失败/成功分支处理
            if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
                // 广播停止失败,param->adv_stop_cmpl.status为错误码
            } else {
                // 广播已成功停止,设备不再发送广播包
            }
            break;

            // 其他未处理的GAP事件(如扫描请求、连接请求等)
        default:
            break;
    }
}

/**
 * @brief 初始化BLE GAP(通用访问配置文件)功能
 * @return esp_err_t 初始化结果:ESP_OK表示成功,其他值为错误码
 * @note 该函数完成两大核心操作:设置设备名称、配置广播数据包
 */
esp_err_t gap_init(void) {
    // 存储函数调用返回值,用于判断每一步操作是否成功
    esp_err_t ret;

    // 第一步:设置BLE设备名称(广播包中会携带此名称,手机扫描时可见)
    ret = esp_ble_gap_set_device_name(DEVICE_NAME);
    // 设备名称设置失败则直接返回错误码,终止初始化流程
    if (ret != ESP_OK) {
        return ret;
    }

    // 第二步:配置BLE广播数据包内容(如设备名、UUID、厂商数据等)
    // 调用后会异步触发ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT事件
    ret = esp_ble_gap_config_adv_data(&adv_data);
    // 广播数据配置失败仅记录状态,不终止流程(可根据需求修改为return ret)

    // GAP初始化核心步骤完成,返回成功
    return ESP_OK;
}

void system_run() {
    // 初始化 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);

    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));


    // 初始化蓝牙控制器
    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(GAP_TAG, "初始化蓝牙控制器失败,错误码: 0x%x", ret);
        return;
    }

    // 启用 BLE 模式
    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
    if (ret != ESP_OK) {
        //  ESP_LOGE(GAP_TAG, "启用蓝牙控制器失败,错误码: 0x%x", ret);
        return;
    }

    // 初始化 Bluedroid 协议栈
    ret = esp_bluedroid_init();
    if (ret != ESP_OK) {
        //   ESP_LOGE(GAP_TAG, "初始化 Bluedroid 失败,错误码: 0x%x", ret);
        return;
    }

    // 启用 Bluedroid 协议栈
    ret = esp_bluedroid_enable();
    if (ret != ESP_OK) {
        //  ESP_LOGE(GAP_TAG, "启用 Bluedroid 失败,错误码: 0x%x", ret);
        return;
    }


    // 注册 GAP 回调(放在广播配置前)
    ret = esp_ble_gap_register_callback(esp_gap_cb);
    if (ret != ESP_OK) {
        //   ESP_LOGE(GAP_TAG, "注册 GAP 回调失败,错误码: 0x%x", ret);
        return;
    }

    // 初始化 GAP 广播
    ret = gap_init();
    if (ret != ESP_OK) {
        //  ESP_LOGE(GAP_TAG, "GAP 初始化失败");
        return;
    }
    // 主循环
    while (1) {
        vTaskDelay(10);
    }
}

​

​

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐