前言
- 前面我们已经了解了 ESP32 的 BLE 整体架构,现在我们开始实际学习一下Bluedroid 从机篇的广播和扫描。
- 本文将会以 ble_ibeacon demo 为例子进行讲解,需要注意的一点是。ibeacon 分为两个部分,一个是作为广播者,一个是作为观察者。
IBEACON_RECEIVER这个宏表示作为观察者,IBEACON_SENDER这个宏被置 1 表示为广播者。 - 需要注意的一点是,本文先仅介绍广播相关内容
ibeacon 介绍
ibeacon 是什么?
- 作为一名初学者,当听到 ibeacon 时候,大概率是一脸懵逼的。即使网上搜索大量资料,没有亲身体验,也是一头雾水。为了方便各位理解,就以我来上海实习,周末逛的第一个景点 – 豫园 为例子进行分析。
- 当我们进入景点,肯定会有一个二维码建议我们扫描,然后之后就会有电子讲解功能。例如我现在扫描了豫园的二维码,打开了一个微信小程序,此时微信小程序上就能够显示出我的位置在哪里。

3. 如果你移动到一个地方,该小程序就会进行相关讲解当前景点的一些历史文化信息。此时,各位有没有想过一个问题,该小程序,是如何精确的知道我们当前是在哪个景点呢?
4. 此时,就是利用的 ibeacon 技术进行的。如果你有兴趣的话,可以在走到某个景点,发现微信小程序播报讲解时刻停下来,然后在这个附近十米内的范围转转,会惊奇的发现,一些地方藏有这种小方块。


5. 这个小方块,就是本文要进行讲解的,ESP32 作为广播者的功能。而你手机,就是充当的观察者。
ibeacon 有什么用?
- 现在我们明白了 ibeacon 技术大概是什么东西了。那么这个有什么作用呢?从上面的例子我们就可以很好的知道,室内定位,广播信息。
- 当前,室内定位技术一直是一项值得探索的技术,ibeacon 可以说提供了一个不错的选择(不过个人感觉目前 BLE 室内定位更多的是聚焦于 AOA)。
- 同样,在商场,我们只需要打开微信小程序走到哪家店铺,就可以直接查看那家店铺的商品信息,这样一定程度上可以方便用户挑选商品。
ibeacon 工程介绍
工程源码
- 我们先拷贝 ble_ibeacon demo 例程出来,打开ibeacon_demo.c 文件,将其替换为如下内容。
解析代码流程
- 现在,我们开始捋一遍 ibeacon 工程代码顺序以方便我们后续理解。
NVS 分区
- 首先是 NVS 分区的初始化,他主要用于存储一些 RF(射频)校准数据,以确保无线通信的性能和稳定性。
- 当 ESP32 第一次启动并运行无线功能(如 Wi-Fi 或蓝牙)时,它会进行 RF 校准,以确定在当前硬件和环境条件下的最佳射频参数。
- 校准过程的结果会被存储在 NVS 中,这样在后续启动时,设备可以直接使用这些校准数据,避免每次启动都需要重新校准。从而提高设备运行效率。
- 为了更为方便的理解 NVS 分区在当前的作用,我们可以进行如下实验。我们会发现,只有第一次才会产生 RF 校验数据失败,而后就将不再出现该信息。
- 这是因为,在第一次芯片启动时,芯片会去检测 NVS 分区是否存在 RF 校验数据。如果有,那么就马上利用该数据进行启动射频模块。如果没有,那么就先进行 RF 校准,然后将校准数据存储在 NVS 分区,方便后续芯片快速启动。
bluedroid 协议栈启动
- 如下为 bluedroid 协议栈启动代码,为什么这样编写,我已经在 ESP32 的 BLE 整体架构 这篇博客讲解,不再进行赘述。
esp_ble_gap_config_adv_data_raw() 设置广播数据
- 在设置广播数据之前,我们需要先知道 BLE 的广播数据包格式是什么样的。下面两张图即可展示出广播包的数据格式,具体含义请阅读 BTHome数据格式解析 的 BLE 广播包格式 章节,此处不做赘述。
- 了解了 BLE 广播格式之后,现在我们要做的就是分析一下 ibeacon 的广播包 AD Structure 应该如何编写。
- 首先,一个 BLE 广播包必须存在 0x01 类型的广播数据,该广播主要是用于指示当前设备能进行的一些行为。因为我们当前是单模,因此需要置位 bit1 和 bit2,因此 AD Data 为 0x06。
bit | 描述 |
0 | 有限可发现模式 |
1 | 一般可发现模式 |
2 | 不支持 BR/EDR ,当前为单模设备 |
3 | 设备同时支持 LE 和 BR/EDR 的 Control |
4 | 设备同时支持 LE 和 BR/EDR 的 HOST,需要注意,在 Core Specification Supplement 10 该位被取消 |
5~7 | 保留 |
- 通过上面分析,因此我们可以得到,第一个 flags 应该填入的数据内容如下:
- 分析完 Flags 的数据包,我们再来看看最关键的 ibeacon 数据格式。
byte | 描述 |
0 | 数据长度,固定为 0x1A |
1 | AD Type,固定为 0xFF 表示厂商自定义数据 |
2~3 | 固定为 0x004C ,此为 Apple 公司的标识符。可在 Assigned Numbers Document (PDF) 7 Company Identifiers 章节查阅 |
4 | 固定为 0x02,这个是由 Apple 公司定义的数据类型 |
5 | ibeacon 数据长度,固定为 0x15 |
6~21 | 用户定义的 iBeacon UUID,用于唯一标识应用场景 |
22~23 | 用户自定义的主要值,用于分组或区域标识 |
24~25 | 用户自定义的次要值,用于更精细的分组或区域标识 |
26 | 发射功率,可通过该值配合 RSSI 获得当前位置与广播设备距离 |
- 通过上述分析,我们就可以将固定的报头数据先设置好。
- 之后我们就可以设置自己想要广播的数据了。
- 一切设置完成后,我们只需要调用
esp_ble_config_ibeacon_data()函数将数据进行填充即可。该函数其实就是对 Payload 段数据进行了一下配置,看如下日志打印信息就可知道。如果感兴趣,各位可以看一下该函数实现,其实是非常简单的。
注意:
esp_ble_config_ibeacon_data()并不是 ESP32 官方的库函数!!!这个是编写例程的人写的自定义函数!!!
- 配置好要广播的数据后,直接调用库函数
esp_ble_gap_config_adv_data_raw()即可。我们来看看 nrf Connect 结合上述打印信息,就会发现这个函数就是将你需要广播的数据存入 Payload 段 。

esp_ble_gap_start_advertising() 启动广播
- 在上面,我们调用
esp_ble_gap_config_adv_data_raw()函数设置原始广播数据完成之后,将会触发ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT事件。 - 我们可以在该事件中判断原始广播数据是否设置成功,如果设置成功,那么我们即可调用
esp_ble_gap_start_advertising()函数启动广播。 - 在调用
esp_ble_gap_start_advertising()函数时,需要传入如下结构体。
- 如果是对 BLE HCI 比较熟悉的朋友就会发现,这个函数其实就是让 HOST 层向 Control 层发送 LE Set Advertising Parameters command 命令。
- 我们可以打开 Core 5.3 的 2353 页,会发现这个命令传入的参数和
esp_ble_gap_start_advertising()函数传入的参数一模一样。

12. 关于这个命令,规范书中的描述如下:
- HCI_LE_Set_Advertising_Parameters 命令用于设置广播参数。
- advertissing_interval_min 小于或等于 Advertising_Interval_Max 。advertissing_interval_min 和 Advertising_Interval_Max 不应该是相同的值,以使控制器能够确定给定其他活动的最佳广告间隔。
- 对于高占空比定向广告,即当 Advertising_Type 为 0x01 (ADV_DIRECT_IND,高占空比)时,不使用 Advertising_Interval_Min 和 Advertising_Interval_Max 参数,应忽略。
- Advertising_Type 用于确定在启用发布时用于发布的数据包类型。
- “Own_Address_Type” 参数表示发布报文使用的地址类型。
- 如果 Own_Address_Type 等于 0x02 或 0x03, Peer_Address 参数包含对端设备的身份地址,Peer_Address_Type 参数包含对端设备的身份类型(即0x00或0x01)。这些参数用于在解析表中定位相应的本地 IRK;此 IRK 用于生成广告中使用的自己的地址。
- 如果是定向广播,即当 Advertising_Type 设置为 0x01 (ADV_DIRECT_IND,高占空比)或0x04 (ADV_DIRECT_IND,低占空比模式)时,则 Peer_Address_Type 和 Peer_Address 有效。
- 如果 Own_Address_Type 等于 0x02 或 0x03,Control 使用 Peer_Address 参数中包含的对端设备的身份地址和 Peer_Address_Type 参数中包含的对端设备的身份地址类型(即0x00或0x01)对应的对端设备的 IRK 生成对端设备的可解析私有地址。
- Advertising_Channel_Map 是一个位字段,表示发送广播报文时应使用的广告通道索引。在 Advertising_Channel_Map 参数中至少要设置一个通道位。
- 当定向广播被启用时,Advertising_Filter_Policy 参数应该被忽略。
- 如果 Control 启用了广播功能,则 HOST 不得发出该命令;如果启用了广播功能,则应使用 "命令禁用 "错误代码。
- 如果 HOST 提供的发布间隔范围(Advertising_Interval_Min, Advertising_Interval_Max)超出了 Control 支持的发布间隔范围,则 Control 将返回不支持的特征或参数值(0x11)错误码。
adv_int_min 和 adv_int_max
adv_int_min和adv_int_max用于指示广播的时间间隔。bluedroid 协议栈会选取该范围内的任意值作为广播间隔,但是实测后发现,他一般选取adv_int_max作为广播间隔。adv_int_min和adv_int_max范围都要求在 0x0020 ~ 0x4000 之间,如果没有设置该值,默认为 0x0800 (1.28 s) 。时间计算公式为 Time = N * 0.625 ms,既最终的广播时间范围为 20 ms to 10.24 s。
- 此时有人可能会有疑问了,我代码里面命令设置的最大广播间隔时间为 40ms 啊,怎么抓包数据有些广播间隔为 48ms 多呢?
- 这个就需要涉及到 SIG 规定的广播间隔内容了。SIG 规定,为了解决多个设备同时广播时的冲突问题,从而避免广播包的碰撞,确保更加可靠的广播和接收,在 BLE 广播过程中,存在一个 随机延迟(random delay),通常称为 广播延迟(advertising delay)。

- 如果我们希望一个确切的广播间隔,那么就可以让
adv_int_min和adv_int_max相等即可。
注:
adv_int_min和adv_int_max相等只是让 advInterval 为我们设定的固定值,advDelay 依旧会存在!而且 SIG 不建议让adv_int_min和adv_int_max相等!
-
adv_int_max必须大于adv_int_min,否则就会出现如下报错。(部分日志信息是本例程用于调试写的)
adv_type
- 该参数用于设置 BLE 的广播类型。
- 如果是学习 《低功耗权威指南》或者其他类型的熟记中会发现,广播类型只有四种:通用广播,定向广播,不可连接广播,可发现广播。这个时候有人肯定会疑问,为什么这里的定向广播有两种。
- 这个就设计到版本更替的问题了,《低功耗权威指南》是用于讲解 BLE 4.0 的权威书籍,但是 ESP32 的 Bluedroid 是支持 BLE 5.0 的。从 BLE 5.0 开始,广播拥有第五种类型,即可连接低占空比定向广告。
- 现在我就开始分别介绍一下这几种广播的区别:
广播类型 | 描述 | 关闭方式 |
ADV_TYPE_IND | 这种广播是最常用的广播方式。进行通用广播的设备是能够被扫描,被连接的。 | 调用 esp_ble_gap_stop_advertising() 函数主动关闭广播,或者连接建立。 |
ADV_TYPE_DIRECT_IND_HIGH | 该广播类型主要针对希望快速建立连接的需求,开启定向广播后,完整的广播事件必须每 3.75ms 重复一次,正因如此之快的广播速率,导致该广播包将在占满整个广播信道,进而导致该区域内其他设备无法进行广播,因此定向广播不可以持续超过 1.28s 之上。 | 该广播只有两种结束方式,第一种是收到指定的对端设备连接请求,第二种是超过 1.28s。一旦超过 1.28s 还没有建立连接,Control 层应该向 HOST 层发送 Advertising Timeout 事件,告知广播超时。(这里需要注意,ESP32 的该广播事件似乎有 bug,并不会上报广播超时事件) |
ADV_TYPE_SCAN_IND | 该类型广播不可以用于发起连接,但允许其他设备进行扫描,可以理解为将连接功能去除的 ADV_TYPE_IND。 | 调用 esp_ble_gap_stop_advertising() 函数主动关闭广播。 |
ADV_TYPE_NONCONN_IND | 该类型广播针对的是不想被连接,仅进行广播的设备,例如本文的 Ibeacon 设备。这也是唯一可用于只有发射机而没有接收机设备的广播类型。 | 调用 esp_ble_gap_stop_advertising() 函数主动关闭广播。 |
ADV_TYPE_DIRECT_IND_LOW | 该广播属于定向广播,但是并不会像 ADV_TYPE_DIRECT_IND_HIGH 那样快速的将整个广播信道占满,他是会在 | 调用 esp_ble_gap_stop_advertising() 函数主动关闭广播,或者和指定的对端设备连接建立。 |
own_addr_type
- 这里设置设备的地址类型,没有特殊需求,直接设置为公共地址即可。

设备地址类型 | 描述 |
公共地址(BLE_ADDR_TYPE_PUBLIC) | 全球唯一且固定的地址,需要向 IEEE 组织购买。因为全球唯一,因此容易被跟踪 |
静态地址(BLE_ADDR_TYPE_RANDOM) | 自己定义,上电初始化完成后不能再进行修改。每次芯片启动都可能会更换地址 |
可解析地址(BLE_ADDR_TYPE_RPA_PUBLIC) | 通讯双方共享 IRK ,生成随机可解析私有地址。只有拥有广播者的 IRK 时,才能跟踪其广播活动。目的是为了防止恶意第三方跟踪蓝牙设备。 |
不可解析地址(BLE_ADDR_TYPE_RPA_RANDOM) | 定期更新地址,SIG 推荐15 min 更新一次,更新时间间隔不要超过 1 小时。通常用于设备只需要一次性广播数据,且不需要被接收方识别或跟踪。例如,发送传感器数据的设备、匿名设备发现、或仅广播某些临时数据的场景。 |
peer_addr
- 对端设备 MAC 地址。只有当
adv_type为ADV_TYPE_DIRECT_IND_HIGH或者ADV_TYPE_DIRECT_IND_LOW才有效。
peer_addr_type
- 对端设备地址类型。只有当
adv_type为ADV_TYPE_DIRECT_IND_HIGH或者ADV_TYPE_DIRECT_IND_LOW才有效。
channel_map
- 广播的信道。你可以设置在指定的广播信道进行广播,或者是所有广播信道都广播。
adv_filter_policy
- 因为我们是 Ibeacon 所有需要让所有人能够扫描到,因此设置为
ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY。 - 白名单是指的特定的对端设备地址,我们可以调用
esp_ble_gap_update_whitelist()函数更新白名单。
如果开发过 Nordic 的相关芯片,会发现一个问题,怎么 Nordic 的芯片还可以过滤 UUID,名称,外观等信息呢?这个就是 Nordic 的协议栈中软件实现的,和 SIG 相关规定无关,如果你希望有这样的功能,可以自行实现。
广播启动完成
- 当调用
esp_ble_gap_start_advertising()函数之后,将会触发 ESP_GAP_BLE_ADV_START_COMPLETE_EVT 事件。在该事件中,我们可以知道广播是否完成。 - 当广播结束之后,将会触发
ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT事件,不过当前的 Ibeacon 例程中,不会停止广播,因此该事件不会触发。
参考
- Apple Ibeacon 官方介绍
- 低功耗蓝牙开发者手册
- BTHome数据格式解析
- nRF52832蓝牙iBeacon广播
- 谷雨文档中心:BLE技术揭秘
- Core 5.3


1513

被折叠的 条评论
为什么被折叠?



