NRF52832学习笔记(9)——GAP从机端广播

一、背景

本篇是关于配置以及启动或关闭广播的流程,广播自定义数据包查看 NRF52832学习笔记(10)——GAP从机端广播自定义数据

1.1 蓝牙协议栈

链路层(LL)控制设备的射频状态,有五个设备状态:待机、广播、扫描、初始化和连接。

广播 为广播数据包,而 扫描 则是监听广播。

GAP通信中角色,中心设备(Central - 主机) 用来扫描和连接 外围设备(Peripheral - 从机)

大部分情况下外围设备通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。

也有些情况是不需要连接的,只要外设广播自己的数据即可,用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。

1.2 从机广播

从机(外围设备)要被主机连接,那么它就必须先被主机发现。这个时候,从机设备把自身信息以广播形式发射出去。

比如设备A需要先进行广播,即 设备A(Advertiser)不断发送如下广播信号,t 为广播间隔。每发送一次广播包,我们称其为一次 广播事件(advertising event),因此 t 也称为广播事件间隔,如下图所示。广播事件是一阵一阵的,每次会是有一个持续时间的,蓝牙芯片只有在广播事件期间才打开射频模块发射广播,这个时候功耗比较高,其余时间蓝牙芯片都处于idle待机状态,因此平均功耗就非常低。

当广播发出的时候,每一个广播事件包含三个广播包,即分别在 37/38/39 三个通道上同时广播相同的信息。下图 observer 为主机观察者,advertiser 就是从机广播。

二、配置广播参数

2.1 广播参数相关宏及变量

在 main.c 中

#define DEVICE_NAME                     "Nordic_Template"       /**< Name of device. Will be included in the advertising data. */
#define MANUFACTURER_NAME               "NordicSemiconductor"   /**< Manufacturer. Will be passed to Device Information Service. */
#define APP_ADV_INTERVAL                300                     /**< The advertising interval (in units of 0.625 ms. This value corresponds to 187.5 ms). */

#define APP_ADV_DURATION                0//18000                /**< The advertising duration (180 seconds) in units of 10 milliseconds. */

// YOUR_JOB: Use UUIDs for service(s) used in your application.
static ble_uuid_t m_adv_uuids[] =                                               /**< Universally unique service identifiers. */
{
    {BLE_UUID_DEVICE_INFORMATION_SERVICE, BLE_UUID_TYPE_BLE}
};

2.2 初始化广播参数

在 main.c 中

/**@brief Function for application main entry.
 */
int main(void)
{
    ···
    ···
    /*-------------------------- 蓝牙协议栈初始化 ---------------------------*/
    advertising_init();                 // 广播初始化
    /*-------------------------- 开启应用 ---------------------------*/
    // Start execution.
    NRF_LOG_INFO("Template example started."); 
    advertising_start(erase_bonds);    // 开启广播  

    // Enter main loop.
    for(;;)
    {
        idle_state_handle();
    }
}

2.2.1 advertising_init

/**@brief Function for initializing the Advertising functionality.
 */
static void advertising_init(void)
{
    ret_code_t err_code;
    ble_advertising_init_t init;

    memset(&init, 0, sizeof(init));
    
    // 初始化广播数据包内容
    init.advdata.name_type               = BLE_ADVDATA_FULL_NAME;  // 显示全名
    init.advdata.include_appearance      = true;  // 显示图标
    init.advdata.flags                   = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
    init.advdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
    init.advdata.uuids_complete.p_uuids  = m_adv_uuids;

    init.config.ble_adv_fast_enabled  = true;              // 广播类型,快速广播             
    init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;  // 广播间隔
    init.config.ble_adv_fast_timeout  = APP_ADV_DURATION;  // 广播超时时间,值0则保持一种广播模式不变

    init.evt_handler = on_adv_evt;

    err_code = ble_advertising_init(&m_advertising, &init);// 初始化广播,导入参数
    APP_ERROR_CHECK(err_code);

    ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);// 设置广播识别号
}

广播初始化实际上就是初始化两个结构体,一个是 &advdata 广播数据,一个是 &config 选择项。

设置完广播参数后,通过函数 ble_advertising_init() 配置广播参数到协议栈中。

2.3 广播参数定义

&advdata 广播数据定义了广播的基本参数,比如名称类型,最短名称长度,设备模式,发射功率等等,不是所有参数你都需要在初始化函数中进行设置。根据需求选择。

/**@brief Advertising data structure. This structure contains all options and data needed for encoding and
 *        setting the advertising data. */
typedef struct
{
    ble_advdata_name_type_t      name_type;                           /**< Type of device name. */
    uint8_t                      short_name_len;                      /**< Length of short device name (if short type is specified). */
    bool                         include_appearance;                  /**< Determines if Appearance shall be included. */
    uint8_t                      flags;                               /**< Advertising data Flags field. */
    int8_t *                     p_tx_power_level;                    /**< TX Power Level field. */
    ble_advdata_uuid_list_t      uuids_more_available;                /**< List of UUIDs in the 'More Available' list. */
    ble_advdata_uuid_list_t      uuids_complete;                      /**< List of UUIDs in the 'Complete' list. */
    ble_advdata_uuid_list_t      uuids_solicited;                     /**< List of solicited UUIDs. */
    ble_advdata_conn_int_t *     p_slave_conn_int;                    /**< Slave Connection Interval Range. */
    ble_advdata_manuf_data_t *   p_manuf_specific_data;               /**< Manufacturer specific data. */
    ble_advdata_service_data_t * p_service_data_array;                /**< Array of Service data structures. */
    uint8_t                      service_data_count;                  /**< Number of Service data structures. */
    bool                         include_ble_device_addr;             /**< Determines if LE Bluetooth Device Address shall be included. */
    ble_advdata_le_role_t        le_role;                             /**< LE Role field. Included when different from @ref BLE_ADVDATA_ROLE_NOT_PRESENT. @warning This field can be used only for NFC. For BLE advertising, set it to NULL. */
    ble_advdata_tk_value_t *     p_tk_value;                          /**< Security Manager TK value field. Included when different from NULL. @warning This field can be used only for NFC. For BLE advertising, set it to NULL.*/
    uint8_t *                    p_sec_mgr_oob_flags;                 /**< Security Manager Out Of Band Flags field. Included when different from NULL. @warning This field can be used only for NFC. For BLE advertising, set it to NULL.*/
    ble_gap_lesc_oob_data_t *    p_lesc_data;                         /**< LE Secure Connections OOB data. Included when different from NULL. @warning This field can be used only for NFC. For BLE advertising, set it to NULL.*/
} ble_advdata_t;

2.3.1 名称类型name_type

设置广播里,显示广播名称的三种类型:

/**@brief Advertising data name type. This enumeration contains the options available for the device name inside
 *        the advertising data. */
typedef enum
{
    BLE_ADVDATA_NO_NAME,                                              /**< Include no device name in advertising data. */
    BLE_ADVDATA_SHORT_NAME,                                           /**< Include short device name in advertising data. */
    BLE_ADVDATA_FULL_NAME                                             /**< Include full device name in advertising data. */
} ble_advdata_name_type_t;

2.3.2 展示图标include_appearance

设置是否需要展示图标
init.advdata.include_appearance = true; // 显示图标

2.3.3 蓝牙设备模式flags

/**@defgroup BLE_GAP_ADV_FLAGS GAP Advertisement Flags
 * @{ */
#define BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE         (0x01)   /**< LE Limited Discoverable Mode. */
#define BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE         (0x02)   /**< LE General Discoverable Mode. */
#define BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED         (0x04)   /**< BR/EDR not supported. */
#define BLE_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER         (0x08)   /**< Simultaneous LE and BR/EDR, Controller. */
#define BLE_GAP_ADV_FLAG_LE_BR_EDR_HOST               (0x10)   /**< Simultaneous LE and BR/EDR, Host. */
#define BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE   (BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED)   /**< LE Limited Discoverable Mode, BR/EDR not supported. */
#define BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE   (BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED)   /**< LE General Discoverable Mode, BR/EDR not supported. */
/**@} */


蓝牙模式设置通过 flags 来进行标识:
LE 是 BLE(低功耗蓝牙),BR/EDR 是 蓝牙基本速率/增强速率(传统蓝牙),一般为蓝牙耳机

  • Bit0:LE 有限发现模式
  • Bit1:LE 普通发现模式
  • Bit2:不支持 BR/EDR 模式
  • Bit3:同时支持 BLE 和 BR/EDR 模式(控制器)
  • Bit3:同时支持 BLE 和 BR/EDR 模式(主机)

BLE设备通常设置两种情况:

  • BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE:LE 有限发现模式和不支持 BR/EDR 模式。
  • BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE:LE 普通发现模式和不支持 BR/EDR 模式。

2.4 广播模式配置

&config 定义了广播的模式配置,比如广播模式,广播间隔,广播时间等等

/**@brief   Options for the different advertisement modes.
 *
 * @details This structure is used to enable or disable advertising modes and to configure time-out
 *          periods and advertising intervals.
 */
typedef struct
{
    bool     ble_adv_on_disconnect_disabled;     /**< Enable or disable automatic return to advertising upon disconnecting.*/
    bool     ble_adv_whitelist_enabled;          /**< Enable or disable use of the whitelist. */
    bool     ble_adv_directed_high_duty_enabled; /**< Enable or disable high duty direct advertising mode. Can not be used together with extended advertising. */
    bool     ble_adv_directed_enabled;           /**< Enable or disable direct advertising mode. */
    bool     ble_adv_fast_enabled;               /**< Enable or disable fast advertising mode. */
    bool     ble_adv_slow_enabled;               /**< Enable or disable slow advertising mode. */
    uint32_t ble_adv_directed_interval;          /**< Advertising interval for directed advertising. */
    uint32_t ble_adv_directed_timeout;           /**< Time-out (number of tries) for direct advertising. */
    uint32_t ble_adv_fast_interval;              /**< Advertising interval for fast advertising. */
    uint32_t ble_adv_fast_timeout;               /**< Time-out (in units of 10ms) for fast advertising. */
    uint32_t ble_adv_slow_interval;              /**< Advertising interval for slow advertising. */
    uint32_t ble_adv_slow_timeout;               /**< Time-out (in units of 10ms) for slow advertising. */
    bool     ble_adv_extended_enabled;           /**< Enable or disable extended advertising. */
    uint32_t ble_adv_secondary_phy;              /**< PHY for the secondary (extended) advertising @ref BLE_GAP_PHYS (BLE_GAP_PHY_1MBPS, BLE_GAP_PHY_2MBPS or BLE_GAP_PHY_CODED). */
    uint32_t ble_adv_primary_phy;                /**< PHY for the primary advertising. @ref BLE_GAP_PHYS (BLE_GAP_PHY_1MBPS, BLE_GAP_PHY_2MBPS or BLE_GAP_PHY_CODED). */
} ble_adv_modes_config_t;

2.4.1 广播模式

  • directed_high_duty 高速连接任务模式:利用的就是 BLE 中的直连广播,该模式是为了快速重连上刚刚断开的配对设备。比如利用在快速重连上意外断开的设备,已达到无缝恢复的目的。这种模式只有在扩展广播被关闭下才能够使用。扩展广播是蓝牙5.0新特性

  • directed 定向广播模式:这种方式比高速模式的广播周期要低。

  • fast 快速广播模式:就是普通的广播,不过连接间隔我们可以设置的快一点。

  • slow 慢速广播模式:普通广播,连接间隔设置的慢一点。

  • idle 空闲模式:停止广播。

2.4.2 广播间隔

广播包的广播时间间隔,不同的广播类型下都有自己对应的广播间隔。
工程中设置为:init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;

2.4.3 广播时间

超过这个时间会发生广播超时处理,不同的广播类型下都有自己对应的广播时间。
工程中设置为:init.config.ble_adv_fast_timeout = APP_ADV_DURATION;

三、执行广播

在从机的 ble_app_template 工程的 main.c 中,在广播初始化程序里设置为 init.config.ble_adv_fast_enabled = true; 广播类型为快速广播。那么开始广播时就直接进入快速广播。整个启动过程交给了主函数中 advertising_start() 中的 ble_advertising_start() 来实现。

3.1 advertising_start

未绑定情况下,开启广播。进入 ble_advertising_start()

/**@brief Function for starting advertising.
 */
void advertising_start(bool erase_bonds)
{
    if(erase_bonds == true)
    {
        delete_bonds();
        // Advertising is started by PM_EVT_PEERS_DELETED_SUCEEDED event
    }
    else
    {
        ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);

        APP_ERROR_CHECK(err_code);
    }
}

3.2 ble_advertising_start

根据实参 BLE_ADV_MODE_FAST 在 switch case 中启动快速广播

uint32_t ble_advertising_start(ble_advertising_t * const p_advertising,
                               ble_adv_mode_t            advertising_mode)
{
    uint32_t ret;

    if (p_advertising->initialized == false)
    {
        return NRF_ERROR_INVALID_STATE;
    }

    p_advertising->adv_mode_current = advertising_mode;

    memset(&p_advertising->peer_address, 0, sizeof(p_advertising->peer_address));

    if (  ((p_advertising->adv_modes_config.ble_adv_directed_high_duty_enabled) && (p_advertising->adv_mode_current == BLE_ADV_MODE_DIRECTED_HIGH_DUTY))
        ||((p_advertising->adv_modes_config.ble_adv_directed_enabled)           && (p_advertising->adv_mode_current == BLE_ADV_MODE_DIRECTED_HIGH_DUTY))
        ||((p_advertising->adv_modes_config.ble_adv_directed_enabled)           && (p_advertising->adv_mode_current == BLE_ADV_MODE_DIRECTED))
       )
    {
        if (p_advertising->evt_handler != NULL)
        {
            p_advertising->peer_addr_reply_expected = true;
            p_advertising->evt_handler(BLE_ADV_EVT_PEER_ADDR_REQUEST);
        }
        else
        {
            p_advertising->peer_addr_reply_expected = false;
        }
    }

    p_advertising->adv_mode_current = adv_mode_next_avail_get(p_advertising, advertising_mode);

    // Fetch the whitelist.
    if ((p_advertising->evt_handler != NULL) &&
        (p_advertising->adv_mode_current == BLE_ADV_MODE_FAST || p_advertising->adv_mode_current == BLE_ADV_MODE_SLOW) &&
        (p_advertising->adv_modes_config.ble_adv_whitelist_enabled) &&
        (!p_advertising->whitelist_temporarily_disabled))
    {
        p_advertising->whitelist_in_use         = false;
        p_advertising->whitelist_reply_expected = true;
        p_advertising->evt_handler(BLE_ADV_EVT_WHITELIST_REQUEST);
    }
    else
    {
        p_advertising->whitelist_reply_expected = false;
    }

    // Initialize advertising parameters with default values.
    memset(&p_advertising->adv_params, 0, sizeof(p_advertising->adv_params));

    p_advertising->adv_params.properties.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED;

    // Use 1MBIT as primary phy if no phy was selected.
    if (phy_is_valid(&p_advertising->adv_modes_config.ble_adv_primary_phy))
    {
        p_advertising->adv_params.primary_phy = p_advertising->adv_modes_config.ble_adv_primary_phy;
    }
    else
    {
        p_advertising->adv_params.primary_phy = BLE_GAP_PHY_1MBPS;
    }

    if (p_advertising->adv_modes_config.ble_adv_extended_enabled)
    {
        // Use 1MBIT as secondary phy if no phy was selected.
        if (phy_is_valid(&p_advertising->adv_modes_config.ble_adv_primary_phy))
        {
            p_advertising->adv_params.secondary_phy = p_advertising->adv_modes_config.ble_adv_secondary_phy;
        }
        else
        {
            p_advertising->adv_params.secondary_phy = BLE_GAP_PHY_1MBPS;
        }
    }
    p_advertising->adv_params.filter_policy = BLE_GAP_ADV_FP_ANY;

    // Set advertising parameters and events according to selected advertising mode.
    switch (p_advertising->adv_mode_current)
    {
        case BLE_ADV_MODE_DIRECTED_HIGH_DUTY:
            ret = set_adv_mode_directed_high_duty(p_advertising, &p_advertising->adv_params);
            break;

        case BLE_ADV_MODE_DIRECTED:
            ret = set_adv_mode_directed(p_advertising, &p_advertising->adv_params);
            break;

        case BLE_ADV_MODE_FAST:
            ret = set_adv_mode_fast(p_advertising, &p_advertising->adv_params);
            break;

        case BLE_ADV_MODE_SLOW:
            ret = set_adv_mode_slow(p_advertising, &p_advertising->adv_params);
            break;

        case BLE_ADV_MODE_IDLE:
            p_advertising->adv_evt = BLE_ADV_EVT_IDLE;
            break;

        default:
            break;
    }

    if (p_advertising->adv_mode_current != BLE_ADV_MODE_IDLE)
    {

        ret = sd_ble_gap_adv_set_configure(&p_advertising->adv_handle, p_advertising->p_adv_data, &p_advertising->adv_params);
        if (ret != NRF_SUCCESS)
        {
            return ret;
        }
        ret = sd_ble_gap_adv_start(p_advertising->adv_handle, p_advertising->conn_cfg_tag);

        if (ret != NRF_SUCCESS)
        {
            return ret;
        }
    }

    if (p_advertising->evt_handler != NULL)
    {
        p_advertising->evt_handler(p_advertising->adv_evt);
    }

    return NRF_SUCCESS;
}

但是,我们蓝牙设备不可能一直广播下去,这样很费电,我们需要动态的切换广播状态这个工作由 广播回调函数 完成,在 BLE_ADVERTISING_H 文件中


这个是回调函数必须有的部分,其中 ble_advertising_on_ble_evt() 广播蓝牙回调事件函数是主要作用。

比如我们程序开始设置为快速广播,那么在超时后进入下一级慢速模式。如果是慢速广播,超时进入无效模式。在函数中我们只设置了快速广播超时时间,其他几种广播超时时间没有设置,那么就默认为0,那么整个状态就变为 快速广播超时的TIMEOUT时间内没有连接---> 进入慢速广播---> 立即进入无效模式

ble_advertising_start 开始广播函数每次在超时的时候会调用 adv_mode_next_get 函数,这个函数调用一次会把模式自动加1。

/**@brief Function for checking the next advertising mode.
 *
 * @param[in] adv_mode Current advertising mode.
 */
static ble_adv_mode_t adv_mode_next_get(ble_adv_mode_t adv_mode)
{
    return (ble_adv_mode_t)((adv_mode + 1) % BLE_ADV_MODES);
}

设置广播模式的时候是递进设置的,高速定向广播---> 普通定向广播---> 快速广播---> 慢速广播---> 无效广播 每一级模式到达超时时间后会进入到下一级广播模式。

3.3 on_adv_evt

在 main.c 中,on_adv_evt 为广播事件处理函数,设置广播模式成功后执行对应操作,在蓝牙样例工程里由于没有慢速超时时间,实际上只需用到了两种状态,快速广播和无效广播。在 case 中可以加入自己的一些操作,如快速广播开始后,LED灯会闪烁,而无效广播模式会进入休眠模式。

/**@brief Function for handling advertising events.
 *
 * @details This function will be called for advertising events which are passed to the application.
 *
 * @param[in] ble_adv_evt  Advertising event.
 */
static void on_adv_evt(ble_adv_evt_t ble_adv_evt)
{
    ret_code_t err_code;

    switch(ble_adv_evt)
    {
        case BLE_ADV_EVT_FAST:  // 快速广播模式
            NRF_LOG_INFO("Fast advertising.");
            err_code = bsp_indication_set(BSP_INDICATE_ADVERTISING);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_ADV_EVT_IDLE:  // 无效模式 
            sleep_mode_enter();
            break;

        default:
            break;
    }
}

四、不进入IDLE无效模式

广播模式的切换主要就是广播超时处理,如果没有这个超时时间,就不会切换到下一个模式,保持在一种广播模式不变。


APP_ADV_DURATION 设置为 0 即可。

五、扫描响应包

广播包有两种:广播数据包(Advertising Data)和扫描相应包(Scan Response),其中广播数据包是每个设备必须广播的,而扫描响应包是可选的。

每个包都是 31 个字节,数据包中分为有效数据(significant)和无效数据(non-significant)两部分。

  • 有效数据部分:包含若干个广播数据单元,称为 AD Structure。AD Structure 的组成是:第一个字节是长度值 Len,表示接下来的 Len 个字节是数据部分。数据部分的第一个字节表示数据的类型 AD Type,剩下的 Len - 1 个字节是真正的数据 AD Data。其中 AD Type 非常关键,决定了 AD Data 的数据代表的是什么和怎么解析。
  • 无效数据部分:因为广播包的长度必须是 31 个字节,如果有效数据部分不到 31 个字节,剩下的就用 0 补全。这部分的数据是无效的,解析时忽略即可。

而扫描响应包是为了给广播一个额外的 31 字节数据,用于主机 主动扫描 情况下,反馈数据使用。

比如私有任务的 128bit UUID,我们需要反馈给主机的时候,我们可以采用下面这种方式配置:



设置广播数据包和扫描响应包的数据长度,最长为 31 字节。




程序下载后,使用 nrf connect app 进行扫描,这个 APP 不点击连接,可以点击广播信息,实现主动扫描方式。


用抓捕器抓取,会出现扫描请求包,请求后出现一个扫描响应包。



扫描响应包 ScanRspData 里 11 和 07 分别表述空白字段和 128bit UUID

后面的内容就是该 UUID 的数值了。这样就广播了一个额外的 31 个字节来扩展广播内容。

• 由 Leung 写于 2020 年 2 月 6 日

• 参考:青风电子社区

你可能感兴趣的:(NRF52832学习笔记(9)——GAP从机端广播)