主机扫描
蓝牙扫描可以用作发现周围的从机设备,为建立连接作准备;也可以用作发现空中的蓝牙广播信息。扫描是蓝牙的一个重要功能,也是主机所必备的。
扫描参数配置
ble_gap_scan_params_t 结构体下面是一些常用的扫描参数
.extended 是否接受延长广播
.active 主动扫描,可以获得额外的扫描响应包
.filter_policy 扫描过滤,可以选择过滤的方式
.scan_phys 扫描的速率
.interval 扫描间隔
.window 扫描窗口
.timeout 超时时间
一次扫描的过程包括:
- 1 打开射频开启扫描,并持续一段时间,这段时间是扫描窗口的参数设置。
- 2 关闭射频,等待下一次的扫描。这个时间由扫描间隔决定。
- 3 重复1,2,直到达到超时时间。
超时时间设为0,扫描事件将会一直进行;设置扫描窗口(开窗),应该不大于扫描间隔。如果扫描窗口等于扫描间隔,那么每一次扫描将会一直打开射频,没有空闲状态。当然,这样功耗也会很大;要根据实际需要,合理调整扫描窗口的占空比。
扫描过滤
扫描过滤,主要是可以在混乱的空中广播中筛选指定的广播。有两种方式:一种是白名单,一种是条件过滤。
白名单
白名单就是添加一组蓝牙MAC地址,只有指定MAC地址的设备才可以扫描,连接,从而达到过滤的目的。
过滤一个白名单设备
具体实现:
修改参数设置 .filter_policy = BLE_GAP_SCAN_FP_WHITELIST,
在scan_evt_handler 扫描事件里,增加 NRF_BLE_SCAN_EVT_WHITELIST_REQUEST,添加过滤的地址,然后调用sd_ble_gap_whitelist_set().
case NRF_BLE_SCAN_EVT_WHITELIST_REQUEST:{
ble_gap_addr_t whitelist_address;
ble_gap_addr_t const *p_whitelist_address;
memset(&whitelist_address,0,sizeof(whitelist_address));
whitelist_address.addr_id_peer = 1;
whitelist_address.addr_type = BLE_GAP_ADDR_TYPE_PUBLIC;
whitelist_address.addr[5] = 0x20;
whitelist_address.addr[4] = 0x21;
whitelist_address.addr[3] = 0x06;
whitelist_address.addr[2] = 0x17;
whitelist_address.addr[1] = 0x66;
whitelist_address.addr[0] = 0x66;
p_whitelist_address = &whitelist_address;
err_code = sd_ble_gap_whitelist_set(&p_whitelist_address,1);
if(err_code == NRF_SUCCESS)
{
NRF_LOG_INFO("SUCCESSFUL SETTING !");
}
APP_ERROR_CHECK(err_code);
}
然后再注册的扫描观察者的回调 nrf_ble_scan_on_ble_evt 中,收到广播报告BLE_GAP_EVT_ADV_REPORT后,nrf_ble_scan_on_adv_report函数中,判断是白名单,且设置了自动连接后,直接调用 sd_ble_gap_connect(),发起连接。
同时过滤多个白名单设备,并记录广播包和扫描响应包数目
在主函数中设置过滤的白名单:
uint16_t target_one_resp_bag_num = 0;
uint16_t target_one_adv_bag_num = 0;
uint16_t target_two_resp_bag_num = 0;
uint16_t target_two_adv_bag_num = 0;
uint16_t target_three_resp_bag_num = 0;
uint16_t target_three_adv_bag_num = 0;
uint8_t slaver_one_mac[6] = { 0x01, 0x00, 0x12, 0x08, 0x21, 0x20 };
uint8_t slaver_two_mac[6] = { 0x02, 0x00, 0x12, 0x08, 0x21, 0x20 };
uint8_t slaver_three_mac[6] = { 0x03, 0x00, 0x12, 0x08, 0x21, 0x20 };
在扫描事件回调函数中添加设置
case NRF_BLE_SCAN_EVT_WHITELIST_REQUEST: 设置白名单的条件
case NRF_BLE_SCAN_EVT_WHITELIST_ADV_REPORT: 是白名单的广播报告,在这里判断一下广播包还是扫描响应包,再判断一下是哪个从机,对应变量加一;
static void scan_evt_handler(scan_evt_t const *p_scan_evt)
{
ret_code_t err_code;
switch (p_scan_evt->scan_evt_id) {
case NRF_BLE_SCAN_EVT_CONNECTING_ERROR: {
err_code = p_scan_evt->params.connecting_err.err_code;
APP_ERROR_CHECK(err_code);
} break;
case NRF_BLE_SCAN_EVT_CONNECTED: {
ble_gap_evt_connected_t const *p_connected = p_scan_evt->params.connected.p_connected;
// Scan is automatically stopped by the connection.
NRF_LOG_INFO("Connecting to target %02x%02x%02x%02x%02x%02x",
p_connected->peer_addr.addr[0],
p_connected->peer_addr.addr[1],
p_connected->peer_addr.addr[2],
p_connected->peer_addr.addr[3],
p_connected->peer_addr.addr[4],
p_connected->peer_addr.addr[5]);
sd_ble_gap_rssi_start(p_scan_evt->params.connected.conn_handle, 8, 3);
sd_ble_gap_rssi_get(p_scan_evt->params.connected.conn_handle, &m_rssi, &m_channel);
NRF_LOG_INFO("rssi is %d", m_rssi);
NRF_LOG_INFO("channel id %d", m_channel);
} break;
case NRF_BLE_SCAN_EVT_SCAN_TIMEOUT: {
NRF_LOG_INFO("Scan timed out.");
scan_start();
} break;
#if WHITELIST_ENABLE
case NRF_BLE_SCAN_EVT_WHITELIST_REQUEST: {
NRF_LOG_INFO("whitelist set !");
ble_gap_addr_t whitelist_address[3];
ble_gap_addr_t const *p_whitelist_address[3];
/* 白名单1 */
memset(&whitelist_address[0], 0, sizeof(whitelist_address[0]));
whitelist_address[0].addr_id_peer = 1;
whitelist_address[0].addr_type = BLE_GAP_ADDR_TYPE_PUBLIC;
memcpy(whitelist_address[0].addr,slaver_one_mac,6);
p_whitelist_address[0] = &whitelist_address[0];
/* 白名单2 */
memset(&whitelist_address[1], 0, sizeof(whitelist_address[1]));
whitelist_address[1].addr_id_peer = 1;
whitelist_address[1].addr_type = BLE_GAP_ADDR_TYPE_PUBLIC;
memcpy(whitelist_address[1].addr,slaver_two_mac,6);
p_whitelist_address[1] = &whitelist_address[1];
/* 白名单3 */
memset(&whitelist_address[2], 0, sizeof(whitelist_address[2]));
whitelist_address[2].addr_id_peer = 1;
whitelist_address[2].addr_type = BLE_GAP_ADDR_TYPE_PUBLIC;
memcpy(whitelist_address[2].addr,slaver_three_mac,6);
p_whitelist_address[2] = &whitelist_address[2];
err_code = sd_ble_gap_whitelist_set(p_whitelist_address, 3);
if (err_code == NRF_SUCCESS) {
NRF_LOG_INFO("SUCCESSFUL SETTING !");
}
APP_ERROR_CHECK(err_code);
} break;
#endif
case NRF_BLE_SCAN_EVT_WHITELIST_ADV_REPORT: {
// NRF_LOG_INFO("white list adv report");
if (p_scan_evt->params.p_whitelist_adv_report->type.scan_response) {
if (!memcmp(p_scan_evt->params.p_whitelist_adv_report->peer_addr.addr,
slaver_one_mac,
6)) {
//NRF_LOG_INFO("slaver 1 response bag");
target_one_resp_bag_num += 1;
}
else if (!memcmp(p_scan_evt->params.p_whitelist_adv_report->peer_addr.addr,
slaver_two_mac,
6)) {
//NRF_LOG_INFO("slaver 2 response bag");
target_two_resp_bag_num += 1;
} else if (!memcmp(p_scan_evt->params.p_whitelist_adv_report->peer_addr.addr,
slaver_three_mac,
6)) {
//NRF_LOG_INFO("slaver 3 response bag");
target_three_resp_bag_num += 1;
}
}
else {
if (!memcmp(p_scan_evt->params.p_whitelist_adv_report->peer_addr.addr,
slaver_one_mac,
6)) {
//NRF_LOG_INFO("slaver 1 adv bag");
target_one_adv_bag_num += 1;
}
else if (!memcmp(p_scan_evt->params.p_whitelist_adv_report->peer_addr.addr,
slaver_two_mac,
6)) {
//NRF_LOG_INFO("slaver 2 adv bag");
target_two_adv_bag_num += 1;
} else if (!memcmp(p_scan_evt->params.p_whitelist_adv_report->peer_addr.addr,
slaver_three_mac,
6)) {
//NRF_LOG_INFO("slaver 3 adv bag");
target_three_adv_bag_num += 1;
}
}
} break;
default:
break;
}
}
在开一个定时器,每30s循环打印:
static void count_timeout(void *p_context)
{
static uint8_t i = 1;
NRF_LOG_INFO("run time is %d s", 30 * i);
mac_addr_printf(slaver_one_mac);
NRF_LOG_INFO("adv package is %d", target_one_adv_bag_num);
NRF_LOG_INFO("scan resp package is %d\n", target_one_resp_bag_num);
mac_addr_printf(slaver_two_mac);
NRF_LOG_INFO("adv package is %d", target_two_adv_bag_num);
NRF_LOG_INFO("scan resp package is %d\n", target_two_adv_bag_num);
mac_addr_printf(slaver_three_mac);
NRF_LOG_INFO("adv package is %d", target_three_adv_bag_num);
NRF_LOG_INFO("scan resp package is %d\n", target_three_resp_bag_num);
i++;
}
条件过滤
条件过滤,顾名思义可以提供设置的条件,来筛选出需要连接的设备,有以下几种过滤方式:
typedef enum
{
SCAN_NAME_FILTER, /**< Filter for names. */
SCAN_SHORT_NAME_FILTER, /**< Filter for short names. */
SCAN_ADDR_FILTER, /**< Filter for addresses. */
SCAN_UUID_FILTER, /**< Filter for UUIDs. */
SCAN_APPEARANCE_FILTER, /**< Filter for appearances. */
} nrf_ble_scan_filter_type_t;
可以看到有名字,UUID,地址,外观等多种过滤方式,可以选择一种或几种来作为过滤条件。
具体实现:
在扫描初始化函数里添加
err_code = nrf_ble_scan_filter_set(&m_scan, SCAN_UUID_FILTER, &m_nus_uuid);
APP_ERROR_CHECK(err_code);
err_code = nrf_ble_scan_filters_enable(&m_scan, NRF_BLE_SCAN_UUID_FILTER, false);
APP_ERROR_CHECK(err_code);
如果需要多个条件过滤:
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, &target_uuid);
APP_ERROR_CHECK(err_code);
err_code = nrf_ble_scan_filters_enable(&m_scan,
NRF_BLE_SCAN_NAME_FILTER | NRF_BLE_SCAN_UUID_FILTER,
false);
如果仔细观察上面两个库函数,可以自己简化
memcpy(m_scan.scan_filters.name_filter.target_name[m_scan.scan_filters.name_filter.name_cnt++],
m_peripheral_name,
strlen(m_peripheral_name));
替换 nrf_ble_scan_filter_set(&m_scan, SCAN_NAME_FILTER, m_target_periph_name);
err_code = nrf_ble_scan_filters_disable(&m_scan);
ASSERT(err_code == NRF_SUCCESS);
m_scan.scan_filters.name_filter.name_filter_enabled = true;
替换 nrf_ble_scan_filters_enable(&m_scan, SCAN_NAME_FILTER, false);
其他的可以过滤方式同样可以这样替换。
设置完成后,与白名单类似,扫描观察者的回调 nrf_ble_scan_on_ble_evt 中,收到广播报告BLE_GAP_EVT_ADV_REPORT,判断过滤条件,建立连接。
获取动态rssi
在scan_evt_handler 的 NRF_BLE_SCAN_EVT_CONNECTED 中,添加:
sd_ble_gap_rssi_start(p_scan_evt->params.connected.conn_handle, 8, 3);
sd_ble_gap_rssi_get(p_scan_evt->params.connected.conn_handle, &m_rssi, &m_channel);
NRF_LOG_INFO("rssi is %d", m_rssi);
NRF_LOG_INFO("channel id %d", m_channel);
在扫描观察者的回调函数 nrf_ble_scan_on_ble_evt 中,添加
case BLE_GAP_EVT_RSSI_CHANGED:
sd_ble_gap_rssi_start(p_ble_evt->evt.gatts_evt.conn_handle, 10, 3);
sd_ble_gap_rssi_get(p_ble_evt->evt.gatts_evt.conn_handle, &m_rssi, &m_channel);
NRF_LOG_INFO("rssi is %d", m_rssi);
NRF_LOG_INFO("channel id %d", m_channel);
break;
这样每次rssi发生变化,都会触发事件,然后打印rssi .
广播报告事件
当设置好扫描参数并开启扫描后,主机收到广播包之后,将会触发扫描报告事件BLE_GAP_EVT_ADV_REPORT
,广播的信息将会赋值给扫描报告结构体 ble_gap_evt_adv_report_t ,包含广播类型,地址,物理层传输速率,发射功率,rssi,广播数据等;可以在这里打印出广播信息
case BLE_GAP_EVT_ADV_REPORT:
NRF_LOG_INFO("adv report");
NRF_LOG_INFO("Connecting to target %02x%02x%02x%02x%02x%02x",
p_adv_report->peer_addr.addr[5],
p_adv_report->peer_addr.addr[4],
p_adv_report->peer_addr.addr[3],
p_adv_report->peer_addr.addr[2],
p_adv_report->peer_addr.addr[1],
p_adv_report->peer_addr.addr[0]);
NRF_LOG_INFO("adv tx power is %d", p_adv_report->tx_power);
if (p_adv_report->type.scan_response) {
if (p_adv_report->data.len > 0) {
NRF_LOG_INFO("scan response received !");
NRF_LOG_HEXDUMP_INFO(p_adv_report->data.p_data, p_adv_report->data.len);
} else {
NRF_LOG_INFO("empty bag");
}
}
else //广播数据
{
NRF_LOG_INFO("adv bag !");
NRF_LOG_HEXDUMP_INFO(p_adv_report->data.p_data, p_adv_report->data.len);
}
nrf_ble_scan_on_adv_report(p_scan_data, p_adv_report);
break;
如果你不需要连接,你就可以直接把数据广播出来,然后在 BLE_GAP_EVT_ADV_REPORT 事件中,直接按照私有协议解析数据。
如果你需要连接,我们来看一下 nrf_ble_scan_on_adv_report 函数,它主要是对扫描到广播进行处理,首先会判断你开了哪些过滤条件,比如名字,扫描初始化的时候,name_filter_enabled 被打开,
- init_scan.connect_if_match = true;
nrf_ble_scan_on_adv_report函数中,会判断,并且使能过滤匹配的标志 is_filter_matched,
if (name_filter_enabled) {
filter_cnt++;
if (adv_name_compare(p_adv_report, p_scan_ctx)) {
filter_match_cnt++;
// Information about the filters matched.
scan_evt.params.filter_match.filter_match.name_filter_match = true;
is_filter_matched = true;
}
}
函数最后,会根据上面的条件判断,进行连接操作
if (all_filter_mode && (filter_match_cnt == filter_cnt)) {
scan_evt.scan_evt_id = NRF_BLE_SCAN_EVT_FILTER_MATCH;
nrf_ble_scan_connect_with_target(p_scan_ctx, p_adv_report);
}
else if ((!all_filter_mode) && is_filter_matched) {
scan_evt.scan_evt_id = NRF_BLE_SCAN_EVT_FILTER_MATCH;
nrf_ble_scan_connect_with_target(p_scan_ctx, p_adv_report);
判断 connect_if_match
- 0 退出
- 1 建立连接
static void nrf_ble_scan_connect_with_target(nrf_ble_scan_t const *const p_scan_ctx,
ble_gap_evt_adv_report_t const *const p_adv_report)
{
//NRF_LOG_INFO("start connect !");
ret_code_t err_code;
scan_evt_t scan_evt;
// For readability.
ble_gap_addr_t const * p_addr = &p_adv_report->peer_addr;
ble_gap_scan_params_t const *p_scan_params = &p_scan_ctx->scan_params;
ble_gap_conn_params_t const *p_conn_params = &p_scan_ctx->conn_params;
uint8_t con_cfg_tag = p_scan_ctx->conn_cfg_tag;
// Return if the automatic connection is disabled.
if (!p_scan_ctx->connect_if_match) {
return;
}
// Stop scanning.
nrf_ble_scan_stop();
memset(&scan_evt, 0, sizeof(scan_evt));
// Establish connection.
err_code = sd_ble_gap_connect(p_addr, p_scan_params, p_conn_params, con_cfg_tag);
NRF_LOG_DEBUG("Connecting");
scan_evt.scan_evt_id = NRF_BLE_SCAN_EVT_CONNECTING_ERROR;
scan_evt.params.connecting_err.err_code = err_code;
NRF_LOG_DEBUG("Connection status: %d", err_code);
// If an error occurred, send an event to the event handler.
if ((err_code != NRF_SUCCESS) && (p_scan_ctx->evt_handler != NULL)) {
p_scan_ctx->evt_handler(&scan_evt);
}
}