ESP32C3 BLE5.0 吞吐速率的分析与测试
前言: 本篇文章主要探讨下影响 BLE 传输速率的因素,分析和计算 BLE 1M PHY、BLE 2M PHY 的最大传输速率以及使用 ESP32-C3 验证两种 PHY 的传输速率
1、影响 BLE 传输速率的因素
当前蓝牙核心规范的版本是v5.3, 从 BLE5.0 版本时, BLE已经可以支持多种物理层:LE 1M UNCODED PHY、LE 2M UNCODED PHY 以及 LE CODED PHY, 其中1M UNCODED PHY 也是 BLE4.2 版本所使用的物理层,比特传输速率为 1M bit/s, LE 2M UNCODED PHY 的比特传输速率为 2M bit/s, 而 LE CODED PHY 着重于长距离通讯,比特传输速率反而更低,分别是 125 K bit/s 和 500 K bit/s.
如果我使用 LE 2M UNCODED PHY 的物理层,那传输速率是否可以达到2M bit/s? 当然不可能,影响因素也并不复杂,主要有以下几点
-
从 ATT 层往下的层层打包
一般来说,我们并不主动去操控蓝牙链路层(LL 层)直接发送数据,而是从 ATT 层 、L2CAP 层、LL 层层层封装打包,对端拿到数据是再层层解包, 也是说通过 BLE 传输的数据并不是都是有效数据,还有 “协议数据”
上图可以看到各个协议层的 HEADER , LE 1M UNCODED PHY时,Preamble是 1个字节, LE 2M UNCODED PHY时,Preamble是 2个字节
在BLE 4.0 和 4.1 中,最大 ATT Payload 为 20 字节,在BLE 4.2 和 5.0 中,一种称为数据长度扩展 (DLE) 的新功能允许 ATT 有效负载最多容纳 244 字节的数据,一般 DLE 是可以配置的,从单包数据利用率来看,DLE 调至最大更好,很多蓝牙芯片默认设置就是 DLE 最大
-
帧间空间 (T_IFS)
同一数据信道上, 两个空中包的之间的距离是 150 us, 从上一包的包尾到下一包的包头, 这个是一个固定的时间消耗
-
连接事件和连接间隔
在两个 BLE 设备建立连接后,需要在一个个连接事件中进行相互收发包, 每个连接事件中双方要完成一次收发包,如果没有数据发, 也需要发送一个空包给对端。一个连接事件中双方可以有多个数据包的交互,取决于数据包的长度及连接间隔大小; 连接间隔是两个相邻连接事件起始之间的时间距离,可通过应用层配置
综上来看,影响 BLE 传输速率的因素主要是应用层发包方式和连接间隔的调整,举个例子,应用层使用 write command 的方式发包要比使用 write request 的方式快很多,因为后者对端还要回复 write response 包; 连接间隔的调整指的在一个连接事件中尽可能提升数据包的填充率,例如一个连接事件中能发送 14 个 负载为 150 的数据,如果调整下 DLE负载数据为 145, 连接事件可以发送 15 个包,这样就整体提升了发送数据的速率, 或者微小的提升连接间隔,一个连接事件中能发送 15 个 负载为 150 的数据
这里有个问题是连接间隔是不是越大越好?或者越小越好?这个和设备厂商或者协议栈有关系,一个连接事件的最大包的个数有限制, 你调整到连接间隔为 1s, 一般就超过这个限制了, 那速率也会很慢,实际最好是连接间隔越小并且能使当前连接事件中包含更多的数据包
2、BLE 传输速率的计算和分析
以乐鑫的吞吐量示例为例, 单向 client 发送 write command 数据,数据长度 490, 使用 LE 1M UNCODED PHY, 默认的连接间隔是 40ms (可以通过抓包或者建立连接后调用 esp_ble_get_current_conn_params
获得),ATT 层 3 个字节 header, L2CAP 层 4 个 字节 header, DLE 默认是最大的,所以链路层会分两包进行发送,平均每个 LL 层数据包长度及占用时间如下:
LL层数据包长度 = Preamble + Access Address + LL Header + Data + CRC = 1+4+2+(497/2)+3=259 byte
LL层数据包占用时间 = (259*8) bits / 1 Mbps = 2072 us
slave 设备收到包后要回复一个空包,空包长度及占用时间如下:
空包长度 = Preamble + Access Address + LL Header + CRC = 1+4+2+3=10 byte
空包占用时间 = = 80 bits / 1 Mbps = 80 us
一次收发包过程所占用的时间:
一次收发包过程占用时间 = LL层数据包占用时间 + IFS + 空包占用时间 + IFS = 80 + 2*150 + 2072 = 2452 us
40ms 的连接间隔可以有 (40 * 1000)/2452 = 16 个收发包过程, 应用层传输速率
应用层传输速率 = (1000/40) * (16/2 *490)*8 = 784000 bit/s
使用两块 ESP32-C3 对跑测试,实际速率是
I (112501) GATTS_DEMO: GATTC write Bit rate = 91805 Byte/s, = 734440 bit/s, time = 101s
I (114501) GATTS_DEMO: GATTC write Bit rate = 91807 Byte/s, = 734456 bit/s, time = 103s
I (116501) GATTS_DEMO: GATTC write Bit rate = 91808 Byte/s, = 734464 bit/s, time = 105s
I (118501) GATTS_DEMO: GATTC write Bit rate = 91777 Byte/s, = 734216 bit/s, time = 107s
I (120501) GATTS_DEMO: GATTC write Bit rate = 91775 Byte/s, = 734200 bit/s, time = 109s
I (122501) GATTS_DEMO: GATTC write Bit rate = 91776 Byte/s, = 734208 bit/s, time = 111s
I (124501) GATTS_DEMO: GATTC write Bit rate = 91778 Byte/s, = 734224 bit/s, time = 113s
I (126501) GATTS_DEMO: GATTC write Bit rate = 91780 Byte/s, = 734240 bit/s, time = 115s
I (128501) GATTS_DEMO: GATTC write Bit rate = 91781 Byte/s, = 734248 bit/s, time = 117s
I (130501) GATTS_DEMO: GATTC write Bit rate = 91783 Byte/s, = 734264 bit/s, time = 119s
I (132501) GATTS_DEMO: GATTC write Bit rate = 91756 Byte/s, = 734048 bit/s, time = 121s
并没有达到理想的计算速率,抓包看到每个连接间隔是 15 个收发包过程,如果按这个进行计算与实际速率相符
应用层传输速率 = (1000/40) * (15/2 *490)*8 = 735000 bit/s
那如果是使用 LE 2M UNCODED PHY 呢, 我们假定使用连接间隔为 47.5ms 计算(其他值可以自己调试和计算)
LL层数据包长度 = Preamble + Access Address + LL Header + Data + CRC = 2+4+2+(497/2)+3=260 byte
LL层数据包占用时间 = (260*8) bits / 2 Mbps = 1040 us
空包长度 = Preamble + Access Address + LL Header + CRC = 2+4+2+3=11 byte
空包占用时间 = = 88 bits / 2 Mbps = 44 us
一次收发包过程占用时间 = LL层数据包占用时间 + IFS + 空包占用时间 + IFS = 44 + 2*150 + 1040 = 1384 us
连接间隔包含收发包过程的个数 = (47.5 * 1000)/1384 = 34
应用层传输速率 = (1000/47.5) * (34/2 *490)*8 = 1402947 bit/s = 1.40 Mbit/s
实际使用两块 ESP32-C3 使用 2M PHY 对跑测试
I (186511) GATTS_DEMO: GATTC write Bit rate = 169031 Byte/s, = 1352248 bit/s, time = 184s
I (188511) GATTS_DEMO: GATTC write Bit rate = 169017 Byte/s, = 1352136 bit/s, time = 186s
I (190511) GATTS_DEMO: GATTC write Bit rate = 169028 Byte/s, = 1352224 bit/s, time = 188s
I (192511) GATTS_DEMO: GATTC write Bit rate = 169041 Byte/s, = 1352328 bit/s, time = 190s
I (194511) GATTS_DEMO: GATTC write Bit rate = 169051 Byte/s, = 1352408 bit/s, time = 192s
I (196511) GATTS_DEMO: GATTC write Bit rate = 169059 Byte/s, = 1352472 bit/s, time = 194s
I (198511) GATTS_DEMO: GATTC write Bit rate = 169069 Byte/s, = 1352552 bit/s, time = 196s
I (200511) GATTS_DEMO: GATTC write Bit rate = 169074 Byte/s, = 1352592 bit/s, time = 198s
I (202511) GATTS_DEMO: GATTC write Bit rate = 169013 Byte/s, = 1352104 bit/s, time = 200s
I (204511) GATTS_DEMO: GATTC write Bit rate = 168962 Byte/s, = 1351696 bit/s, time = 202s
I (206511) GATTS_DEMO: GATTC write Bit rate = 168956 Byte/s, = 1351648 bit/s, time = 204s
能达到 1.35 Mbit/s 左右,基本上也是接近计算数据,实际可能是连接事件中并没有包含 34 个收发包过程,不好确定,因为当前的 sniffer 设备很少可以抓 LE 2M UNCODED PHY 数据包的,具备此功能的也价格比较昂贵。
3、修改 ESP32C3 吞吐量测试示例
最简单方法是将 吞吐量示例 与 ble50_security_client 和 ble50_security_server 两个示例进行结合, 修改扫描、广播和发起连接的 API 为 扩展 API
-
throughput_server
修改广播参数与广播数据
static uint8_t ext_adv_raw_data[] = { 0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x03, 0x03, 0xab, 0xcd, 0x11, 0X09, 'B', 'L', 'E', '5', '0', '_', 'T', 'H', 'R', 'O', 'U', 'G', 'H','P', 'U', 'T', }; static esp_ble_gap_ext_adv_t ext_adv[1] = { [0] = {EXT_ADV_HANDLE, EXT_ADV_DURATION, EXT_ADV_MAX_EVENTS}, }; esp_ble_gap_ext_adv_params_t ext_adv_params_2M = { .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE, .interval_min = 0x20, .interval_max = 0x20, .channel_map = ADV_CHNL_ALL, .filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, .primary_phy = ESP_BLE_GAP_PHY_1M, .max_skip = 0, .secondary_phy = ESP_BLE_GAP_PHY_2M, .sid = 0, .scan_req_notif = false, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, .tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE, };
修改设置广播参数、数据及开启广播的流程
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { switch (event) { case ESP_GATTS_REG_EVT: //... esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params_2M); //... } static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT: ESP_LOGI(GATTS_TAG,"ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT status %d", param->ext_adv_set_params.status); esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE, sizeof(ext_adv_raw_data), &ext_adv_raw_data[0]); break; case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT: ESP_LOGI(GATTS_TAG,"ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT status %d", param->ext_adv_data_set.status); esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]); break; //... }
连接后更新连接参数
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { switch (event) { case ESP_GATTS_CONNECT_EVT: //... is_connect = true; esp_ble_conn_update_params_t conn_params = {0}; memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); conn_params.latency = 0; conn_params.max_int = 38; // max_int = 38*1.25ms = 47.5ms conn_params.min_int = 38; // min_int = 38*1.25ms = 47.5ms conn_params.timeout = 400; // timeout = 400*10ms = 4000ms gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id; //start sent the update connection parameters to the peer device. esp_ble_gap_update_conn_params(&conn_params); break; //... }
-
throughput_client
设置扫描参数和要寻找的对端设备名称
static const char remote_device_name[] = "BLE50_THROUGHPUT"; static esp_ble_ext_scan_params_t ext_scan_params = { .own_addr_type = BLE_ADDR_TYPE_PUBLIC, .filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE, .cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK | ESP_BLE_GAP_EXT_SCAN_CFG_CODE_MASK, .uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40}, .coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40}, }; const esp_ble_gap_conn_params_t phy_1m_conn_params = { .scan_interval = 0x40, .scan_window = 0x40, .interval_min = 0x10, .interval_max = 0x20, .latency = 0, .supervision_timeout = 600, .min_ce_len = 0, .max_ce_len = 0, }; const esp_ble_gap_conn_params_t phy_2m_conn_params = { .scan_interval = 0x40, .scan_window = 0x40, .interval_min = 0x10, .interval_max = 0x20, .latency = 0, .supervision_timeout = 600, .min_ce_len = 0, .max_ce_len = 0, }; const esp_ble_gap_conn_params_t phy_coded_conn_params = { .scan_interval = 0x40, .scan_window = 0x40, .interval_min = 0x10, .interval_max = 0x20, .latency = 0, .supervision_timeout = 600, .min_ce_len = 0, .max_ce_len = 0, };
设置扫描参数以及扫描结果处理并发起连接
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; switch (event) { case ESP_GATTC_REG_EVT: ESP_LOGI(GATTC_TAG, "REG_EVT"); esp_err_t scan_ret = esp_ble_gap_set_ext_scan_params(&ext_scan_params); if (scan_ret){ ESP_LOGE(GATTC_TAG, "set extend scan params error, error code = %x", scan_ret); } break; //... } static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { uint8_t *adv_name = NULL; uint8_t adv_name_len = 0; switch (event) { case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT: { if (param->set_ext_scan_params.status != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(GATTC_TAG, "extend scan parameters set failed, error status = %x", param->set_ext_scan_params.status); break; } esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD); break; } case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: //scan start complete event to indicate scan start successfully or failed if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(GATTC_TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status); break; } ESP_LOGI(GATTC_TAG, "scan start success"); break; case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: { uint8_t *adv_name = NULL; uint8_t adv_name_len = 0; if(param->ext_adv_report.params.event_type & ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY) { ESP_LOGI(GATTC_TAG, "legacy adv, adv type 0x%x data len %d", param->ext_adv_report.params.event_type, param->ext_adv_report.params.adv_data_len); } else { ESP_LOGI(GATTC_TAG, "extend adv, adv type 0x%x data len %d", param->ext_adv_report.params.event_type, param->ext_adv_report.params.adv_data_len); } adv_name = esp_ble_resolve_adv_data(param->ext_adv_report.params.adv_data, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); if (!connect && strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) { connect = true; esp_ble_gap_stop_ext_scan(); esp_log_buffer_hex("adv addr", param->ext_adv_report.params.addr, 6); esp_log_buffer_char("adv name", adv_name, adv_name_len); ESP_LOGI(GATTC_TAG, "Stop extend scan and create aux open, primary_phy %d secondary phy %d\n", param->ext_adv_report.params.primary_phy, param->ext_adv_report.params.secondly_phy); esp_ble_gap_prefer_ext_connect_params_set(param->ext_adv_report.params.addr, ESP_BLE_GAP_PHY_1M_PREF_MASK | ESP_BLE_GAP_PHY_2M_PREF_MASK | ESP_BLE_GAP_PHY_CODED_PREF_MASK , &phy_1m_conn_params, &phy_2m_conn_params, &phy_coded_conn_params); esp_ble_gattc_aux_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, param->ext_adv_report.params.addr, param->ext_adv_report.params.addr_type, true); } break; } //... }
esp_ble_gap_prefer_ext_connect_params_set
也可以不用去配置
两个demo 示例修改后在编译前要在 menuconfig 中打开 CONFIG_BT_BLE_50_FEATURES_SUPPORTED
宏,不然编译不通过, ESP32S3 配置流程与 ESP32C3 相同
以上使用的 write command 方式或者 notify 方式,类似于 网络协议中的 UDP, 并不能保证数据可达,实际中一些重要的不能丢包的场合如 OTA 过程可以使用 write request 方式或者 indicate 方式发送数据,但传输速率可能达不到以上数据传输速率的一半,所以以上测试速率可以作为一个极限速率参考
参考:文章来源:https://www.toymoban.com/news/detail-664394.html
https://novelbits.io/bluetooth-5-speed-maximum-throughput/文章来源地址https://www.toymoban.com/news/detail-664394.html
到了这里,关于ESP32C3 BLE5.0 吞吐速率的分析与测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!