一、背景
本篇是關於配置以及啓動或關閉廣播的流程,廣播自定義數據包查看 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 日
• 參考:青風電子社區