一、背景
1.1 蓝牙协议栈
链路层(LL)控制设备的射频状态,有五个设备状态:待机、广播、扫描、初始化和连接。
广播 为广播数据包,而 扫描 则是监听广播。
GAP通信中角色,中心设备(Central - 主机) 用来扫描和连接 外围设备(Peripheral - 从机)。
大部分情况下外围设备通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。
也有些情况是不需要连接的,只要外设广播自己的数据即可,用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。
1.2 扫描概念
扫描是一个在一定范围内用来寻址其他低功耗蓝牙设备广播的过程。扫描者设备在扫描过程中会使用广播信道。与广播过程不同的是,扫描过程没有严格的时间定义和信道规则。扫描过程应该按照由 Host 层所设定扫描定时参数还运行。
1.2.1 被动扫描
被动扫描:在被动扫描中,扫描设备应该仅仅去监听广播包,而不向广播设备发送任何数据。
一旦扫描参数设置完成,主机就可以在协议栈中发送命令启动扫描。扫描过程中,如果控制器接收到符合过滤策略或其他规则的广播数据包,则发送一个广播报告事件给主机。报告事件除了有广播者的设备地址外,还包括广播数据包中的数据,以及接收广播数据包时的信号接收强度。我们可以利用该信号强度以及位于广播包中的发射功率,共同确定信号的路径损失,从而给出大致的范围,这个应用就是防丢器和蓝牙定位。
1.2.2 主动扫描
主动扫描:不仅可以捕获到从端设备的广播数据包,还可以捕获扫描响应包,并区分它们。
控制器收到扫描数据后将向主机发送一个广播报告事件(adv_report),该事件包括了链路层数据包的广播类型。因此,主机能够判断从端设备是否可以连接或扫描,并且区分出广播数据包和扫描响应包。
二、配置扫描参数
2.1 扫描参数变量
在 main.c 中
static ble_gap_scan_params_t m_scan_param = /**< Scan parameters requested for scanning and connection. */
{
.active = 0x01, // 主动扫描
.interval = NRF_BLE_SCAN_SCAN_INTERVAL, // 扫描间隔
.window = NRF_BLE_SCAN_SCAN_WINDOW, // 扫描窗口
.filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL,
.timeout = NRF_BLE_SCAN_SCAN_DURATION, // 扫描超时
.scan_phys = BLE_GAP_PHY_1MBPS,
.extended = true,
};
- active:是否主动扫描,配置为1则是主动扫描,0则是被动扫描
- interval:扫描间隔,控制器间隔多长时间扫描一次,也就是两个连续的扫描窗口开始时间的时间间隔。在 NRF 上设置为 0x0004 and 0x4000 in 0.625ms units(2.5ms 到 10.24s)
- window:扫描窗口,每次扫描所持续的时间,在持续时间内,扫描设备一直在广播信道上运行。在 NRF 上设置为 0x0004 and 0x4000 in 0.625ms units(2.5ms 到 10.24s)
- filter_policy:扫描筛选策略,也就死说接受任何广播数据或者仅仅接受白名单设备的广播数据包。实际上就死决定是否使用白名单过滤广播数据包。这里注意一点,如果定向广播数据包中的目的地址并非是自己的,那么该数据必须抛弃,即使广播数据包的发送者在自己的白名单中。
- timeout:扫描超时,超过指定的时间后,没有扫描到设备将停止扫描。在 NRF 上设置为 0x0001 and 0xFFFF in seconds,设置为 0 则认为没有 timeout
- scan_phys:扫描的物理层速度
注意:扫描窗口和扫描间隔两个参数非常重要。扫描窗口的设置要小于或等于扫描间隔,并且都要是 0.625ms 的整倍数。这两个参数决定了主机控制器的扫描占空比。比如,如果设置扫描间隔为 100 ms,扫描窗口为 10ms ,那么主机控制器的扫描占空比就死 10%。特别注意可以捕获的定向数据包的最低占空比为 0.4%,即每一秒中扫描时间为 3.75ms,这些时间设置在任何蓝牙 4.x 处理器中都是一致的,不仅仅限于 NRF 处理器。
如果把时间间隔设置为相同的大小,那么控制器会进行连续扫描,每个间隔会改变扫描频率,也就死切换扫描信道。
2.2 扫描过滤相关宏
/**@defgroup BLE_GAP_SCAN_FILTER_POLICIES GAP Scanner filter policies
* @{ */
#define BLE_GAP_SCAN_FP_ACCEPT_ALL 0x00 /**< Accept all advertising packets except directed advertising packets
not addressed to this device. */
#define BLE_GAP_SCAN_FP_WHITELIST 0x01 /**< Accept advertising packets from devices in the whitelist except directed
packets not addressed to this device. */
#define BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED 0x02 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_ACCEPT_ALL.
In addition, accept directed advertising packets, where the advertiser's
address is a resolvable private address that cannot be resolved. */
#define BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED 0x03 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_WHITELIST.
In addition, accept directed advertising packets, where the advertiser's
address is a resolvable private address that cannot be resolved. */
/**@} */
- BLE_GAP_SCAN_FP_ACCEPT_ALL:接收所有的广播包,除去广播地址不是指向该设备的定向广播。
- BLE_GAP_SCAN_FP_WHITELIST:接收在白名单里所有的广播,除去广播地址不是指向该设备的定向广播。
- BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED:接收所有的广播包,包含定向广播包。这里如果广播MAC地址是私密地址,这里是无法被解析的。
- BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED:接收白名单里所有的广播包,包含定向广播包。这里如果广播MAC地址是私密地址,这里是无法被解析的。
2.3 定义扫描模块
NRF_BLE_SCAN_DEF(m_scan); /**< Scanning module instance. */
2.4 初始化扫描参数
/**@brief Function for initialization the scanning and setting the filters.
*/
static void scan_init(void)
{
ret_code_t err_code;
nrf_ble_scan_init_t init_scan;
memset(&init_scan, 0, sizeof(init_scan));
init_scan.p_scan_param = &m_scan_param;
err_code = nrf_ble_scan_init(&m_scan, &init_scan, scan_evt_handler);
APP_ERROR_CHECK(err_code);
if(strlen(m_target_periph_name) != 0)
{
err_code = nrf_ble_scan_filter_set(&m_scan,
SCAN_NAME_FILTER,
m_target_periph_name);
APP_ERROR_CHECK(err_code);
}
err_code = nrf_ble_scan_filter_set(&m_scan,
SCAN_UUID_FILTER,
&m_adv_uuids[HART_RATE_SERVICE_UUID_IDX]);
APP_ERROR_CHECK(err_code);
err_code = nrf_ble_scan_filter_set(&m_scan,
SCAN_UUID_FILTER,
&m_adv_uuids[RSCS_SERVICE_UUID_IDX]);
APP_ERROR_CHECK(err_code);
err_code = nrf_ble_scan_filters_enable(&m_scan,
NRF_BLE_SCAN_ALL_FILTER,
false);
APP_ERROR_CHECK(err_code);
}
在 main 函数中,执行 scan_init() 进行初始化扫描参数
/**@brief Function for initializing the application main entry.
*/
int main(void)
{
bool erase_bonds;
/*-------------------------- 外设驱初始化 ---------------------------*/
// Initialize.
log_init(); // 日志驱动初始化
/*-------------------------- 蓝牙协议栈初始化 ---------------------------*/
power_management_init(); // 能量初始化
ble_stack_init(); // 协议栈初始化
scan_init(); // 扫描初始化
gap_params_init();
gatt_init();
conn_params_init(); // 连接参数初始化
db_discovery_init();
peer_manager_init();
services_init(); // 服务初始化
advertising_init(); // 广播初始化
/*-------------------------- 开启应用 ---------------------------*/
// Start execution.
NRF_LOG_INFO("Relay example started.");
if(erase_bonds == true)
{
NRF_LOG_INFO("delete_bonds");
// Scanning and advertising is done upon PM_EVT_PEERS_DELETE_SUCCEEDED event.
delete_bonds();
}
else
{
NRF_LOG_INFO("adv_scan_start");
adv_scan_start();
}
// Enter main loop.
for(;;)
{
idle_state_handle();
}
}
三、开启扫描
包含头文件
#include "nrf_ble_scan.h"
开启扫描函数
/**@brief Function for initializing the scanning.
*/
void scan_start(void)
{
ret_code_t err_code;
err_code = nrf_ble_scan_start(&m_scan);
APP_ERROR_CHECK(err_code);
}
四、关闭扫描
包含头文件
#include "nrf_ble_scan.h"
关闭扫描函数
nrf_ble_scan_stop();
五、广播报告
控制器收到广播数据包后向主机发送一个广播报告事件。
5.1 扫描事件处理
在 main.c 中
/**@brief Function for handling BLE events from the central application.
*
* @details This function parses scanning reports and initiates a connection to peripherals when a
* target UUID is found. It updates the status of LEDs used to report the central application
* activity.
*
* @param[in] p_ble_evt Bluetooth stack event.
*/
static void on_ble_central_evt(ble_evt_t const *p_ble_evt)
{
ret_code_t err_code;
ble_gap_evt_t const *p_gap_evt = &p_ble_evt->evt.gap_evt;
switch(p_ble_evt->header.evt_id)
{
···
case BLE_GAP_EVT_ADV_REPORT:
GetAdvReport(p_gap_evt->params.adv_report);
break;
···
5.2 获取广播报告
/**
@brief 获取广播数据
@param advReport - 广播报告结构体
@return 无
*/
void GetAdvReport(ble_gap_evt_adv_report_t advReport)
{
// 在这里加入对广播报告的处理
}
5.2.1 ble_gap_evt_adv_report_t
/**@brief Event structure for @ref BLE_GAP_EVT_ADV_REPORT.
*
* @note If @ref ble_gap_adv_report_type_t::status is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA,
* not all fields in the advertising report may be available.
*
* @note When ble_gap_adv_report_type_t::status is not set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA,
* scanning will be paused. To continue scanning, call @ref sd_ble_gap_scan_start.
*/
typedef struct
{
ble_gap_adv_report_type_t type; /**< Advertising report type. See @ref ble_gap_adv_report_type_t. */
ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr is resolved:
@ref ble_gap_addr_t::addr_id_peer is set to 1 and the address is the
peer's identity address. */
ble_gap_addr_t direct_addr; /**< Contains the target address of the advertising event if
@ref ble_gap_adv_report_type_t::directed is set to 1. If the
SoftDevice was able to resolve the address,
@ref ble_gap_addr_t::addr_id_peer is set to 1 and the direct_addr
contains the local identity address. If the target address of the
advertising event is @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE,
and the SoftDevice was unable to resolve it, the application may try
to resolve this address to find out if the advertising event was
directed to us. */
uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising packet was received.
See @ref BLE_GAP_PHYS. */
uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising packet was received.
See @ref BLE_GAP_PHYS. This field is set to @ref BLE_GAP_PHY_NOT_SET if no packets
were received on a secondary advertising channel. */
int8_t tx_power; /**< TX Power reported by the advertiser in the last packet header received.
This field is set to @ref BLE_GAP_POWER_LEVEL_INVALID if the
last received packet did not contain the Tx Power field.
@note TX Power is only included in extended advertising packets. */
int8_t rssi; /**< Received Signal Strength Indication in dBm of the last packet received. */
uint8_t ch_index; /**< Channel Index on which the last advertising packet is received (0-39). */
uint8_t set_id; /**< Set ID of the received advertising data. Set ID is not present
if set to @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */
uint16_t data_id:12; /**< The advertising data ID of the received advertising data. Data ID
is not present if @ref ble_gap_evt_adv_report_t::set_id is set to
@ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */
ble_data_t data; /**< Received advertising or scan response data. If
@ref ble_gap_adv_report_type_t::status is not set to
@ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the data buffer provided
in @ref sd_ble_gap_scan_start is now released. */
ble_gap_aux_pointer_t aux_pointer; /**< The offset and PHY of the next advertising packet in this extended advertising
event. @note This field is only set if @ref ble_gap_adv_report_type_t::status
is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. */
} ble_gap_evt_adv_report_t;
- type:广播报告类型
- peer_addr:扫描设备的MAC地址。如果peer_addr被解析,那么参数ble_gap_addr_t::addr_id_peer被设置为1,同时该地址是对等方的身份地址
- direct_addr:扫描定向广播的MAC地址。当参数ble_gap_adv_report_type_t::directed被设置为1,包含目的地址的广播事件。如果协议栈能够解析地址,参数ble_gap_addr_t::addr_id_peer被设置为1,direct_addr包含本地的认证地址。如果广播事件的目的地址是BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE类型,协议栈是服务解析的,应用程序可以尝试解析这个地址,以查明广播是否指向我们
- primary_phy:主广播包的物理层速度
- secondry_phy:第二扩展广播包的物理层速度。看参数BLE_GAP_PHYS。如果在二级广播频道上没有收到信息包,则此字段为0
- tx_power:TX功率报告由广播客户端在最后收到的包头。如果最后收到的数据包不包含TX字段,则这个字段被设置为BLE_GAP_POWER_LEVEL_INVALID
- rssi:接收的信号强度
- ch_index:接收到最后一个广播包的频道(0-39)
- set_id:设置接收广播数据的ID
- data_id:接收的广播数据的广播数据ID
- data:接收的广播数据包或者扫描响应包。如果参数ble_gap_adv_report_type_t::status没有被设置为参数BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA,参数sd_ble_gap_scan_start中提供的数据缓存立即释放
- aux_pointer:在此扩展广播事件中,下一个广播包的偏移量。注意:只有当ble_gap_adv_report_type_t::status被设置为BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA时,该字段才会被设置
5.2.2 ble_gap_adv_report_type_t
/**@brief Advertising report type. */
typedef struct
{
uint16_t connectable : 1; /**< Connectable advertising event type. */
uint16_t scannable : 1; /**< Scannable advertising event type. */
uint16_t directed : 1; /**< Directed advertising event type. */
uint16_t scan_response : 1; /**< Received a scan response. */
uint16_t extended_pdu : 1; /**< Received an extended advertising set. */
uint16_t status : 2; /**< Data status. See @ref BLE_GAP_ADV_DATA_STATUS. */
uint16_t reserved : 9; /**< Reserved for future use. */
} ble_gap_adv_report_type_t;
- connectable:可连接广播事件类型
- scannable:可扫描广播事件类型
- directed:定向广播事件类型
- scan_response:收到扫描响应
- extended_pdu:收到加长广播
- status:数据状态,参考参数BLE_GAP_ADV_DATA_STATUS
/**@defgroup BLE_GAP_ADV_DATA_STATUS GAP Advertising data status
* @{ */
// 广播包的所有数据已收到
#define BLE_GAP_ADV_DATA_STATUS_COMPLETE 0x00 /**< All data in the advertising event have been received. */
// 需要接收更多的数据
#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA 0x01 /**< More data to be received.
@note This value will only be used if
@ref ble_gap_scan_params_t::report_incomplete_evts and
@ref ble_gap_adv_report_type_t::extended_pdu are set to true. */
// 不完整的数据,缓冲区大小不足以接收更多
#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED 0x02 /**< Incomplete data. Buffer size insufficient to receive more.
@note This value will only be used if
@ref ble_gap_adv_report_type_t::extended_pdu is set to true. */
// 未能接收剩余的数据
#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MISSED 0x03 /**< Failed to receive the remaining data.
@note This value will only be used if
@ref ble_gap_adv_report_type_t::extended_pdu is set to true. */
/**@} */
- reserved:保留部分
5.2.3 ble_data_t
/**@brief Data structure. */
typedef struct
{
uint8_t *p_data; /**< Pointer to the data buffer provided to/from the application. */
uint16_t len; /**< Length of the data buffer, in bytes. */
} ble_data_t;
- p_data:广播或扫描响应的数据
- len:广播或扫描响应的数据长度
六、提取广播内容
/**
@brief 获取广播数据或扫描应答数据中adType对应的数据
@param adType -[in] 数据类型
@param pAdvData -[in] 广播包或扫描应答包结构体
@param pTypeData -[out] 对应的adType类型数据结构体
@return 0 - 成功;5 - 未找到
*/
uint32_t GetAdtypeData(uint8_t adType, uint8_array_t *pAdvData, uint8_array_t *pTypeData)
{
uint32_t index = 0;
uint8_t *pData;
pData = pAdvData->p_data;
while(index < pAdvData->size) // 判断当前指针是否还未到包尾
{
uint8_t fieldLength = pData[index];
uint8_t fieldType = pData[index + 1];
if(fieldType == adType) // 如果找到了adType
{
pTypeData->p_data = &pData[index + 2]; // 数据段内容
pTypeData->size = fieldLength - 1; // 数据段长度
return NRF_SUCCESS; // 找到了adType
}
index += fieldLength + 1; // 没找到adType则指向下一个数据段
}
return NRF_ERROR_NOT_FOUND; // 本数据串中没有找到对应adType
}
6.1 提取名字
uint32_t errCode;
// 获取名字全称
errCode = GetAdtypeData(BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME, pAdvData, pTypeData);
// 没有,则获取名字缩写
if(errCode != NRF_SUCCESS)
{
errCode = GetAdtypeData(BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME, pAdvData, pTypeData);
}
6.2 提取UUID
uint32_t errCode;
// 获取部分服务UUID列表
errCode = GetAdtypeData(BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE, pAdvData, pTypeData);
// 没有,则获取全部服务UUID列表
if(errCode != NRF_SUCCESS)
{
errCode = GetAdtypeData(BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE, pAdvData, pTypeData);
}
6.2 提取自定义数据
GetAdtypeData(BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA, pAdvData, pTypeData);
• 由 Leung 写于 2020 年 3 月 14 日
• 参考:青风电子社区