一、背景
1.1 低功耗蓝牙(BLE)协议栈
链路层(LL) 控制设备的射频状态,有五个设备状态:待机、广播、扫描、初始化和连接。
广播 为广播数据包,而 扫描 则是监听广播。
GAP通信中角色,中心设备(Central - 主机) 用来扫描和连接 外围设备(Peripheral - 从机)。
大部分情况下外围设备通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。
也有些情况是不需要连接的,只要外设广播自己的数据即可,用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。
1.2 从机广播
从机(外围设备)要被主机连接,那么它就必须先被主机发现。这个时候,从机设备把自身信息以广播形式发射出去。
比如设备A需要先进行广播,即 设备A(Advertiser) 不断发送如下广播信号,t 为广播间隔。每发送一次广播包,我们称其为一次 广播事件(advertising event),因此 t 也称为广播事件间隔,如下图所示。广播事件是一阵一阵的,每次会是有一个持续时间的,蓝牙芯片只有在广播事件期间才打开射频模块发射广播,这个时候功耗比较高,其余时间蓝牙芯片都处于idle待机状态,因此平均功耗就非常低。
当广播发出的时候,每一个广播事件包含三个广播包,即分别在 37/38/39 三个通道上同时广播相同的信息。下图 observer 为主机观察者,advertiser 就是从机广播。
1.3 ESP32蓝牙应用结构
蓝牙是⼀种短距通信系统,其关键特性包括鲁棒性、低功耗、低成本等。蓝牙系统分为两种不同的技术:经典蓝牙 (Classic Bluetooth) 和蓝牙低功耗 (Bluetooth Low Energy)。
ESP32 支持双模蓝牙,即同时支持经典蓝牙和蓝牙低功耗。
从整体结构上,蓝牙可分为控制器 (Controller) 和主机 (Host) 两⼤部分:控制器包括了 PHY、Baseband、Link Controller、Link Manager、Device Manager、HCI 等模块,用于硬件接⼝管理、链路管理等等;主机则包括了 L2CAP、SMP、SDP、ATT、GATT、GAP 以及各种规范,构建了向应用层提供接口的基础,方便应用层对蓝牙系统的访问。主机可以与控制器运行在同⼀个宿主上,也可以分布在不同的宿主上。ESP32 可以支持上述两种方式。
1.4 Bluedroid主机架构
在 ESP-IDF 中,使用经过大量修改后的 BLUEDROID 作为蓝牙主机 (Classic BT + BLE)。BLUEDROID 拥有较为完善的功能,⽀持常用的规范和架构设计,同时也较为复杂。经过大量修改后,BLUEDROID 保留了大多数 BTA 层以下的代码,几乎完全删去了 BTIF 层的代码,使用了较为精简的 BTC 层作为内置规范及 Misc 控制层。修改后的 BLUEDROID 及其与控制器之间的关系如下图:
二、BLE广播应用分析
广播通信相关的协议层次 GAP--》HCI--》Link Layer。
LL(Link Layer)位于最底层,负责广播通信有关功能的定义和实现,包括物理通道的选择、相关的链路状态的定义、PDU的定义、设备过滤(Device Filtering)机制的实现等。
HCI负责将LL提供的所有功能,以Command/Event的形式抽象出来,供Host使用。
GAP负责从应用程序的角度,抽象并封装LL提供的功能,以便让应用以比较傻瓜的方式进行广播通信。当然,这不是必须的,也就是说,我们可以在没有GAP参与的情况下,进行广播通信。
三、API说明
以下控制器和虚拟 HCI 接口位于 bt/include/esp32/include/esp_bt.h。
3.1 esp_bt_controller_mem_release
3.2 esp_bt_controller_init
3.3 esp_bt_controller_enable
3.4 esp_vhci_host_register_callback
3.5 esp_vhci_host_check_send_available
3.6 esp_vhci_host_send_packet
以下 GAP 接口位于 bt/host/bluedroid/api/include/api/esp_bt_main.h 和 bt/host/bluedroid/api/include/api/esp_gap_ble_api.h。
3.7 esp_bluedroid_init
3.8 esp_bluedroid_enable
3.9 esp_ble_gap_register_callback
3.10 esp_ble_gap_config_adv_data
3.11 esp_ble_gap_config_adv_data_raw
3.12 esp_ble_gap_config_scan_rsp_data_raw
3.13 esp_ble_gap_start_advertising
3.14 esp_ble_gap_stop_advertising
四、使用HCI接口BLE广播
HCI负责将LL提供的所有功能,以Command/Event的形式抽象出来,供上层使用。
HCI Command格式
OCF(10bit)+OGF(6bit) | Parameter Total Length | Parameter 1 | Parameter 2 | Parameter 3 |
---|
其中OCF和OGF组成16bit的操作码,Parameter Total Length,指示该Command 所有参数长度,Parameter1、Parameter2、等等;16bits 的参数,由具体的Command决定。
HCI Event格式
Event Code(8 bits) | Parameter Total Length | Parameter 1 | Parameter 2 | Parameter 3 |
---|
使用 esp-idf\examples\bluetooth\hci\controller_vhci_ble_adv 中的例程
- 初始化和使能蓝牙控制器
esp_bt_controller_init()
esp_bt_controller_enable()
- 先配置广播参数,然后定义广播数据(广播包数据最多31的字节),最后开启广播。
hci_cmd_send_ble_set_adv_param()
hci_cmd_send_ble_set_adv_data()
hci_cmd_send_ble_adv_start()
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_bt.h"
#include "esp_log.h"
#include "nvs_flash.h"
static const char *tag = "BLE_ADV";
#define HCI_H4_CMD_PREAMBLE_SIZE (4)
/* HCI Command opcode group field(OGF) */
#define HCI_GRP_HOST_CONT_BASEBAND_CMDS (0x03 << 10) /* 0x0C00 */
#define HCI_GRP_BLE_CMDS (0x08 << 10)
#define HCI_RESET (0x0003 | HCI_GRP_HOST_CONT_BASEBAND_CMDS)
#define HCI_BLE_WRITE_ADV_ENABLE (0x000A | HCI_GRP_BLE_CMDS)
#define HCI_BLE_WRITE_ADV_PARAMS (0x0006 | HCI_GRP_BLE_CMDS)
#define HCI_BLE_WRITE_ADV_DATA (0x0008 | HCI_GRP_BLE_CMDS)
#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)
#define BD_ADDR_LEN (6) /* Device address length */
typedef uint8_t bd_addr_t[BD_ADDR_LEN]; /* Device address */
#define UINT16_TO_STREAM(p, u16) {*(p)++ = (uint8_t)(u16); *(p)++ = (uint8_t)((u16) >> 8);}
#define UINT8_TO_STREAM(p, u8) {*(p)++ = (uint8_t)(u8);}
#define BDADDR_TO_STREAM(p, a) {int ijk; for (ijk = 0; ijk < BD_ADDR_LEN; ijk++) *(p)++ = (uint8_t) a[BD_ADDR_LEN - 1 - ijk];}
#define ARRAY_TO_STREAM(p, a, len) {int ijk; for (ijk = 0; ijk < len; ijk++) *(p)++ = (uint8_t) a[ijk];}
enum {
H4_TYPE_COMMAND = 1,
H4_TYPE_ACL = 2,
H4_TYPE_SCO = 3,
H4_TYPE_EVENT = 4
};
static uint8_t hci_cmd_buf[128];
/*
* @brief: BT controller callback function, used to notify the upper layer that
* controller is ready to receive command
*/
static void controller_rcv_pkt_ready(void)
{
printf("controller rcv pkt ready\n");
}
/*
* @brief: BT controller callback function, to transfer data packet to upper
* controller is ready to receive command
*/
static int host_rcv_pkt(uint8_t *data, uint16_t len)
{
printf("host rcv pkt: ");
for (uint16_t i = 0; i < len; i++) {
printf("%02x", data[i]);
}
printf("\n");
return 0;
}
static esp_vhci_host_callback_t vhci_host_cb = {
controller_rcv_pkt_ready,
host_rcv_pkt
};
static 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 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 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 uint16_t make_cmd_ble_set_adv_data(uint8_t *buf, uint8_t data_len, uint8_t *p_data)
{
UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);
UINT16_TO_STREAM (buf, HCI_BLE_WRITE_ADV_DATA);
UINT8_TO_STREAM (buf, HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA + 1);
memset(buf, 0, HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA);
if (p_data != NULL && data_len > 0) {
if (data_len > HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA) {
data_len = HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA;
}
UINT8_TO_STREAM (buf, data_len);
ARRAY_TO_STREAM (buf, p_data, data_len);
}
return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA + 1;
}
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);
}
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);
}
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] = {0x80, 0x81, 0x82, 0x83, 0x84, 0x85};
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);
}
static void hci_cmd_send_ble_set_adv_data(void)
{
char *adv_name = "ESP-BLE-HELLO";
uint8_t name_len = (uint8_t)strlen(adv_name);
uint8_t adv_data[31] = {0x02, 0x01, 0x06, 0x0, 0x09};
uint8_t adv_data_len;
adv_data[3] = name_len + 1;
for (int i = 0; i < name_len; i++) {
adv_data[5 + i] = (uint8_t)adv_name[i];
}
adv_data_len = 5 + name_len;
uint16_t sz = make_cmd_ble_set_adv_data(hci_cmd_buf, adv_data_len, (uint8_t *)adv_data);
esp_vhci_host_send_packet(hci_cmd_buf, sz);
}
/*
* @brief: send HCI commands to perform BLE advertising;
*/
void bleAdvtTask(void *pvParameters)
{
int cmd_cnt = 0;
bool send_avail = false;
esp_vhci_host_register_callback(&vhci_host_cb);
printf("BLE advt task start\n");
while (1) {
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);
}
}
void app_main(void)
{
/* 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 );
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;
}
/*
* If call mem release here, also work. Input ESP_BT_MODE_CLASSIC_BT, the function will
* release the memory of classic bt mode.
* esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
*
*/
/*
* If call mem release here, also work. Input ESP_BT_MODE_BTDM, the function will calculate
* that the BLE mode is already used, so it will release of only classic bt mode.
* esp_bt_controller_mem_release(ESP_BT_MODE_BTDM);
*/
xTaskCreatePinnedToCore(&bleAdvtTask, "bleAdvtTask", 2048, NULL, 5, NULL, 0);
}
查看打印:
搜索广播:
五、使用GAP接口BLE广播
5.1 配置广播参数
static esp_ble_adv_params_t ble_adv_params = {
.adv_int_min = 0x20, //最小时间间隔
.adv_int_max = 0x40, //最大时间间隔
.adv_type = ADV_TYPE_NONCONN_IND, //广播类型
.own_addr_type = BLE_ADDR_TYPE_PUBLIC, //拥有者的蓝牙设备地址类型
.channel_map = ADV_CHNL_ALL, //广播通道映射
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, //广播过滤器设置
};
5.1.1 广播间隔
设置在0x0020到0x4000,默认0x0800(1.28s),实际时间=N * 0.625 ms
,时间范围在20ms到10.24s
5.1.2 广播类型
/// Advertising mode
typedef enum {
ADV_TYPE_IND = 0x00,
ADV_TYPE_DIRECT_IND_HIGH = 0x01,
ADV_TYPE_SCAN_IND = 0x02,
ADV_TYPE_NONCONN_IND = 0x03,
ADV_TYPE_DIRECT_IND_LOW = 0x04,
} esp_ble_adv_type_t;
BLE 广播主要有 5 种类型,分别为:
-
可连接可扫描非定向广播 (Connectable scannable
undirected event type)
-
高占空比定向广播 (High duty cycle directed event type)
-
可扫描非定向广播 (Scannable undirected event type)
-
不可连接非定向⼴播 (Non-connectable undirected event type)
5.1.3 广播过滤策略
在 ESP32 的 BLE 中,通过设置 adv_filter_policy 枚举类型来实现广播过滤策略,此枚举类型中有以下 4 个值:
• ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY 可被任何设备扫描和连接(不使用白名单)
• ADV_FILTER_ALLOW_SCAN_WLST_CON_ANY 处理所有连接请求和只处理在白名单设备中的扫描请求
• ADV_FILTER_ALLOW_SCAN_ANY_CON_WLST 处理所有扫描请求和只处理在白名单中的连接请求
• ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST 只处理在白名单中设备的连接请求和扫描请求
5.2 配置广播内容
esp_ble_gap_config_adv_data()
使用 esp_ble_adv_data_t
结构体进行设置
5.3 开启广播
esp_ble_gap_start_advertising()
使用 esp_ble_adv_params_t
结构体进行设置
5.4 广播流程
5.4.1 使用public地址进行广播
5.4.2 使用可解析地址进行广播
5.4.3 使用静态随机地址进行广播
5.5 实例
根据 esp-idf\examples\bluetooth\bluedroid\ble\ble_ibeacon 中的例程修改
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_bt_defs.h"
#include "esp_ibeacon_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
static const char* DEMO_TAG = "IBEACON_DEMO";
extern esp_ble_ibeacon_vendor_t vendor_config;
///Declare static functions
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
static esp_ble_adv_params_t ble_adv_params = {
.adv_int_min = 0x20,
.adv_int_max = 0x40,
.adv_type = ADV_TYPE_NONCONN_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
esp_err_t err;
switch (event) {
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:{
esp_ble_gap_start_advertising(&ble_adv_params);
break;
}
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
//adv start complete event to indicate adv start successfully or failed
if ((err = param->adv_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(DEMO_TAG, "Adv start failed: %s", esp_err_to_name(err));
}
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if ((err = param->adv_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS){
ESP_LOGE(DEMO_TAG, "Adv stop failed: %s", esp_err_to_name(err));
}
else {
ESP_LOGI(DEMO_TAG, "Stop adv successfully");
}
break;
default:
break;
}
}
void ble_ibeacon_appRegister(void)
{
esp_err_t status;
ESP_LOGI(DEMO_TAG, "register callback");
//register the scan callback function to the gap module
if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) {
ESP_LOGE(DEMO_TAG, "gap register error: %s", esp_err_to_name(status));
return;
}
}
void ble_ibeacon_init(void)
{
esp_bluedroid_init(); // 初始化蓝牙栈bluedroid stack
esp_bluedroid_enable(); // 使能蓝牙栈
ble_ibeacon_appRegister();
}
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init()); // 初始化NVS
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();
esp_bt_controller_init(&bt_cfg); // 初始化蓝牙控制器
esp_bt_controller_enable(ESP_BT_MODE_BLE); // 使能蓝牙控制器
ble_ibeacon_init();
esp_ble_ibeacon_t ibeacon_adv_data;
esp_err_t status = esp_ble_config_ibeacon_data (&vendor_config, &ibeacon_adv_data);
if (status == ESP_OK){
esp_ble_gap_config_adv_data_raw((uint8_t*)&ibeacon_adv_data, sizeof(ibeacon_adv_data));
}
else {
ESP_LOGE(DEMO_TAG, "Config iBeacon data failed: %s\n", esp_err_to_name(status));
}
}
查看打印:
查看广播:
• 由 Leung 写于 2021 年 6 月 21 日
• 参考:ESPIDF开发ESP32学习笔记【经典蓝牙与BLE】
基于ESP32的Bluedroid蓝牙协议栈架构分析(1) --- HCI线程分析
基于ESP32的Bluedroid蓝牙协议栈架构分析(2)--- ADV广播流程分析
ESP32 广播流程
【BLE】ESP32开发填坑和广播简介