ESP32 VHCI实现BLE广播,就是这么神奇

Posted Wireless_Link

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ESP32 VHCI实现BLE广播,就是这么神奇相关的知识,希望对你有一定的参考价值。

零. 声明


本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下:

第一篇:ESP-IDF基本介绍,主要会涉及模组,芯片,开发板的介绍,环境搭建,程序编译下载,启动流程等一些基本的操作,让你对ESP-IDF开发有一个总体的认识,比我们后续学习打下基础!

第二篇:ESP32-IDF外设驱动介绍,主要会根据esp-idf现有的driver,提供各个外设的驱动,比如LED,OLED,SPI LCD,TOUCH,红外,Codec ic等等,在这一篇中,我们不仅仅来做外设驱动,还会对常用的外设总线做一个介绍,让大家知其然又知其所以然!

第三篇:目前比较火热的GUI LVGL介绍,主要会设计LVGL7.1,LVGL8的移植介绍,并且也会介绍各个组件,知道原理后,最后,我们会推出一款组态软件来构建我们的GUI,来提升我们的效率!

第四篇:ESP32-蓝牙,熟悉我的,应该都知道,我即使从事蓝牙协议栈的开发的,所以这个是我们独有的优势,在这一篇章,我们会提供不仅仅是蓝牙应用方法的知识,也会应用结合蓝牙底层协议栈的理论,让你彻底从上到下打通蓝牙任督二脉!

第五篇:Wi-Fi介绍,熟悉我的,应该也知道,我们也做过一款sdio wifi的驱动教程板子,所以在wifi这方面我们也是有独有的优势,在这一篇章,我们同样不仅仅提供Wi-Fi应用方面的知识,也会结合底层理论,让你对Wi-Fi有一个清晰的认知!

另外,我们的教程包括但是不局限于以上篇章,为了给你一个更好的导航,以下信息尤其重要,请详细查看!!

------------------------------------------------------------------------------------------------------------------------------------------

购买开发板(点击我)

ESP32系列文章目录(点击我)

Github代码仓库(点击我)

蓝牙交流扣扣群:539357317

微信公众号↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

​​​​

------------------------------------------------------------------------------------------------------------------------------------------

一. 整体介绍

此文章主要介绍基于ESP32 VHCI的架构实现BLE的搜索,也就是不使用默认的Host API,自己编写一部分代码来实现功能,具体ESP32的架构如下图所示:

那我们做的事情是什么呢?我们相当于把默认的Host拿掉,也就是如图所示的bluedroid部分,写Host部分的广播实现

此文章算是熟悉一个VHCI的开发模式,不管对于默认的Host(Bluedroid/nimble)甚至自己移植进来一个Host都有很大的帮助,我就算起到一个抛砖引玉的作用吧!

二. menuconfig实现

我们虽然不用ESP32的默认Host,但是我们要用他默认的Controller,所以还是要配置一下的,一共三个地方需要特别留意一下,其他保持默认,如下图所示:

1.Bluetooth controller mode

2.HCI mode

3.bluetooth Host

三. 程序实现

1.初始化NVS

/* Initialize NVS — it is used to store PHY calibration data */
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 );

这一段必须要加上,主要是用于存储PHY的信息,否则无法正常使用controller

2.初始化controller,注册Controller的callback函数实现

esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();

ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
if (ret) 
    ESP_LOGI(TAG, "Bluetooth controller release classic bt memory failed: %s", esp_err_to_name(ret));
    return;


if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) 
    ESP_LOGI(TAG, "Bluetooth controller initialize failed: %s", esp_err_to_name(ret));
    return;


if ((ret = esp_bt_controller_enable(ESP_BT_MODE_BLE)) != ESP_OK) 
    ESP_LOGI(TAG, "Bluetooth controller enable failed: %s", esp_err_to_name(ret));
    return;


esp_vhci_host_register_callback(&vhci_host_cb);

此段我们在介绍esp32 controller api的时候已经详细介绍,看API名字也知道主要在做什么事情,没什么难度,我们来看下注册给controller的callback的实现

static void controller_rcv_pkt_ready(void)

    printf("controller rcv pkt ready\\n");



static int host_rcv_pkt(uint8_t *data, uint16_t len)

    printf("host rcv pkt: ");
    for (uint16_t i = 0; i < len; i++) 
        printf("0x%02x ", data[i]);
    
    printf("\\n");
    return 0;


static esp_vhci_host_callback_t vhci_host_cb = 
    controller_rcv_pkt_ready,
    host_rcv_pkt
;

以上代码,我们接受到数据,并没有做什么事情,只是打印下数据的hex而已!

3.实现HCI 的广播

while (1) 
    bool send_avail = false;
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    send_avail = esp_vhci_host_check_send_available();
    if (send_avail) 
        switch (cmd_cnt) 
            case 0: hci_cmd_send_reset(); ++cmd_cnt; break;
            case 1: hci_cmd_send_ble_set_adv_param(); ++cmd_cnt; break;
            case 2: hci_cmd_send_ble_set_adv_data(); ++cmd_cnt; break;
            case 3: hci_cmd_send_ble_adv_start(); ++cmd_cnt; break;
        
    
    printf("BLE Advertise, flag_send_avail: %d, cmd_sent: %d\\n", send_avail, cmd_cnt);

在代码中,我们不去解析hci event的parse,只是一个个命令的发送hci command来实现广播,此部分也是我们本文章的重点,下面我就来一一详细介绍下

3.1 hci广播的流程

如果不考虑整个蓝牙Host的健壮性以及可用性,只考虑用ESP32 VHCI架构实现BLE广播功能,那么其实4个步骤就能实现,分别如下:

1) 发送HCI reset command

2) 发送ble set adv param,也就是设置广播的参数

3) 发送ble set adv data,也就是设置广播的数据

4) 发送ble adv start command,也就是开启广播

在后面的小节我们再一一介绍下每个command的格式以及作用!

3.2 hci command的格式

我们知道了步骤,并且知道了发送哪些command,但是command的格式是什么呢?那么这个算是一个比较大的话题,牵扯到蓝牙core spec hci章节的内容,我们本章本着应用文章,此部门我们简单的一笔带过,如果想彻底了解内部原理,那么我建议你看下这两篇文章:

蓝牙HCI command/event/acl/sco格式介绍_Wireless_Link的博客-CSDN博客

蓝牙传输介质Transport UART H4(RS232)介绍_Wireless_Link的博客-CSDN博客_蓝牙通过什么介质传输

以上两篇文章,分别介绍H4以及HCI的格式,好了,我们回归主题来介绍下HCI command的格式。

Opcode:每个命令被分配一个2字节的操作码(opcode),用来唯一地识别不同类型的命令,操作码(opcode)参数分为两个字段,称为操作码组字段(Opcode Group Field, OGF)和操作码命令字段(Opcode Command Field, OCF)。其中OGF占用高6bit字节,OCF占用低10bit字节。

一共有以下几组OGF:

1)Link Control commands, the OGF is defined as 0x01.链路控制OGF,也就是控制蓝牙芯片跟remote沟通的命令

2)Link Policy commands, the OGF is defined as 0x02,链路策略OGF,也就是写一些Policy,比如转换角色等

3)HCI Control and Baseband commands, the OGF is defined as 0x03,控制本地芯片跟基带的OGF。比如reset本地芯片等。

4)Informational Parameters commands, the OGF is defined as 0x04。读取信息的OGF,比如读取本地芯片的LMP版本呢,支持的command,蓝牙地址等,

5)status parameters commands, the OGF is defined as 0x05,状态参数OGF,比如读取RSSI等。

6)Testing commands, the OGF is defined as 0x06,测试命令的OGF,比如让芯片进入测试模式(DUT,device under test)

7)LE Controller commands, the OGF code is defined as 0x08,BLE 的comand

8)vendor-specific debug commands,the OGF code is defined as 0x3F,此部分是vendor定义的,也就是芯片厂商为了扩展core文档的HCI command定义

OCF众多,在每个OGF下都有一堆的OCF定义

Parameter Total Length:后续参数的长度

Parameter:每个command的para不同

3.3 hci reset命令

hci command的命令如下:

OGF是0x03,OCF是0x03

hci reset作用如下:

The HCI_Reset command willreset the Controller and the Link Manager on the BR/EDR Controller or the Link Layer on an LE Controller. If the Controller supports both BR/EDR and LEthen the HCI_Reset command shall reset the Link Manager, Baseband and Link Layer. The HCI_Reset command shall not affect the used HCI transport layer since the HCI transport layers may have reset mechanisms of their own. After the reset is completed, the current operational state will be lost, the Controller will enter standby mode and the Controller will automatically revert to the default values for the parameters for which default values are defined in the specification.

Note: The HCI_Reset command will not necessarily perform a hardware reset. This is implementation defined.

The Host shall not send additional HCI commands before the HCI_Command_Complete event related to the HCI_Reset command has been received。

函数实现如下:

/*  HCI Command Opcode group Field (OGF) */
#define HCI_GRP_HOST_CONT_BASEBAND_CMDS    (0x03 << 10)            /* 0x0C00 */
#define HCI_GRP_BLE_CMDS                   (0x08 << 10)

/*  HCI Command Opcode Command Field (OCF) */
#define HCI_RESET                       (0x0003 | HCI_GRP_HOST_CONT_BASEBAND_CMDS)

uint16_t make_cmd_reset(uint8_t *buf)

    UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);
    UINT16_TO_STREAM (buf, HCI_RESET);
    UINT8_TO_STREAM (buf, 0);
    return HCI_H4_CMD_PREAMBLE_SIZE;

static void hci_cmd_send_reset(void)

    uint16_t sz = make_cmd_reset (hci_cmd_buf);
    esp_vhci_host_send_packet(hci_cmd_buf, sz);

组成的数据格式是:0x01 ,0x03 0x0c 0x00

3.4 hci set adv param命令

hci command的格式如下:

OGF=0x08,OCF=0x06

我们来看下这个command的格式:

这个命令是设置广播的参数,其中参数如下:

Advertising_Interval_Min:广播的最小间隔

Advertising_Interval_Max:广播的最大间隔

设备每次广播时,会在3个广播信道上(根据设置的channel map而决定哪个信道)发送相同的报文。这些报文被称为一个广播事件。除了定向报文以外,其他广播事件均可以选择“20ms ~ 10.24s”不等的间隔。两个相邻广播事件之间的时间称为广播间隔。

但是,设备周期性的发送广播会有一个问题:由于设备间的时钟会不同程度的漂移,两个设备可能在很长一段时间同时广播而造成干扰。为防止这一情况的发生,除定向广播之外的其他广播类型,发送时间均会被扰动。实现该扰动的方式为,在上一次广播事件后加入“0 ~ 10ms”的随机延时。这意味着,即使两个设备广播间隔相同,并在相同信道及时间点上发送造成了冲突,但它们发送下一个广播事件时也会有很大可能不再冲突。

所以,两个相邻的广播事件的之间的时间间隔(T_advEvent)为:T_AdvEvent = advInterval + advDelay

其中,advInterval 必须是“0.625ms”的整数倍,范围是“20ms ~ 10.24s”之间。对于可扫描非定向广播和不可连接非定向广播这两种广播类型,该值最好不小于100ms,即(160个0.625ms)。advDelay是Link Layer(链接层)分配的一个伪随机数,它的范围为“0 ~ 10ms”。

广播包的截图如下:

当然,实际设置过程中没有广播间隔参数,而是设置Advertising_Interval_Min(最小广播间隔)和Advertising_Interval_Max(最大广播间隔)这两个参数来调整广播间隔,它们都是以“0.625ms”为单位,如果要固定广播间隔为某一个值,只需要将这两个参数设置为同一个有效数值即可。

Advertising_Type:广播类型

一共分为四种广播类型:

1)可连接的非定向广播(Connectable Undirected Event Type):这是一种用途最广的广播类型,包括广播数据和扫描响应数据,它表示当前设备可以接受其他任何设备的连接请求。

鉴于此种广播类型用的最多,下面我们来讨论一下此类型下广播事件中广播包的发送情况,另外要注意在一个广播事件中,前一个“ADV_IND PDUs”的开始到相邻的下一个“ADV_IND PDUs”的开始处的时间要小于等于 10ms :

第一种情况:仅仅有广播 PDUs 。截图显示如下:

第二种情况:在广播事件的中间有“SCAN_REQ”和“SCAN_RSP PDUs”。截图显示如下:

第三种情况:在广播事件的结尾有“SCAN_REQ”和“SCAN_RSP PDUs”。截图显示如下:

第四种情况:在广播事件的中间接收到“CONNECT_REQ PDU”的情况。截图显示如下:

此情况下,广播事件会关闭,不会继续在下一个信道上广播。

2)可连接的定向广播(Connectable Directed Event Type):定向广播类型是为了尽可能快的建立连接。这种报文包含两个地址:广播者的地址和发起者的地址。发起者收到发给自己的定向广播报文之后,可以立即发送连接请求作为回应。

定向广播类型有特殊的时序要求。完整的广播事件必须每3.75ms重复一次。这一要求使得扫描设备只需扫描3.75ms便可以收到定向广播设备的消息。

当然,如此快的发送会让报文充斥着广播信道,进而导致该区域内的其他设备无法进行广播。因此,定向广播不可以持续1.28s以上的时间。如果主机没有主动要求停止,或者连接没有建立,控制器都会自动停止广播。一旦到了1.28s,主机便只能使用间隔长得多的可连接非定向广播让其他设备来连接。

当使用定向广播时,设备不能被主动扫描。此外,定向广播报文的净荷中也不能带有其他附加数据。该净荷只能包含两个必须的地址。

3)不可连接的非定向广播(Non-connectable Undirected Event Type):仅仅发送广播数据。

4)可扫描的非定向广播(Scannable Undirected Event Type):这种广播不能用于发起连接,但允许其他设备扫描该广播设备。这意味着该设备可以被发现,既可以发送广播数据,也可以响应扫描发送扫描回应数据,但不能建立连接。这是一种适用于广播数据的广播形式,动态数据可以包含于广播数据之中,而静态数据可以包含于扫描响应数据之中。

注意:所谓的定向和非定向针对的是广播的对象,如果是针对特定的对象进行广播(在广播包PDU中会包含目标对象的MAC)就是定向广播,反之就是非定向。可连接和不可连接是指是否接受连接请求,如果是不可连接的广播类型,它将不回应连接请求。可扫描广播类型是指回应扫描请求。

不同的广播类型对扫描请求和连接请求的不同结果如下图:

Own_Address_Type:本地地址类型

Peer_Address_Type:对端地址类型

Public Device Addrss:公有设备地址是设备所特有的并且是不可改变的。类似网络设备的MAC地址,它的长度为48位。由两部分组成:

Ramdom Device Address:随机设备地址(私有设备地址),它也是48位。组成如下所示:

Peer_Address:对端蓝牙地址

Advertising_Channel_Map:广播信道表

决定在哪个信道上广播,如果0x07就是在广播全信道(37,38,39)

Advertising_Filter_Policy:过滤策略

对应上图的内容解释如下:

接受任何设备的扫描请求或连接请求。(Value:0x00)

仅仅接受白名单中的特定设备的扫描请求,但是接受任何设备的连接请求。(Value:0x01)

接受任何设备的扫描请求,但仅仅接受白名单中的特定设备的连接请求。(Value:0x02)

仅仅接受白名单中的特定设备的扫描请求和连接请求。(Value:0x03)

整个实现的代码如下:

/*  HCI Command Opcode group Field (OGF) */
#define HCI_GRP_HOST_CONT_BASEBAND_CMDS    (0x03 << 10)            /* 0x0C00 */
#define HCI_GRP_BLE_CMDS                   (0x08 << 10)

/*  HCI Command Opcode Command Field (OCF) */
#define HCI_RESET                       (0x0003 | HCI_GRP_HOST_CONT_BASEBAND_CMDS)
/* Advertising Commands. */
#define HCI_BLE_WRITE_ADV_ENABLE        (0x000A | HCI_GRP_BLE_CMDS)
#define HCI_BLE_WRITE_ADV_DATA          (0x0008 | HCI_GRP_BLE_CMDS)
#define HCI_BLE_WRITE_ADV_PARAMS        (0x0006 | HCI_GRP_BLE_CMDS)

/* HCI Command length. */
#define HCIC_PARAM_SIZE_WRITE_ADV_ENABLE        1
#define HCIC_PARAM_SIZE_BLE_WRITE_ADV_PARAMS    15
#define HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA      31

uint16_t make_cmd_ble_set_adv_param (uint8_t *buf, uint16_t adv_int_min, uint16_t adv_int_max,
                                     uint8_t adv_type, uint8_t addr_type_own,
                                     uint8_t addr_type_dir, bd_addr_t direct_bda,
                                     uint8_t channel_map, uint8_t adv_filter_policy)

    UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);
    UINT16_TO_STREAM (buf, HCI_BLE_WRITE_ADV_PARAMS);
    UINT8_TO_STREAM  (buf, HCIC_PARAM_SIZE_BLE_WRITE_ADV_PARAMS );

    UINT16_TO_STREAM (buf, adv_int_min);
    UINT16_TO_STREAM (buf, adv_int_max);
    UINT8_TO_STREAM (buf, adv_type);
    UINT8_TO_STREAM (buf, addr_type_own);
    UINT8_TO_STREAM (buf, addr_type_dir);
    BDADDR_TO_STREAM (buf, direct_bda);
    UINT8_TO_STREAM (buf, channel_map);
    UINT8_TO_STREAM (buf, adv_filter_policy);
    return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_BLE_WRITE_ADV_PARAMS;

static void hci_cmd_send_ble_set_adv_param(void)

    uint16_t adv_intv_min = 256; // 160ms
    uint16_t adv_intv_max = 256; // 160ms
    uint8_t adv_type = 0; // connectable undirected advertising (ADV_IND)
    uint8_t own_addr_type = 0; // Public Device Address
    uint8_t peer_addr_type = 0; // Public Device Address
    uint8_t peer_addr[6] = 0;
    uint8_t adv_chn_map = 0x07; // 37, 38, 39
    uint8_t adv_filter_policy = 0; // Process All Conn and Scan

    uint16_t sz = make_cmd_ble_set_adv_param(hci_cmd_buf,
                  adv_intv_min,
                  adv_intv_max,
                  adv_type,
                  own_addr_type,
                  peer_addr_type,
                  peer_addr,
                  adv_chn_map,
                  adv_filter_policy);
    esp_vhci_host_send_packet(hci_cmd_buf, sz);

3.5 hci set adv data命令

hci command的格式如下:

OGF=0x08,OCF=0x08

我们来看下这个command的格式:

Advertising_Data_Length:广播数据长度,最长是31个字节

Advertising_Data:广播数据

广播参数格式如下:

一个1byte的length,n byte的type,后面跟的是这个item的广播数据,符合L T V格式

L:length T:Type V:value

其中Type跟HCI EIR基本一样,在文档CSS_V9中,想详细看的可以看下

说不讲原理,又忍不住给你们讲解原理了,就此打住,想看广播数据的,可以看下我这篇文章:

低功耗蓝牙搜索广播的实现流流程介绍 /BLE scan flow ----- 蓝牙低功耗协议栈_Wireless_Link的博客-CSDN博客

在上述文章步骤6)解析BLE广播event的数据包 有广播数据类型,格式等

我们看下代码实现

/*  HCI Command Opcode group Field (OGF) */
#define HCI_GRP_HOST_CONT_BASEBAND_CMDS    (0x03 << 10)            /* 0x0C00 */
#define HCI_GRP_BLE_CMDS                   (0x08 << 10)

/*  HCI Command Opcode Command Field (OCF) */
#define HCI_RESET                       (0x0003 | HCI_GRP_HOST_CONT_BASEBAND_CMDS)
/* Advertising Commands. */
#define HCI_BLE_WRITE_ADV_ENABLE        (0x000A | HCI_GRP_BLE_CMDS)
#define HCI_BLE_WRITE_ADV_DATA          (0x0008 | HCI_GRP_BLE_CMDS)
#define HCI_BLE_WRITE_ADV_PARAMS        (0x0006 | HCI_GRP_BLE_CMDS)

/* HCI Command length. */
#define HCIC_PARAM_SIZE_WRITE_ADV_ENABLE        1
#define HCIC_PARAM_SIZE_BLE_WRITE_ADV_PARAMS    15
#define HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA      31
uint16_t make_cmd_ble_set_adv_enable (uint8_t *buf, uint8_t adv_enable)

    UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);
    UINT16_TO_STREAM (buf, HCI_BLE_WRITE_ADV_ENABLE);
    UINT8_TO_STREAM  (buf, HCIC_PARAM_SIZE_WRITE_ADV_ENABLE);
    UINT8_TO_STREAM (buf, adv_enable);
    return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_WRITE_ADV_ENABLE;

static void hci_cmd_send_ble_adv_start(void)

    uint16_t sz = make_cmd_ble_set_adv_enable (hci_cmd_buf, 1);
    esp_vhci_host_send_packet(hci_cmd_buf, sz);

3.6 hci send ble adv start命令

hci command的格式如下:

OGF=0x08,OCF=0x0a

我们来看下这个command的格式:

Advertising_Enable:停止或者关闭BLE广播

代码如下:

/*  HCI Command Opcode group Field (OGF) */
#define HCI_GRP_HOST_CONT_BASEBAND_CMDS    (0x03 << 10)            /* 0x0C00 */
#define HCI_GRP_BLE_CMDS                   (0x08 << 10)

/*  HCI Command Opcode Command Field (OCF) */
#define HCI_RESET                       (0x0003 | HCI_GRP_HOST_CONT_BASEBAND_CMDS)
/* Advertising Commands. */
#define HCI_BLE_WRITE_ADV_ENABLE        (0x000A | HCI_GRP_BLE_CMDS)
#define HCI_BLE_WRITE_ADV_DATA          (0x0008 | HCI_GRP_BLE_CMDS)
#define HCI_BLE_WRITE_ADV_PARAMS        (0x0006 | HCI_GRP_BLE_CMDS)

/* HCI Command length. */
#define HCIC_PARAM_SIZE_WRITE_ADV_ENABLE        1
#define HCIC_PARAM_SIZE_BLE_WRITE_ADV_PARAMS    15
#define HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA      31
uint16_t make_cmd_ble_set_adv_enable (uint8_t *buf, uint8_t adv_enable)

    UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);
    UINT16_TO_STREAM (buf, HCI_BLE_WRITE_ADV_ENABLE);
    UINT8_TO_STREAM  (buf, HCIC_PARAM_SIZE_WRITE_ADV_ENABLE);
    UINT8_TO_STREAM (buf, adv_enable);
    return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_WRITE_ADV_ENABLE;

static void hci_cmd_send_ble_adv_start(void)

    uint16_t sz = make_cmd_ble_set_adv_enable (hci_cmd_buf, 1);
    esp_vhci_host_send_packet(hci_cmd_buf, sz);

四.程序效果

整个代码运行效果就是我们可以搜索到我们的设备,但是不能连接哈,因为我们自己写的这部分代码不是完整的Host代码

CSDN 社区图书馆,开张营业! 深读计划,写书评领图书福利~

以上是关于ESP32 VHCI实现BLE广播,就是这么神奇的主要内容,如果未能解决你的问题,请参考以下文章

ESP32 VHCI实现BLE广播,就是这么神奇

ESP32 VHCI架构实现BLE扫描设备

ESP32 VHCI架构实现BLE扫描设备

ESP32 VHCI架构实现BLE扫描设备

ESP32学习笔记(26)——BLE GAP从机端广播

ESP32学习笔记(29)——BLE iBeacon广播