一、背景
本篇是关于配置以及启动或关闭广播的流程,广播自定义数据包查看 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 日
• 参考:青风电子社区