Long Range 是BLE 5.0中新增加的特性,主要是通过扩频的方式,提高了天线接收的灵敏度,同时将最大的发送功率,从4.0/4.1/4.2中的10mW增大到5.0的100mW。相对于BLE来说,确实增加了通信的距离。
Long Range 并没有那么神奇,主要代码变动在于PHY层的编码方式,应用层是一样的。将普通的BLE方式修改为Long Range模式也非常简单,如下:
从机设备(BLE Peripheral):
- sdk_config配置
设置广播包数据长度,255Bytes
/**@defgroup BLE_GAP_ADV_SET_DATA_SIZES Advertising data sizes.
* @{ */
#define BLE_GAP_ADV_SET_DATA_SIZE_MAX (255) /**< Maximum data length for an advertising set.
2. 广播初始化配置
因为longrange模式需要工作在可连接,扫描不可回应的状态,所以广播回应包需要设置为NULL,禁止respond。
另外,广播参数主要是编码方式改为PHY CODED方式
adv_params.primary_phy = BLE_GAP_PHY_CODED;
adv_params.secondary_phy = BLE_GAP_PHY_CODED;
adv_params.properties.type = BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED;
详情看如下初始化代码:
/**@brief Advertising functionality initialization.
*
* @details Encodes the required advertising data and passes it to the stack.
* Also builds a structure to be passed to the stack when starting advertising.
*/
static void advertising_init(void)
{
uint32_t err_code;
ble_advdata_t advdata;
ble_gap_adv_params_t adv_params;
static uint8_t advdata_buff[BLE_GAP_ADV_SET_DATA_SIZE_MAX];
static uint8_t srdata_buff[BLE_GAP_ADV_SET_DATA_SIZE_MAX];
uint16_t advdata_buff_len = BLE_GAP_ADV_SET_DATA_SIZE_MAX;
uint16_t srdata_buff_len = BLE_GAP_ADV_SET_DATA_SIZE_MAX;
uint8_t flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
// Build and set advertising data.
memset(&advdata, 0, sizeof(advdata));
//设置广播包
advdata.name_type = BLE_ADVDATA_FULL_NAME;
advdata.include_appearance = true;
advdata.flags = flags;
advdata.uuids_complete.uuid_cnt = sizeof(adv_uuids) / sizeof(adv_uuids[0]);
advdata.uuids_complete.p_uuids = adv_uuids;
//编码
err_code = ble_advdata_encode(&advdata, advdata_buff, &advdata_buff_len);
APP_ERROR_CHECK(err_code);
memset(&advdata_enc, 0, sizeof(advdata_enc));
//广播包赋值
advdata_enc.adv_data.p_data = advdata_buff;
advdata_enc.adv_data.len = advdata_buff_len;
//设置回应包
// Build and set advertising data.
memset(&advdata, 0, sizeof(advdata));
//设置厂商信息
ble_advdata_manuf_data_t manuf_data;
manuf_data.company_identifier = COMPANY_IDENTIFIER;
manuf_data.data.size = ADV_ADDL_MANUF_DATA_LEN;
manuf_data.data.p_data = m_add_adv_manuf_data;
advdata.p_manuf_specific_data = &manuf_data;
//编码
err_code = ble_advdata_encode(&advdata, srdata_buff, &srdata_buff_len);
APP_ERROR_CHECK(err_code);
//回应包赋值
advdata_enc.scan_rsp_data.p_data = NULL;
advdata_enc.scan_rsp_data.len = 0;
// Initialise advertising parameters (used when starting advertising).
memset(&adv_params, 0, sizeof(adv_params));
adv_params.primary_phy = BLE_GAP_PHY_CODED;
adv_params.secondary_phy = BLE_GAP_PHY_CODED;
adv_params.properties.type = BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED;
adv_params.filter_policy = BLE_GAP_ADV_FP_ANY;
adv_params.interval = APP_ADV_INTERVAL;
adv_params.duration = APP_ADV_DURATION;
err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &advdata_enc, &adv_params);
APP_ERROR_CHECK(err_code);
}
3. 设置广播功率并启动广播
建议设置为最高的8DB,如果电量有限,也可以设置低一些发射功率。代码如下:
void advertising_start(void)
{
uint32_t err_code;
//设置广播功率,8dB,连接状态也将会是继承这个发射功率
err_code = sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_ADV, m_adv_handle, 8);
APP_ERROR_CHECK(err_code);
err_code = sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG);
if (err_code != NRF_SUCCESS && err_code != NRF_ERROR_INVALID_STATE)
{
APP_ERROR_HANDLER(err_code);
}
}
主机设备(BLE Central):
- sdk_config配置
配置扫描广播包和从机数目和从机一致,255Bytes
// <o> NRF_BLE_SCAN_BUFFER - Data length for an advertising set.
#ifndef NRF_BLE_SCAN_BUFFER
#define NRF_BLE_SCAN_BUFFER 255
#endif
2. 通过配置好扫描参数和扫描功率启动蓝牙扫描
扫描PHY层参数需要匹配广播的参数
//蓝牙扫描参数配置
static const ble_gap_scan_params_t m_scan_params =
{
.active = 1,
.extended = 1,
.interval = NRF_BLE_SCAN_SCAN_INTERVAL,
.window = NRF_BLE_SCAN_SCAN_WINDOW,
.timeout = NRF_BLE_SCAN_SCAN_DURATION,
.filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL,
.scan_phys = BLE_GAP_PHY_CODED,
};
//蓝牙扫描数据,提供给系统一个缓冲
static ble_data_t m_scan_buffer;
static uint8_t scan_buffer_data[NRF_BLE_SCAN_BUFFER];
/**@brief Function to start scanning. */
uint32_t ble_scan_start(void)
{
ret_code_t ret;
m_scan_buffer.p_data = scan_buffer_data;
m_scan_buffer.len = NRF_BLE_SCAN_BUFFER;
/* It is ok to ignore the function return value here, because this function can return NRF_SUCCESS or
NRF_ERROR_INVALID_STATE, when app is not in the scanning state.*/
UNUSED_RETURN_VALUE(sd_ble_gap_scan_stop());
//设置发送功率,8dB,连接也将会是继承这个发射功率
ret = sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_SCAN_INIT, BLE_CONN_HANDLE_INVALID, 8);
APP_ERROR_CHECK(ret);
ret = sd_ble_gap_scan_start(&m_scan_params, &m_scan_buffer);
APP_ERROR_CHECK(ret);
return ret;
}
实际测量和应用
1. 经笔者实际体验和使用,发现longrange模式下时,常规的BLE主机是无法扫描到该设备的,只有两个设备同时处于longrange模式才能通信,这会限制从设备和APP上应用的场景。
2. 经测试,RSSI受天线和遮挡物的影响很大,基本上没法和距离形成比例关系,不建议通过RSSI来估算距离。
3. 远距离发起连接容易超时导致连接失败,因为BLE连接需要比较严格的通信时序,一旦握手时期未收到指定的RF数据包,则会导致连接失败。这种表现比如在近处连接好后,走到远处可以持续通信。但是在远处可以扫描到设备,却无法正常连接。
因此笔者比较推荐longrange的应用可以通过更新可变数据的广播包来实现接收数据。如果需要双向通信,可以通过编程让设备运行于主从模式下,既扫描,又广播以达到稳定的异步通信过程。
关于BLE可变数据广播包,笔者推荐利用广播超时模式,在超时事件里更新广播数据即可,广播包数据更新放在厂商信息区域,不影响其他蓝牙广播数据段。实现代码如下:
static void advertising_updata(uint8_t adv_manuf_data[ADV_ADDL_MANUF_DATA_LEN])
{
uint32_t err_code;
ble_advdata_t advdata;
ble_gap_adv_params_t adv_params;
static uint8_t advdata_buff[BLE_GAP_ADV_SET_DATA_SIZE_MAX];
uint16_t advdata_buff_len = BLE_GAP_ADV_SET_DATA_SIZE_MAX;
uint8_t flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
// Build and set advertising data.
memset(&advdata, 0, sizeof(advdata));
//设置广播包
advdata.name_type = BLE_ADVDATA_FULL_NAME;
advdata.include_appearance = true;
advdata.flags = flags;
advdata.uuids_complete.uuid_cnt = sizeof(adv_uuids) / sizeof(adv_uuids[0]);
advdata.uuids_complete.p_uuids = adv_uuids;
//设置厂商信息
ble_advdata_manuf_data_t manuf_data;
manuf_data.company_identifier = 0x1234;
manuf_data.data.size = ADV_ADDL_MANUF_DATA_LEN;
manuf_data.data.p_data = adv_manuf_data;
advdata.p_manuf_specific_data = &manuf_data;
//编码
err_code = ble_advdata_encode(&advdata, advdata_buff, &advdata_buff_len);
APP_ERROR_CHECK(err_code);
//memset(&advdata_enc, 0, sizeof(advdata_enc));
//广播包赋值
advdata_enc.adv_data.p_data = advdata_buff;
advdata_enc.adv_data.len = advdata_buff_len;
//回应包赋值
advdata_enc.scan_rsp_data.p_data = NULL;
advdata_enc.scan_rsp_data.len = 0;
// Initialise advertising parameters (used when starting advertising).
memset(&adv_params, 0, sizeof(adv_params));
adv_params.primary_phy = BLE_GAP_PHY_CODED;
adv_params.secondary_phy = BLE_GAP_PHY_CODED;
adv_params.properties.type = BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED;;
adv_params.filter_policy = BLE_GAP_ADV_FP_ANY;
adv_params.interval = APP_ADV_INTERVAL;
adv_params.duration = APP_ADV_DURATION;
err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &advdata_enc, NULL);
APP_ERROR_CHECK(err_code);
}
注意:这个广播更新函数必须在广播超时或者停止后才能调用,否则容易出错导致产生err_code