# BLE 一次能传多少数据 # BLE 到底一包能够收发多少数据是很多开发者都会关心的。我们知道 BLE 5.0 物理层设计 2Mpbs 的码元率,实际数据传输速率远远到不了这个级别。主要原因是无法直接单次发送 1M 或者 1K 数据,必须按照我们知道的 `ATT_MTU` 作为最大值进行拆包发送,不断拆包的过程中导致整体蓝牙有效数据吞吐量下降。本章节,我们实用性出发研究如何究竟蓝牙一包能够发送多少数据。 以下是 GAPP Client 进行特征值写请求的完整抓包,`AttValue` 表示写入的值,该值的最大长度到底是多少呢?本文将详细研究并且验证。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/att_wirte.png) 本文的正确打开姿势,我们希望你已经仔细阅读并理解协议栈的 L2CAP,和 BLE4.2/5.0 全新支持的 LE 数据扩展功能。 * Logical Link Control and Adaptation Layer Protocol (L2CAP) * LE Data Length Extension ## 术语 ## |术语|解释| |---|----| |PDU|Potocal Data Unit 协议数据单元,对于协议栈数据单元我们应该理解为蓝牙协议分层的消息实体的完整封装,例如 LL PDU 表示逻辑链路层的协议消息的完整封装| |MTU|Maximum Transmission Unit 最大传输单元,和本文研究的 BLE 数据包的最大长度意思接近| |LL|Logic Link 逻辑链路层| |PHY| physical layer 物理层 蓝牙协议的最底层| ## 分析 ## 在 BLE PHY 包格式由以下组成:排除 前导码、访问地址、CRC 地址 对于 BLE4.0/BLE4.1 上层协议(LL)最大可以包含** 39 **字节长度的 PDU(协议数据单元)。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/ll_layer_pakcet_format4.0.png) 更新后的 BLE4.2/BLE5.0 直接升级硬件将这一长度扩展到** 257 **,默认了兼容 BLE4.0/4.1 还是采用了上面的** 39 **设计。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/ll_layer_pakcet_format.png) >**注意**: PHY 的数据包最大长度直接由硬件决定,也是限制 BLE 数据包大小的根本原因。换句话说,PHY 的硬件寄存器/存储器/ FIFO 等直接决定了单个蓝牙数据包最大长度。 从蓝牙协议分层设计来看,逻辑链路层是最底层,已经正常连接的两个设备之间通信 PDU 主要由 `LL Data PDU` 组成。`LL Playload` 作为上层协议的负载。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/data_channel_pdu.png) 逻辑链路层再往上就是 L2CAP ——逻辑逻辑链路控制适配协议,如下的 Basic L2CAP Header+Information Payload 组成基本帧( B-frame )。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/basic_l2cap.png) L2CAP 协议再上就是操作特征值的 ATT 层,这一层数据长度被最大传输单元 `ATT_MTU` 限制,ATT_Payload 由 `Attribute OPCode` + `Attribute Parames` + `Authentication Signature`(可选)组成。排除 12 字节可选的认证签名,和一个字节属性操作码。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/att_pdu.png) 对于写属性值,`Attribute Parameters` = `Attribute Handle`(2) + `Attribute Value` 。 `Attribute Value` 就是操作属性的值,其最大长度= `ATT_MTU` - `Attribute OPCode`(1)- `Attribute Handle`(2) = `ATT_MTU`-3 。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/att_wirte_format.png) 同样地,对于读操作属性,主要包含在 ReadResponse 操作码。最大长度 = `ATT_MTU` - `Attribute OPCode`(1) = `ATT_MTU`-1 。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/att_read_format.png) 总之,对于 Host 部分读写属性值最大有效数据长度直接由 `ATT_MTU` 决定的,但根本是受 Controller `LL PDU` 大小限制。 >**注意**:这里不太好理解,对于 Controller 的 `LL PDU` 是限制单个蓝牙数据包根本原因,但 Host 的 L2CAP 可以对数据缓存后重新拆分,发送和接收,组装以达到更大的数据吞吐量。 ## 更改 simpler_peripheral 配置更大 MTU ## ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/GATTserverandclient.png) 对于 simpler_peripheral 工程我们的开发板作为 GATT Server 决定 `ATT_MTU` 的最大值。 simpler_peripheral 默认的 `ATT_MTU` 为 23 ,即实际写属性值最大长度为 20 。GATT 服务端 `ATT_MTU` 由以上宏定义,GATT 客户端可以通过`GATT_ExchangeMTU()` 命令配置。 GATT 服务端根据当前的配置的 ATT_MTU 决定是否生效。例如我们这里的 `simple_peripheral` 作为 GATT 服务端,以更改宏 `L2CAP_MTU_SIZE` 为 150 ,` GATT_ExchangeMTU()` 需要配置到 200,返回 150 表示生效。 >**注意**: 对于 `GATT_ExchangeMTU()` 操作,只能由 GATT Client 发起,并且 GATT 服务端返回实际支持最大 ATT_MTU 。 参考《运行第一个例程》,直接更改最大 ATT_MTU 到 251 ,通过《 BTool》配置客户端 ATT_MTU 到 251 ,并且更改 simple_peripheral characteristic 5 数组为 251 ,属性为可读可写。 ### 更改工程 ### * 更改配置宏 ```C //source\ti\ble5stack\icall\inc\ble_user_config.h MAX_PDU_SIZE Line 214 // Maximum size in bytes of the BLE HCI PDU. Valid range: 27 to 255 // The maximum ATT_MTU is MAX_PDU_SIZE - 4. #ifndef MAX_PDU_SIZE #if defined(BLE_V42_FEATURES) && (BLE_V42_FEATURES & SECURE_CONNS_CFG) #define MAX_PDU_SIZE 255 #else #define MAX_PDU_SIZE 27 #endif //(BLE_V42_FEATURES & SECURE_CONNS_CFG) #endif ``` 配置`L2CAP MAX_PDU_SIZE` 为255, `ATT_MTU` 最大 为251 读写特征值最大长度应该`ATT_MTU`-3=248; ```C //source\ti\blestack\profiles\simple_profile\simple_gatt_profile.h SIMPLEPROFILE_CHAR5_LEN Line 85 #define SIMPLEPROFILE_CHAR5_LEN 248 ``` 由于增加了特征值 5 的大小为 248,相应参考特征的栈空间也得增加,它对应着任务栈,否则程序可能会跑飞。 ``` //examples\rtos\CC2640R2_LAUNCHXL\blestack\simple_peripheral\src\app\simple_peripheral.c SBP_TASK_STACK_SIZE Line 180 #ifndef SBP_TASK_STACK_SIZE #define SBP_TASK_STACK_SIZE 1024 #endif ``` >**注意**:这里之所以要加大任务栈否则任务要跑飞,是因为部分任务直接分配了栈空间进行特征值拷贝。 同时为了更好测试,直接将特征 5 的属性更改为直接读写,不需要配对连接。 ```C //source\ti\blestack\profiles\simple_profile\cc26xx\simple_gatt_profile.c Characteristic Value 5 Line 323 // Characteristic Value 5 { { ATT_BT_UUID_SIZE, simpleProfilechar5UUID }, GATT_PERMIT_READ|GATT_PERMIT_WRITE, 0, simpleProfileChar5 }, ``` 但是 simple_peripheral 默认没有进行 Characteristic Value 5 写操作,所以需要更改。 ```C //source\ti\blestack\profiles\simple_profile\cc26xx\simple_gatt_profile.c simpleProfile_WriteAttrCB Line660 case SIMPLEPROFILE_CHAR5_UUID: if(len+offset>SIMPLEPROFILE_CHAR5_LEN) { status = ATT_ERR_INVALID_VALUE_SIZE; } //Write the value if ( status == SUCCESS ) { uint8 *pCurVal = (uint8 *)pAttr->pValue; for(uint8 i=0;ipValue == simpleProfileChar5) { notifyApp = SIMPLEPROFILE_CHAR5; } } break; ``` 同时要在应用程序回调处理 Characteristic Value 5 的写入操作,方便打印到串口调试。 ```C //examples\rtos\CC2640R2_LAUNCHXL\blestack\simple_peripheral\src\app\simple_peripheral.c SimpleBLEPeripheral_processCharValueChangeEvt Line 1183 case SIMPLEPROFILE_CHAR5: SimpleProfile_GetParameter(SIMPLEPROFILE_CHAR5, newValue); sprintf(sprintChar,"%s","Char 5: "); //!< 大概每 60 个字符要换一行 line=7; index=8; for(uint8_t i=0;i0) { Display_print1(dispHandle,line,0,"%s",sprintChar); } break; ``` ### 现象 ### * simple_periphearal 打印进入广播状态 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/print.png) * BTool 扫描连接 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/btools_scan_connect.png) * 配对 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/paring.png) * 交换MTU,设置到 GATT 服务端编译支持的最大 251 。 >**注意**:此过程一定要发生在读取特征值之前,否则会超时。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/mtu_exchang_req.png) ```C -------------------------------------------------------------------- [26] : - 05:28:39.401 -Type : 0x04 (Event) -EventCode : 0x00FF (HCI_LE_ExtEvent) -Data Length : 0x06 (6) bytes(s) Event : 0x067F (1663) (GAP_HCI_ExtentionCommandStatus) Status : 0x00 (0) (Success) OpCode : 0xFD02 (ATT_ExchangeMTUReq) DataLength : 0x00 (0) Dump(Rx): 0000:04 FF 06 7F 06 00 02 FD 00 ......... -------------------------------------------------------------------- [27] : - 05:28:39.511 -Type : 0x04 (Event) -EventCode : 0x00FF (HCI_LE_ExtEvent) -Data Length : 0x08 (8) bytes(s) Event : 0x0503 (1283) (ATT_ExchangeMTURsp) Status : 0x00 (0) (Success) ConnHandle : 0x0000 (0) PduLen : 0x02 (2) ServerRxMTU : 0x00FB (251) Dump(Rx): 0000:04 FF 08 03 05 00 00 00 02 FB 00 ........... -------------------------------------------------------------------- [28] : - 05:28:39.521 -Type : 0x04 (Event) -EventCode : 0x00FF (HCI_LE_ExtEvent) -Data Length : 0x08 (8) bytes(s) Event : 0x057F (1407) (ATT_MtuUpdatedEvt) Status : 0x00 (0) (Success) ConnHandle : 0x0000 (0) PduLen : 0x02 (2) MTU : 0x00FB (251) Dump(Rx): 0000:04 FF 08 7F 05 00 00 00 02 FB 00 ........... -------------------------------------------------------------------- ``` ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/exchange.png) * 读取特征值 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/read_success.png) * 成功写入,将读取 Attribute Value 前面和后面都改成 `FFFFFF03` 和 `C6FFFF` 。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/write_value.png) ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/write_success.png) ### 总结 ### 尽管通过 GATT 服务端配置更大 MTU ,再通过 GATT Client 交换,成功读取 200 字节的特征值。但在抓包分析中不难发现在 Controller 的链路层还是被拆分为最大长度 27 字节发送。 通过前面分析已经不难理解:BLE4.2/5.0 为了兼容 BLE4.0/4.1设备,所以默认 TX PDU 设计成了 27 字节。尽管在 Host 配置更了更大的 ATT_MTU ,其实只是 L2CAP 帮我们进行了拆分和重组。 为了获取到更大数据吞吐量,我们继续研究 BLE4.2/5.0 新增功能 LE Data Extence。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/larger_pakcet_sniffer.png) ### Troubleshooting ### * 写入数据失败,提示找不到 Attribute Handle。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/write_error.png) 需要添加特征值 5 的写回调处理,否则会直接提示找不到句柄。 * 读取特征值超时 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app_examples/exchange_mtu/images/read_timeout.png) 因为更改的 GATT 服务端也就是 simple_peripheral 工程的默认 MTU 大小,正常读写特征值前需要进行 `ATT_Exchange_MTU_Req` 请求,否则会直接失败。 ## LE Data Extension ## LE Data Length Extension 文章详细讲解过,BLE4.2/5.0 已经硬件支持 257 的 LL Data Payload Size ,为了兼容 BLE4.1/4.0 而默认保留该功能。接下来详细讲解如何使能该功能,实现真正意义的最大 `ATT_MTU` 发送,同时体验 BLE4.2/5.0 全新速率。 BLE4.2/5.0 设备默认支持 TX_PDU 大小,为了兼容 BLE4.1/4.0 设备,默认设置为 27 字节/328us;同时又为了兼容 BLE4.2/5.0RX PDU 默认设置为 251字节/2152us。默认使能 251/2120us 只需要主从收发的任意一方设置自己的 TX_PDU 为 251 字节/2120us ,那么收发双发就会直接发起协商功能。 所以直接设置 Peripheral/Salve TX_PDU 为 251 字节/2120us。在 simple_peripheral 工程 `SimpleBLEPeripheral_init` 加入以下代码: ```C #define APP_SUGGESTED_PDU_SIZE 251 #define APP_SUGGESTED_TX_TIME 2120 //This API is documented in hci.h HCI_LE_WriteSuggestedDefaultDataLenCmd(APP_SUGGESTED_PDU_SIZE ,APP_SUGGESTED_TX_TIME); ``` ### 抓包分析 ### * M->S LL_LENGTH_REQ (Rx/Tx)(251、2120) (27、318) ``` +----------------------------------------------------+----------------- - - - | Packet sniffer frame header | +----+-------------+-------------------------+-------+ |info| Packet nbr. | Time stamp | Length| Packet data +----+-------------+-------------------------+-------+----------------- - - - | 01 | E1 00 00 00 | 29 0C 05 0E 02 00 00 00 | 15 00 | 14 BD AA 6D C4 0F 09 **14** ** FB 00 48 08 1B 00 48 01 ** 75 9A 9B 2A 9A +----+-------------+-------------------------+-------+----------------- - - - ``` * S->M LL_LENGTH_REQ (Rx/Tx)(251、2120) (27、318) ``` +----------------------------------------------------+----------------- - - - | Packet sniffer frame header | +----+-------------+-------------------------+-------+ |info| Packet nbr. | Time stamp | Length| Packet data +----+-------------+-------------------------+-------+----------------- - - - | 01 | E2 00 00 00 | C2 0A 07 0E 02 00 00 00 | 15 00 | 14 BD AA 6D C4 0B 09 **14** ** FB 00 48 08 1B 00 48 01** B3 1B C9 2E 9A +----+-------------+-------------------------+-------+----------------- - - - ``` * S->M ``` +----------------------------------------------------+----------------- - - - | Packet sniffer frame header | +----+-------------+-------------------------+-------+ |info| Packet nbr. | Time stamp | Length| Packet data +----+-------------+-------------------------+-------+----------------- - - - | 01 | E3 00 00 00 | 3C 0B 85 10 02 00 00 00 | 15 00 | 14 BD AA 6D C4 03 09 **15** **FB 00 48 08 1B 00 48 01** C7 83 7F 26 82 +----+-------------+-------------------------+-------+----------------- - - - ``` * M->S LL_LENGTH_REQ (Rx/Tx)(251、2120) (27、318) ``` +----------------------------------------------------+----------------- - - - | Packet sniffer frame header | +----+-------------+-------------------------+-------+ |info| Packet nbr. | Time stamp | Length| Packet data +----+-------------+-------------------------+-------+----------------- - - - | 01 | E4 00 00 00 | D7 09 87 10 02 00 00 00 | 15 00 | 14 BD AA 6D C4 07 09 **15** **FB 00 48 08 1B 00 48 01** 01 02 2D 34 82 +----+-------------+-------------------------+-------+----------------- - - - ``` ## 加入我们 ## 文章所有代码、工具、文档开源。加入我们[**QQ群 591679055**](http://shang.qq.com/wpa/qunwpaidkey=d94f12d37c3b37892af4b757c6dc34bea140f3f3128a8d68e556a3d728148e85)获取更多支持,共同研究CC2640R2F&BLE5.0。

CC2640R2F&BLE5.0-乐控畅联© Copyright 2017, 成都乐控畅联科技有限公司.