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 日

• 參考:青風電子社區

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章