简介
目前BLE已经发布到BLE5.2的标准,而蓝牙的传输速率,在BLE4.2和BLE5.0版本上分别进行了提升,这里介绍下使用Noridc芯片时,如何进行配置,以达到最快的传输速度(只讨论1主对1从的情况)。
基础概念介绍
这里对一些后面用到的名词做一下简单介绍,如果要详细了解,可以自行进行查找。
在BLE中,作为Client端,可以通过write command
和write request
方式进行发送数据,参考蓝牙规范Vol3.Part F.3.4.5
章节。
write command
: client向server发送命令,不需要server给应答。
write request
: clien向server发送请求,需要server给出ack。
作为server端,可以通过Notification
和indication
方式发送数据给client端,参考蓝牙规范Vol3.Part F.3.4.7
章节。
Notification
:server向client发送通知,不需要client应答。
Indication
: server向client发送通知,需要client应答。
ATT_MTU
: MAXIMUM TRANSMISSION UNIT,即在一个传输单元中的最大有效数据传输量,mtu的格式为1字节op_code,2字节attr handle,因此实际一帧传输的用户数据为ATT_MTU-3
。在BLE4.0中,只支持23的ATT_MTU,在BLE4.2以后,ATT_MTU最大可以是247字节。
DLE
: dat length extension,一般建议设置为ATT_MTU+4
,如果低于ATT_MTU,ATT data会被分包,这里记得就行,后面会单独开一个章节介绍一下。
CLE
:connection event length extension,链接事件长度扩展,在BLE4.0中,单个连接间隔,最多只能发送4(IOS)或者6(Android)个数据包,当打开CLE功能时,协议栈会判断链接间隔剩下的时间是否还支持发送数据包,如果还有时间,那么会继续进行数据发送,而不是受限于6个包。
connection interval
:链接间隔,主从机建立连接以后,每隔多久交互一次数据,IOS要求连接间隔最小时15ms,最大最小之间的时间差值最低15ms,因此IOS支持的最快连接间隔最小最大分别是15ms-30ms
,安卓端最低7.5ms。
官方理论值
Nordic官方公布的协议栈理论速度如下,这里只讨论最快速度,所以只看没有ack的Notification
和Indication
方式参考协议栈手册BLE data throughput
章节:
nRF51
nRF51不支持ATT_MTU
扩展,也不支持DLE
,CLE
和LE 2M PHY
,因此,使用最快的7.5ms的连接间隔,23字节的ATT_MTU,BW Config
配置为HIGH
时,Notification
和write command
方式速率为149.2kbps
,Indication
和write request
方式速率为10.6kbps
。
nRF52
nRF52系列支持上面所有的特性。
在ATT_MTU=23,其他特性都不打开
的情况下,也就是BLE4.0的标准特性下,传输速率如下图所示:
对比上面的51系列,可以看到,同样的LE 1M PHY条件下,52的速率为192kbps,速度提高了53kbps左右,而LE 2M PHY情况下,速率为256kbps。
在ATT_MTU=158,其他特性都不打开
的情况下,传输速率如下图所示:
可以看到,在2M PHY的情况下,速率为330kbps。
在ATT_MTU = 247,DLE=251,CLE = conn interval
情况下,传输速率如下:
在打开CLE
的情况下,传输速率最快达到1376kbps
,这个就是目前我们所能达到的最快的速率
。
可以看到,影响蓝牙传输速率的因素有:
- 连接间隔
- ATT_MTU SIZE
- DLE
- LE PHY
- CLE
代码实例测试
以下代码测试均以Nordic SDK中的ble_app_uart为基础,手机端使用安卓一加6
进行测试,手机支持上面提到的所有特性。:
nRF51
目前nordic支持51的最后一个版本sdk为SDK12.3,以该版本sdk为例进行测试,开发环境为keil。
由于nRF51在传输速率方面只支持BLE4.0,所以我们需要配置的参数只有连接间隔
和BW Config
选项。
对于链接间隔,理论上是越小越好,但是IOS要求最低是15ms-30ms,因此,使用这个范围,可以达到最快的传输速度,但是考虑到手机兼容性,可以适当的将这个最大值调高一点。
还有就是协议栈初始化时,配置BW Config为high。
ble_common_opt_conn_bw_t opt_conn_bw;
opt_conn_bw.role = role;
opt_conn_bw.conn_bw.conn_bw_tx = BLE_CONN_BW_HIGH;
opt_conn_bw.conn_bw.conn_bw_rx = BLE_CONN_BW_HIGH;
return sd_ble_opt_set(BLE_COMMON_OPT_CONN_BW, (ble_opt_t const *)&opt_conn_bw);
role的角色定义如下,根据自己需要配置。
/**@defgroup BLE_GAP_ROLES GAP Roles
* @note Not explicitly used in peripheral API, but will be relevant for central API.
* @{ */
#define BLE_GAP_ROLE_INVALID 0x0 /**< Invalid Role. */
#define BLE_GAP_ROLE_PERIPH 0x1 /**< Peripheral Role. */
#define BLE_GAP_ROLE_CENTRAL 0x2 /**< Central Role. */
51的配置部分比较少,需要注意的是,在发送数据的时候,只要api的返回值不是
NRF_ERROR_RESOURCES
或者在更早的sdk中,应该是NRF_ERROR_NO_TX_BUFFER
,就连续调用发送的api进行发送数据。
如果发生了上面的error,那么就等触发一个tx_complete事件,再继续发送。
nRF52系列
github测试demo:https://github.com/faithlm/ble_app_uart_throughout_test
目前最新版本SDK为sdk17.02,以该版本sdk进行测试,以该版本sdk为例进行测试,开发环境为ses。
从上面协议栈参数配置可知,如果要达到最快的速率,需要配置如下参数:
增大ATT_MTU
目前协议栈支持的最大 att_mtu 为247,如果要修改,可以更改sdk_config中的如下选项:
// <o> NRF_SDH_BLE_GATT_MAX_MTU_SIZE - Static maximum MTU size.
#ifndef NRF_SDH_BLE_GATT_MAX_MTU_SIZE
#define NRF_SDH_BLE_GATT_MAX_MTU_SIZE 247
#endif
增大DLE
目前版本协议栈支持最大的DLE为 251,可以通过sdk_config.h 的如下选项进行配置:
// <i> Requested BLE GAP data length to be negotiated.
#ifndef NRF_SDH_BLE_GAP_DATA_LENGTH
#define NRF_SDH_BLE_GAP_DATA_LENGTH 251
#endif
打开CLE
连接事件长度扩展,可以通过下面接口进行配置,配置放在协议栈初始化后面:
//status 为true时,打开CLE
void conn_evt_len_ext_set(bool status)
{
ret_code_t err_code;
ble_opt_t opt;
memset(&opt, 0x00, sizeof(opt));
opt.common_opt.conn_evt_ext.enable = status ? 1 : 0;
err_code = sd_ble_opt_set(BLE_COMMON_OPT_CONN_EVT_EXT, &opt);
APP_ERROR_CHECK(err_code);
}
这里还有一个参数需要注意,NRF_SDH_BLE_GAP_EVENT_LENGTH
,这个定义了连接事件的长度,单位是1.25ms,在sdk_config.h中配置,一般情况下,这个值可以等于连接间隔
:
// <o> NRF_SDH_BLE_GAP_EVENT_LENGTH - GAP event length.
// <i> The time set aside for this connection on every connection interval in 1.25 ms units.
#ifndef NRF_SDH_BLE_GAP_EVENT_LENGTH
#define NRF_SDH_BLE_GAP_EVENT_LENGTH 400
#endif
使用LE 2M PHY
:
目前支持3中LE PHY,分别是LE 1M PHY
,LE 2M PHY
,LE CODED PHY
,其中LE 2M PHY
用于高速率,,而理论距离是LE 1M PHY
的一半,而LE CODED PHY
用于长距离模式,LE 1M PHY
兼顾了距离和速率,可以根据自己实际需要进行选择,如果对传输距离有要求,需要谨慎选择。
LE PHY
可以通过调用sd_ble_gap_phy_update(uint16_t conn_handle, ble_gap_phys_t const *p_gap_phys)
接口发起更新,一般在peripheral端,收到ble_evt_connected
事件的时候进行请求更新。如下所示,即可配置为只支持2M 的PHY:
static test_params_t m_test_params =
{
.phys.tx_phys = BLE_GAP_PHY_2MBPS,
.phys.rx_phys = BLE_GAP_PHY_2MBPS,
};
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
uint32_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_CONNECTED:
//这里只写phy更新部分,其他的省略
err_code = sd_ble_gap_phy_update(p_gap_evt->conn_handle, &m_test_params.phys);
APP_ERROR_CHECK(err_code);
break;
}
}
调整连接间隔
由于启用了CLE功能,所以这里不需要根据nRF51的方式,使用最小的连接间隔,调整一个比较大的就可以,注意,需要保持NRF_SDH_BLE_GAP_EVENT_LENGTH
大于等于连接间隔,协议栈给出的参考值是50ms或者400ms,对应的速率分别是1328kbps和1376kbps。
#define CONN_INTERVAL_MIN (uint16_t)(MSEC_TO_UNITS(50, UNIT_1_25_MS)) /**< Minimum acceptable connection interval, in units of 1.25 ms. */
#define CONN_INTERVAL_MAX (uint16_t)(MSEC_TO_UNITS(60, UNIT_1_25_MS)) /**< Maximum acceptable connection interval, in units of 1.25 ms. */
增大协议栈发送buff
在ram空间有剩余的情况下,可以给协议栈配置更大的queue大小,增大缓存,默认值是1,根据实际情况配置。
下面的给协议栈配置了长度为7的队列缓冲区。
memset(&ble_cfg, 0, sizeof(ble_cfg));
ble_cfg.conn_cfg.conn_cfg_tag = APP_BLE_CONN_CFG_TAG;
ble_cfg.conn_cfg.params.gattc_conn_cfg.write_cmd_tx_queue_size = 7;
ble_cfg.conn_cfg.params.gatts_conn_cfg.hvn_tx_queue_size = 7;
err_code = sd_ble_cfg_set(BLE_CONN_CFG_GATTC, &ble_cfg, ram_start);
注意,经过上面的修改以后,在协议栈初始化时,会报NO_MEM的错误,,如下图所示,这个时候log会提示需要将ram修改为多大,根据需要进行调整即可:
经过上面修改,即可达到最大的传输速率,理论值是1376.2 kbps
github上的demo,增加了数据发送及时间记录,可以进行验证,测试结果如下图,8s时间,发送1220*1000 Byte数据,相当于传输速率为1220kbps,已经接近理论极限值。