蓝牙-广播

GAP广播

1.1概述

支持4.0~5.1规范定义的所有BLE广播类型:

  • 传统广播
  • 扩展广播
  • 周期广播

传统广播的有效载荷最长为31B,而扩展广播(包括周期广播)每个广播包的有效载荷最长接近255B,每个扩展广播又可包含多个广播包(广播链条),总有效载荷最长达1650B。

1.1.1类型

广播有4种不同的属性:

  • 可连接:接受对方发来的连接建立请求
  • 可扫描:接受对方发来的扫描请求,并响应
  • 定向:只用于可连接广播,只接受特定方发来的连接请求
  • 高占空比:以更高的频率重复发送广播数据,常用语需要快速连接的场景

对于传统的扫描响应包,有效载荷最长也为31B;扩展的扫描响应包,有效载荷最长为1650B。

传统广播类型:

|----------------|-----------------|------|
| 类型 | PDU类型 | 广播数据 |
| 非定向可连接可扫描广播 | ADV_IND | 支持 |
| 定向可连接广播(非高占空比) | ADC_DIRECT_IND | 不支持 |
| 定向可连接广播(高占空比) | ADV_DIRECT_IND | 不支持 |
| 非定向可扫描广播 | ADV_SCAN_IND | 支持 |
| 非定向不可连接不可扫描广播 | ADV-NONCONN_IND | 支持 |

1.1.2过滤策略

概念:对于可连接或可扫描广播,可以只接受某些设备的连接建立请求或扫描请求。BLE 定义了 4 种策略:

复制代码
typedef enum adv_filter_policy
{
    // 接受所有的连接建立请求或扫描请求
    ADV_FILTER_ALLOW_ALL    = 0x00,
    // 只接受白名单内的扫描请求,接收所有的连接建立请求
    ADV_FILTER_ALLOW_SCAN_WLST_CON_ALL,
    // 只接受白名单内的连接建立请求,接收所有的扫描请求
    ADV_FILTER_ALLOW_SCAN_ALL_CON_WLST,
    // 只接受白名单内的连接建立请求和扫描请求
    ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST
} adv_filter_policy_t;

1.1.3PHY

对于扩展广播,计需要再煮广播通信(37、38、39)上发送少量信息,也需要再其他信道(即辅广播信道)上发送。所以需要分别设置主、辅广播信道只能使用1M、Coded等两种PHY,而辅广播信道3种PHY皆可。

1.1.4广播集

从5.0开始,BLE支持并发,发送多个广播,每个广播称为一个广播集,由广播句柄指示。每个广播使用 各自独立的参数,包括地址、广播类型、PHY、数据等。开发者可以为广播指定一个4比特长的SID。

1.1.5相关事件

  • HCI_SUBEVENT_LE_ADVERTISING_SET_TERMINATED:一个广播集停止广播时,HCI回调会收到HCI_SUBEVENT_LE_ADVERTISING_SET_TERMINATED事件。这个事件的粗发条件如下:
    1. 连接建立:此时status为0,可读取广播句柄和连接句柄的对应关系;
    2. 已达到预定的广播时长、次数,自动停止:此时的status为0x43(达到指定次数时),或0x3c(达到指定的广播时长时);
    3. 极端情况:Controller无法完成任务处理,此时status为0xFE
  • HCI_SUBEVENT_LE_SCAN_REQUEST_RECEIVED:开发者使能扫描请求指示后,HCI回调会收到HCI_SUBEVENT_LE_SCAN_REQUEST_RECEIVED 事件。
  • HCI_SUBEVENT_PRD_ADV_SUBEVT_DATA_REQ:发送PAwR时,通过该事件向上层应用请求设置子事件数据。该事件的内容为:le_mete_event_prd_adv_subevent_data_req_t。应用收到该事件后,可通过gap_set_periodic_adv_subevent_data为子事件设置数据。
  • HCI_SUBEVENT_PRD_ADV_RSP_REPORT:收到PAwR的响应时,HCI回调收到HCI_SUBEVENT_PRD_ADV_RSP_REPORT事件,内容为:le_mete_event_prd_adv_rsp_report_t

2.使用说明

2.1配置广播

主要用到4个函数,gap_set_adv_set_random_addrgap_set_ext_adv_paragap_set_ext_adv_datagap_set_ext_scan_response_data,分别配置随机地址、参数、广播数据和扫描响应数据。参数最复杂的函数是gap_set_ext_adv_para,其原型为:

复制代码
uint8_t gap_set_ext_adv_para(
    // 广播集句柄
    const uint8_t adv_handle,
    // 属性比特组合
    const adv_event_properties_t properties,
    // 广播间隔
    const uint32_t interval_min,
    const uint32_t interval_max,
    // 使用的主广播信道比特组合(0x7 表示使用全部 3 个主广播信道)
    const adv_channel_bits_t primary_adv_channel_map,
    // 使用的地址类型(随机地址来自 gap_set_adv_set_random_addr)
    const bd_addr_type_t own_addr_type,
    // 设置定向广播的对端地址
    const bd_addr_type_t peer_addr_type,
    const uint8_t *peer_addr,
    // 过滤策略
    const adv_filter_policy_t adv_filter_policy,
    // 发射功率,单位为 dBm
    const int8_t tx_power,
    // 主信道 PHY
    const phy_type_t primary_adv_phy,
    // 是否允许跳过部分辅信道的发送(填 0 表示总是发送)
    const uint8_t secondary_adv_max_skip,
    // 辅信道 PHY
    const phy_type_t secondary_adv_phy,
    // 广播集 SID
    const uint8_t sid,
    // 使能扫描请求上报
    const uint8_t scan_req_notification_enable);

其中,properties为以下比特的组合:

复制代码
// 可连接广播
#define    CONNECTABLE_ADV_BIT       ...
// 可扫描广播
#define    SCANNABLE_ADV_BIT         ...
// 定向广播
#define    DIRECT_ADV_BIT            ...
// 高频广播
#define    HIGH_DUTY_CIR_DIR_ADV_BIT ...
// 传统广播
#define    LEGACY_PDU_BIT            ...
// 匿名广播
#define    ANONY_ADV_BIT             ...
// 包含发射功率
#define    INC_TX_ADV_BIT            ...

对于传统广播,必须受定义的约束。扩展广播不能既可连接又可扫描;不支持高占空比广播 。匿名广播中不包含广播者的地址,所以称为"匿名"广播。附加INC_TX_ADV_BIT比特后,广播内自动包含发射功率,比在载荷内通过AD项"0x0A - <<Tx Power Level>>"发送开销更小。

注意!Controller执行gap_set_ext_adv_para()命令时,会重新初始化对应的广播集,数据需要重新设置。广播数据以空口PDU的格式可能改变。由于一个扩展广播集可能包含多个广播PDU,Controller难以逐个调整PDU的数据格式,所以采用了这种重新初始化、由上层应用重新配置数据的方案。

对于带广播数据的广播,使用gap_set_ext_adv_data设置广播数据:

复制代码
uint8_t gap_set_ext_adv_data(
  // 广播集句柄
  const uint8_t adv_handle,
  // 数据总长度(对于扩展广播,最长可达 1650)
  uint16_t length,
  // 数据
  const uint8_t *data);

对于可扫描广播,使用gap_set_ext_scan_response_data设置扫描响应数据:

复制代码
uint8_t gap_set_ext_scan_response_data(
  const uint8_t adv_handle,
  const uint16_t length,
  const uint8_t *data);

2.2广播数据

使用Wizard里的广播数据编辑器可以方便地编辑数据。广播数据编辑器同时可以生成一些常数,方便开发者编程修改广播数据。下面例子把蓝牙地址的最末两个字节填充到设备名称的最后4个字符里。

  1. 用广播数据编辑器生成初始数据

  2. 导入广播数据及常数:

    复制代码
    static uint8_t adv_data[] = {
        #include "../data/advertising.adv"
    };
    // 这个文件里是编辑器生成的常数
    #include "../data/advertising.const"
  3. 修改广播名称

    复制代码
    void assign_name(const uint8_t *id_bytes)
    {
        char temp[5];
        sprintf(temp, "%02X%02X", id_bytes[0], id_bytes[1]);
        // ADVERTISING_ITEM_OFFSET_COMPLETE_LOCAL_NAME 是编译器自动生成的常数,
        // 表示 "name_xxxx" 在整个数据里的偏移位置
        memcpy(adv_data + ADVERTISING_ITEM_OFFSET_COMPLETE_LOCAL_NAME + 5,
               temp, sizeof(temp) - 1);
    }
    // 假设地址存放于 rand_addr
    assign_name(&rand_addr[4]);

    2.3配置周期广播

周期广播总是一个不可连接、不可扫描的扩展广播绑定。使用gap_set_ext_adv_para 设置了扩展广播参数后就可以通过gap_set_periodic_adv_para 创建相关联的周期广播:

复制代码
uint8_t gap_set_periodic_adv_para(
  // 使用同一个广播集句柄
  const uint8_t adv_handle,
  // 广播周期
  const uint16_t interval_min,
  const uint16_t interval_max,
  // 属性(仅支持 0 或 PERIODIC_ADV_BIT_INC_TX)
  const periodic_adv_properties_t properties);

周期广播的数据通过gap_set_periodic_adv_data设置,而不是gap_set_ext_adv_para。

2.4起停广播

通过gap_set_ext_adv_enable控制多个广播集的使能、停止状态。

复制代码
uint8_t gap_set_ext_adv_enable(
  // 使能还是停止?
  const uint8_t enable,
  // 广播集数目
  const uint8_t set_number,
  // 每个广播集的使能参数
  const ext_adv_set_en_t *adv_sets);

这个函数支持一种快速停止所有广播的用法:gap_set_ext_adv_enable(0, 0, NULL)。除此之外,都需要用adv_sets数组表明每个广播集的句柄。

对于使能广播的情况,adv_sets使用另外两个参数来控制广播次数

复制代码
typedef struct ext_adv_set_en
{
    uint8_t handle;
    // 广播持续时间,单位为 10ms。0ms 表示一直广播
    uint16_t duration;
    // 最大广播次数。0 表示一直广播
    uint8_t max_events;
} ext_adv_set_en_t;

当duraction或max_event条件满足时,广播就会自动停止。