mirror of
https://github.com/espressif/esp-idf.git
synced 2026-07-14 23:13:00 +03:00
fix(nimble): Optimize Nimble throughput example for 1M/2M/S2/S8
This commit is contained in:
@@ -49,13 +49,13 @@
|
||||
#define WRITE_THROUGHPUT 2
|
||||
#define NOTIFY_THROUGHPUT 3
|
||||
|
||||
#define READ_THROUGHPUT_PAYLOAD 510 /* MTU(512) - ATT read rsp header(1) - 1 (avoid Read Blob) */
|
||||
#define WRITE_THROUGHPUT_PAYLOAD 509 /* MTU(512) - ATT write cmd header(3) */
|
||||
#define READ_THROUGHPUT_PAYLOAD 497 /* 502 bytes ACL -> 2x 251 LL packets exactly (497 + 1 Read Rsp + 4 L2CAP) */
|
||||
#define WRITE_THROUGHPUT_PAYLOAD 495 /* 502 bytes ACL -> 2x 251 LL packets exactly (495 + 3 Write Cmd + 4 L2CAP) */
|
||||
#define LL_PACKET_TIME 2120
|
||||
#define LL_PACKET_LENGTH 251
|
||||
static const char *tag = "blecent_throughput";
|
||||
static int blecent_gap_event(struct ble_gap_event *event, void *arg);
|
||||
static SemaphoreHandle_t xSemaphore;
|
||||
|
||||
static int mbuf_len_total;
|
||||
static int failure_count;
|
||||
static TaskHandle_t throughput_task_handle = NULL;
|
||||
@@ -66,28 +66,102 @@ static int mtu_def = 512;
|
||||
static ble_addr_t conn_addr;
|
||||
static uint16_t handle;
|
||||
#define PHY_1M 0
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
static int current_phy_updated;
|
||||
#define PHY_2M 1
|
||||
#define PHY_CODED_S2 2
|
||||
#define PHY_CODED_S8 3
|
||||
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
static int current_phy_updated;
|
||||
#endif
|
||||
|
||||
/* State for callback-chained read throughput test */
|
||||
static volatile bool read_test_active = false;
|
||||
static uint16_t read_val_handle_g;
|
||||
static uint16_t read_conn_handle_g;
|
||||
static int read_count_g;
|
||||
|
||||
/* ==============================================================================
|
||||
* Connection Parameter Tuning
|
||||
* ==============================================================================
|
||||
* itvl_min / itvl_max: Connection interval (time between connection events).
|
||||
* Units are in 1.25ms. (e.g., 24 * 1.25ms = 30ms).
|
||||
* latency: Slave latency (number of events the peripheral can skip).
|
||||
* supervision_timeout: Time before link is considered lost. Units: 10ms.
|
||||
* min_ce_len / max_ce_len: Connection Event length. max_ce_len = 0xFFFF tells
|
||||
* the controller to use the ENTIRE connection interval
|
||||
* for transmitting data, instead of cutting it short.
|
||||
* ==============================================================================
|
||||
*
|
||||
* Write/Notify-optimized for 1M/2M: 30-50ms interval, full CE length. */
|
||||
static struct ble_gap_upd_params conn_params = {
|
||||
/** Minimum value for connection interval in 1.25ms units */
|
||||
.itvl_min = CONFIG_EXAMPLE_CONN_ITVL_MIN,
|
||||
/** Maximum value for connection interval in 1.25ms units */
|
||||
.itvl_max = CONFIG_EXAMPLE_CONN_ITVL_MAX,
|
||||
/** Connection latency */
|
||||
.latency = CONFIG_EXAMPLE_CONN_LATENCY,
|
||||
/** Supervision timeout in 10ms units */
|
||||
.supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT,
|
||||
/** Minimum length of connection event in 0.625ms units */
|
||||
.min_ce_len = CONFIG_EXAMPLE_CONN_CE_LEN_MIN,
|
||||
/** Maximum length of connection event in 0.625ms units */
|
||||
.max_ce_len = CONFIG_EXAMPLE_CONN_CE_LEN_MAX,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0xFFFF,
|
||||
};
|
||||
|
||||
/* Write/Notify-optimized for Coded S2 */
|
||||
static struct ble_gap_upd_params conn_params_coded_s2 = {
|
||||
.itvl_min = 24, /* 30 ms */
|
||||
.itvl_max = 32, /* 40 ms */
|
||||
.latency = 0,
|
||||
.supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0xFFFF,
|
||||
};
|
||||
|
||||
/* Write/Notify-optimized for Coded S8 */
|
||||
static struct ble_gap_upd_params conn_params_coded_s8 = {
|
||||
.itvl_min = 60, /* 75 ms */
|
||||
.itvl_max = 80, /* 100 ms */
|
||||
.latency = 0,
|
||||
.supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0xFFFF,
|
||||
};
|
||||
|
||||
/* Read-optimized for 1M/2M: short interval for fast round-trips */
|
||||
static struct ble_gap_upd_params conn_params_read = {
|
||||
.itvl_min = 6, /* 7.5 ms */
|
||||
.itvl_max = 8, /* 10 ms */
|
||||
.latency = 0,
|
||||
.supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0xFFFF,
|
||||
};
|
||||
|
||||
/* Read-optimized for Coded S2 */
|
||||
static struct ble_gap_upd_params conn_params_coded_s2_read = {
|
||||
.itvl_min = 12, /* 15 ms */
|
||||
.itvl_max = 16, /* 20 ms */
|
||||
.latency = 0,
|
||||
.supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0xFFFF,
|
||||
};
|
||||
|
||||
/* Read-optimized for Coded S8 */
|
||||
static struct ble_gap_upd_params conn_params_coded_s8_read = {
|
||||
.itvl_min = 36, /* 45 ms */
|
||||
.itvl_max = 40, /* 50 ms */
|
||||
.latency = 0,
|
||||
.supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0xFFFF,
|
||||
};
|
||||
|
||||
static void switch_conn_params(uint16_t conn_handle, const struct ble_gap_upd_params *params) {
|
||||
int rc = ble_gap_update_params(conn_handle, params);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Failed to update connection parameters: %d", rc);
|
||||
} else {
|
||||
ESP_LOGI(tag, "Requested connection parameter update");
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void ble_store_config_init(void);
|
||||
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
@@ -137,22 +211,20 @@ static int blecent_write(uint16_t conn_handle, uint16_t val_handle,
|
||||
start_time = esp_timer_get_time();
|
||||
|
||||
while (write_time < test_time * 1000) {
|
||||
/* Wait till the previous write is complete. For first time Semaphore
|
||||
* is already available */
|
||||
label:
|
||||
rc = ble_gattc_write_no_rsp_flat(conn_handle, val_handle, &value, sizeof value);
|
||||
rc = ble_gattc_write_no_rsp_flat(conn_handle, val_handle, &value, sizeof value);
|
||||
|
||||
if(rc == BLE_HS_ENOMEM) {
|
||||
vTaskDelay(2); /* Wait for buffers to free up and try again */
|
||||
goto label;
|
||||
}
|
||||
else if (rc != 0) {
|
||||
ESP_LOGE(tag, "Error: Failed to write characteristic; rc=%d",rc);
|
||||
goto err;
|
||||
if (rc == BLE_HS_ENOMEM) {
|
||||
vTaskDelay(2); /* Wait for buffers to free up and try again */
|
||||
end_time = esp_timer_get_time();
|
||||
write_time = (end_time - start_time) / 1000;
|
||||
continue;
|
||||
} else if (rc != 0) {
|
||||
ESP_LOGE(tag, "Error: Failed to write characteristic; rc=%d", rc);
|
||||
goto err;
|
||||
}
|
||||
|
||||
end_time = esp_timer_get_time();
|
||||
write_time = (end_time - start_time) / 1000 ;
|
||||
write_time = (end_time - start_time) / 1000;
|
||||
write_count += 1;
|
||||
}
|
||||
|
||||
@@ -171,6 +243,11 @@ err:
|
||||
return ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read callback that chains the next read immediately from within the
|
||||
* NimBLE host context. This eliminates the FreeRTOS task-switch delay
|
||||
* that previously caused each read to waste an extra connection event.
|
||||
*/
|
||||
static int
|
||||
blecent_repeat_read(uint16_t conn_handle,
|
||||
const struct ble_gatt_error *error,
|
||||
@@ -178,55 +255,96 @@ blecent_repeat_read(uint16_t conn_handle,
|
||||
void *arg)
|
||||
{
|
||||
if (error->status == 0) {
|
||||
xSemaphoreGive(xSemaphore);
|
||||
ESP_LOGD(tag, " attr_handle=%d value=", attr->handle);
|
||||
mbuf_len_total += OS_MBUF_PKTLEN(attr->om);
|
||||
read_count_g++;
|
||||
} else {
|
||||
ESP_LOGE(tag, " Read failed, callback error code = %d", error->status );
|
||||
xSemaphoreGive(xSemaphore);
|
||||
ESP_LOGE(tag, " Read failed, callback error code = %d", error->status);
|
||||
/* On error, stop chaining and wake up main task */
|
||||
read_test_active = false;
|
||||
if (throughput_task_handle) {
|
||||
xTaskNotifyGive(throughput_task_handle);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return error->status;
|
||||
|
||||
/* Chain next read immediately (zero task-switch overhead) */
|
||||
if (read_test_active) {
|
||||
int rc = ble_gattc_read(read_conn_handle_g, read_val_handle_g,
|
||||
blecent_repeat_read, arg);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Failed to chain read; rc=%d", rc);
|
||||
read_test_active = false;
|
||||
if (throughput_task_handle) {
|
||||
xTaskNotifyGive(throughput_task_handle);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Test time expired — wake up main task to print results */
|
||||
if (throughput_task_handle) {
|
||||
xTaskNotifyGive(throughput_task_handle);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int blecent_read(uint16_t conn_handle, uint16_t val_handle,
|
||||
ble_gatt_attr_fn *cb, struct peer *peer, int test_time)
|
||||
struct peer *peer, int test_time)
|
||||
{
|
||||
int rc, read_count = 0;
|
||||
int64_t start_time, end_time, read_time = 0;
|
||||
/* Keep track of number of bytes read from char */
|
||||
int rc;
|
||||
int64_t start_time;
|
||||
|
||||
/* Reset counters */
|
||||
mbuf_len_total = 0;
|
||||
read_count_g = 0;
|
||||
read_conn_handle_g = conn_handle;
|
||||
read_val_handle_g = val_handle;
|
||||
read_test_active = true;
|
||||
|
||||
/* Drain any stale notifications from a prior test that timed out */
|
||||
ulTaskNotifyTake(pdTRUE, 0);
|
||||
|
||||
start_time = esp_timer_get_time();
|
||||
|
||||
ESP_LOGD(tag, " Throughput read started :val_handle=%d test_time=%d", val_handle,
|
||||
test_time);
|
||||
|
||||
while (read_time < (test_time * 1000)) {
|
||||
/* Wait till the previous read is complete. For first time use Semaphore
|
||||
* is already available */
|
||||
xSemaphoreTake(xSemaphore, portMAX_DELAY);
|
||||
|
||||
rc = ble_gattc_read(peer->conn_handle, val_handle,
|
||||
blecent_repeat_read, (void *) &peer);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Error: Failed to read characteristic; rc=%d",
|
||||
rc);
|
||||
goto err;
|
||||
}
|
||||
|
||||
end_time = esp_timer_get_time();
|
||||
read_time = (end_time - start_time) / 1000 ;
|
||||
read_count += 1;
|
||||
/* Kick off the first read — the callback will chain all subsequent reads */
|
||||
rc = ble_gattc_read(conn_handle, val_handle,
|
||||
blecent_repeat_read, (void *) peer);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Error: Failed to start read chain; rc=%d", rc);
|
||||
read_test_active = false;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Application data throughput */
|
||||
/* Sleep for the test duration while the callback chain runs autonomously.
|
||||
* If the chain encounters an error (e.g. disconnect), it will notify us early. */
|
||||
uint32_t notified = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(test_time * 1000));
|
||||
|
||||
if (notified == 0) {
|
||||
/* Normal timeout, signal the chain to stop after the current in-flight read completes */
|
||||
read_test_active = false;
|
||||
/* Wait for the last callback to notify us (up to 5s safety timeout) */
|
||||
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000));
|
||||
} else {
|
||||
/* Woken early by an error in the callback chain */
|
||||
read_test_active = false;
|
||||
}
|
||||
|
||||
int64_t end_time = esp_timer_get_time();
|
||||
int actual_secs = (int)((end_time - start_time) / 1000000);
|
||||
if (actual_secs == 0) actual_secs = test_time;
|
||||
|
||||
/* Application data throughput */
|
||||
printf("\n****************************************************************\n");
|
||||
ESP_LOGI(tag, "Application Read throughput = %d bps, Read op counter = %d",
|
||||
(mbuf_len_total * 8) / (test_time), read_count);
|
||||
(mbuf_len_total * 8) / actual_secs, read_count_g);
|
||||
printf("\n****************************************************************\n");
|
||||
return 0;
|
||||
|
||||
err:
|
||||
xSemaphoreGive(xSemaphore);
|
||||
/* Terminate the connection. */
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
return ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
|
||||
@@ -334,7 +452,7 @@ static void throughput_task(void *arg)
|
||||
printf(" | Phy mode: Enter value in 0 for 1M, 1 for 2M ,2 for Coded S2, |\n");
|
||||
printf(" | 3 for Coded S8. |\n");
|
||||
printf(" | |\n");
|
||||
printf(" | e.g. throughput read 600 3 |\n");
|
||||
printf(" | e.g. throughput read 60 3 |\n");
|
||||
printf(" | |\n");
|
||||
printf(" | ** Enter 'throughput read 60 0' for reading char for 60 seconds on 1M Phy |\n");
|
||||
printf(" | OR 'throughput write 60 1' for writing to char for 60 seconds on 2M Phy |\n");
|
||||
@@ -365,12 +483,46 @@ static void throughput_task(void *arg)
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
/* Send a 1-byte control message to the peripheral's read/write char
|
||||
* to tell it which PHY coding we are testing. This ensures the peripheral
|
||||
* perfectly mirrors our PHY preference (1M, 2M, S2, or S8). */
|
||||
if (test_data[2] >= 0 && test_data[2] <= 3) {
|
||||
const struct peer_chr *cmd_chr = peer_chr_find_uuid(peer,
|
||||
THRPT_UUID_DECLARE(THRPT_SVC),
|
||||
THRPT_UUID_DECLARE(THRPT_CHR_READ_WRITE));
|
||||
if (cmd_chr != NULL) {
|
||||
uint8_t phy_cmd = test_data[2];
|
||||
rc = ble_gattc_write_no_rsp_flat(conn_handle, cmd_chr->chr.val_handle, &phy_cmd, 1);
|
||||
if (rc != 0) {
|
||||
ESP_LOGW(tag, "Failed to send PHY cmd to peripheral; rc=%d", rc);
|
||||
} else {
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS); /* Give peripheral time to apply PHY */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* Extended advertising is disabled, only 1M PHY is available */
|
||||
if (test_data[2] != PHY_1M) {
|
||||
ESP_LOGW(tag, "Extended advertising disabled; forcing PHY to 1M (ignoring user selection %d)", test_data[2]);
|
||||
test_data[2] = PHY_1M;
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (test_data[0]) {
|
||||
|
||||
case READ_THROUGHPUT:
|
||||
/* PHY-aware read connection params: short interval for fast
|
||||
* round-trips on 1M/2M, longer for coded PHY */
|
||||
if (test_data[2] == PHY_CODED_S2) {
|
||||
switch_conn_params(conn_handle, &conn_params_coded_s2_read);
|
||||
} else if (test_data[2] == PHY_CODED_S8) {
|
||||
switch_conn_params(conn_handle, &conn_params_coded_s8_read);
|
||||
} else {
|
||||
switch_conn_params(conn_handle, &conn_params_read);
|
||||
}
|
||||
|
||||
/* Read the characteristic supporting long read support
|
||||
* `THRPT_LONG_CHR_READ_WRITE` (0x000b) */
|
||||
chr = peer_chr_find_uuid(peer,
|
||||
@@ -379,12 +531,12 @@ static void throughput_task(void *arg)
|
||||
if (chr == NULL) {
|
||||
ESP_LOGE(tag, "Peer does not support "
|
||||
"LONG_READ (0x000b) characteristic ");
|
||||
break;
|
||||
goto read_cleanup;
|
||||
}
|
||||
|
||||
if (test_data[1] > 0) {
|
||||
rc = blecent_read(conn_handle, chr->chr.val_handle,
|
||||
blecent_repeat_read, (void *) peer, test_data[1]);
|
||||
(void *) peer, test_data[1]);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Error while reading from GATTS; rc = %d", rc);
|
||||
/* Delete task on critical error (connection lost or fatal error) */
|
||||
@@ -398,9 +550,28 @@ static void throughput_task(void *arg)
|
||||
} else {
|
||||
ESP_LOGE(tag, "Please enter non-zero value for test time in seconds!!");
|
||||
}
|
||||
|
||||
read_cleanup:
|
||||
/* Restore write-optimized params after the read test */
|
||||
ESP_LOGI(tag, "Restoring WRITE-optimized conn params");
|
||||
if (test_data[2] == PHY_CODED_S2) {
|
||||
switch_conn_params(conn_handle, &conn_params_coded_s2);
|
||||
} else if (test_data[2] == PHY_CODED_S8) {
|
||||
switch_conn_params(conn_handle, &conn_params_coded_s8);
|
||||
} else {
|
||||
switch_conn_params(conn_handle, &conn_params);
|
||||
}
|
||||
break;
|
||||
|
||||
case WRITE_THROUGHPUT:
|
||||
if (test_data[2] == PHY_CODED_S2) {
|
||||
switch_conn_params(conn_handle, &conn_params_coded_s2);
|
||||
} else if (test_data[2] == PHY_CODED_S8) {
|
||||
switch_conn_params(conn_handle, &conn_params_coded_s8);
|
||||
} else {
|
||||
switch_conn_params(conn_handle, &conn_params);
|
||||
}
|
||||
|
||||
chr = peer_chr_find_uuid(peer,
|
||||
THRPT_UUID_DECLARE(THRPT_SVC),
|
||||
THRPT_UUID_DECLARE(THRPT_CHR_READ_WRITE));
|
||||
@@ -428,6 +599,14 @@ static void throughput_task(void *arg)
|
||||
break;
|
||||
|
||||
case NOTIFY_THROUGHPUT:
|
||||
if (test_data[2] == PHY_CODED_S2) {
|
||||
switch_conn_params(conn_handle, &conn_params_coded_s2);
|
||||
} else if (test_data[2] == PHY_CODED_S8) {
|
||||
switch_conn_params(conn_handle, &conn_params_coded_s8);
|
||||
} else {
|
||||
switch_conn_params(conn_handle, &conn_params);
|
||||
}
|
||||
|
||||
chr = peer_chr_find_uuid(peer,
|
||||
THRPT_UUID_DECLARE(THRPT_SVC),
|
||||
THRPT_UUID_DECLARE(THRPT_CHR_NOTIFY));
|
||||
@@ -463,6 +642,20 @@ static void throughput_task(void *arg)
|
||||
test_data[1]);
|
||||
}
|
||||
vTaskDelay(test_data[1]*1000 / portTICK_PERIOD_MS);
|
||||
|
||||
/* Unsubscribe so the next notify test triggers a fresh
|
||||
* BLE_GAP_EVENT_SUBSCRIBE on the peripheral (cur_notify 0→1) */
|
||||
{
|
||||
uint8_t unsub_value[2] = {0, 0};
|
||||
rc = ble_gattc_write_flat(conn_handle, dsc->dsc.handle,
|
||||
unsub_value, sizeof unsub_value,
|
||||
NULL, NULL);
|
||||
if (rc != 0) {
|
||||
ESP_LOGW(tag, "Unsubscribe failed; rc=%d (non-fatal)", rc);
|
||||
} else {
|
||||
ESP_LOGI(tag, "Unsubscribed from notifications");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -656,7 +849,7 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc)
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(tag, "connect; fields.num_uuids128 =%d", fields.num_uuids128);
|
||||
ESP_LOGD(tag, "connect; fields.num_uuids128 =%d", fields.num_uuids128);
|
||||
for (i = 0; i < fields.num_uuids128; i++) {
|
||||
if ((memcmp(&fields.uuids128[i], THRPT_UUID_DECLARE(THRPT_SVC),
|
||||
sizeof(ble_uuid128_t))) == 0 ) {
|
||||
@@ -759,7 +952,7 @@ blecent_gap_event(struct ble_gap_event *event, void *arg)
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_DISC:
|
||||
ESP_LOGI(tag, "Event DISC ");
|
||||
ESP_LOGD(tag, "Event DISC ");
|
||||
rc = ble_hs_adv_parse_fields(&fields, event->disc.data,
|
||||
event->disc.length_data);
|
||||
if (rc != 0) {
|
||||
@@ -880,6 +1073,11 @@ blecent_gap_event(struct ble_gap_event *event, void *arg)
|
||||
/* Attribute data is contained in event->notify_rx.attr_data. */
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_CONN_UPDATE:
|
||||
ESP_LOGI(tag, "connection updated; status=%d",
|
||||
event->conn_update.status);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_MTU:
|
||||
ESP_LOGI(tag, "mtu update event; conn_handle = %d cid = %d mtu = %d",
|
||||
event->mtu.conn_handle,
|
||||
@@ -890,7 +1088,6 @@ blecent_gap_event(struct ble_gap_event *event, void *arg)
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
case BLE_GAP_EVENT_EXT_DISC:
|
||||
/* An advertisement report was received during GAP discovery. */
|
||||
|
||||
blecent_connect_if_interesting(&event->ext_disc);
|
||||
return 0;
|
||||
|
||||
@@ -977,12 +1174,9 @@ blecent_on_sync(void)
|
||||
void blecent_host_task(void *param)
|
||||
{
|
||||
ESP_LOGI(tag, "BLE Host Task Started");
|
||||
xSemaphore = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(xSemaphore);
|
||||
|
||||
/* This function will return only when nimble_port_stop() is executed */
|
||||
nimble_port_run();
|
||||
vSemaphoreDelete(xSemaphore);
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,35 @@
|
||||
# in this example and some misc buffer sizes are increased
|
||||
|
||||
#
|
||||
# BT config
|
||||
# BT config (universal across all ESP32 variants)
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=n
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
|
||||
# === BT 5.0 / 2M PHY support ===
|
||||
# Ignored automatically on classic ESP32 which lacks BT 5.0
|
||||
CONFIG_BT_NIMBLE_EXT_ADV=n
|
||||
CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y
|
||||
CONFIG_EXAMPLE_EXTENDED_ADV=n
|
||||
|
||||
# === ATT / GATT ===
|
||||
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=20
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255
|
||||
|
||||
# === Memory Pool Tuning ===
|
||||
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
|
||||
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL=4
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y
|
||||
CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=592
|
||||
CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=40
|
||||
|
||||
# Increase controller-to-host buffers
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=40
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255
|
||||
|
||||
# === Connection Parameters (write/notify optimal; code switches dynamically for reads) ===
|
||||
CONFIG_EXAMPLE_CONN_ITVL_MIN=24
|
||||
CONFIG_EXAMPLE_CONN_ITVL_MAX=40
|
||||
|
||||
# Disable NimBLE INFO logging for throughput tests
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL=2
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# === High-Performance Tuning ===
|
||||
# This config is only applied when building for the ESP32-C6 target
|
||||
# It is necessary for the C6 to reach 750+ kbps and 1.3+ Mbps throughput.
|
||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -33,8 +33,8 @@
|
||||
#define THRPT_CHR_NOTIFY 0x000a
|
||||
#define THRPT_LONG_CHR_READ_WRITE 0x000b
|
||||
|
||||
#define READ_THROUGHPUT_PAYLOAD 510 /* MTU(512) - ATT read rsp header(1) - 1 (avoid Read Blob) */
|
||||
#define WRITE_THROUGHPUT_PAYLOAD 509 /* MTU(512) - ATT write cmd header(3) */
|
||||
#define READ_THROUGHPUT_PAYLOAD 497 /* 502 bytes ACL -> 2x 251 LL packets exactly (497 + 1 Read Rsp + 4 L2CAP) */
|
||||
#define WRITE_THROUGHPUT_PAYLOAD 495 /* 502 bytes ACL -> 2x 251 LL packets exactly (495 + 3 Write Cmd + 4 L2CAP) */
|
||||
|
||||
static const char *tag = "bleprph_throughput";
|
||||
|
||||
@@ -142,6 +142,32 @@ gatt_svr_read_write_long_test(uint16_t conn_handle, uint16_t attr_handle,
|
||||
|
||||
case THRPT_CHR_READ_WRITE:
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||
uint16_t om_len = OS_MBUF_PKTLEN(ctxt->om);
|
||||
/* If the central sends exactly 1 byte, it is a PHY control command */
|
||||
if (om_len == 1) {
|
||||
uint8_t requested_phy;
|
||||
os_mbuf_copydata(ctxt->om, 0, 1, &requested_phy);
|
||||
rc = 0;
|
||||
if (requested_phy == 0) { /* 0 = PHY_1M */
|
||||
rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_1M_PREF_MASK, BLE_HCI_LE_PHY_1M_PREF_MASK, 0);
|
||||
ESP_LOGI(tag, "Central requested 1M PHY via GATT command");
|
||||
} else if (requested_phy == 1) { /* 1 = PHY_2M */
|
||||
rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_2M_PREF_MASK, BLE_HCI_LE_PHY_2M_PREF_MASK, 0);
|
||||
ESP_LOGI(tag, "Central requested 2M PHY via GATT command");
|
||||
} else if (requested_phy == 2) { /* 2 = PHY_CODED_S2 */
|
||||
rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_CODED_PREF_MASK, BLE_HCI_LE_PHY_CODED_PREF_MASK, 0x01);
|
||||
ESP_LOGI(tag, "Central requested S2 PHY via GATT command");
|
||||
} else if (requested_phy == 3) { /* 3 = PHY_CODED_S8 */
|
||||
rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_CODED_PREF_MASK, BLE_HCI_LE_PHY_CODED_PREF_MASK, 0x02);
|
||||
ESP_LOGI(tag, "Central requested S8 PHY via GATT command");
|
||||
}
|
||||
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Failed to set preferred LE PHY; rc=%d", rc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
rc = gatt_svr_chr_write(conn_handle, attr_handle,
|
||||
ctxt->om, 0,
|
||||
sizeof gatt_svr_thrpt_static_short_val,
|
||||
|
||||
@@ -31,16 +31,16 @@ static uint8_t s_current_phy;
|
||||
|
||||
static const char *device_name = "nimble_prph";
|
||||
|
||||
#define NOTIFY_THROUGHPUT_PAYLOAD 509 /* MTU(512) - ATT notify header(3) */
|
||||
#define MIN_REQUIRED_MBUF 2 /* Assuming payload of 500Bytes and each mbuf can take 292Bytes. */
|
||||
#define NOTIFY_PIPELINE_DEPTH 15 /* Number of notifications to keep in flight for throughput */
|
||||
#define NOTIFY_THROUGHPUT_PAYLOAD 495 /* Exactly aligns with 2 LL packets */
|
||||
#define MIN_REQUIRED_MBUF 10 /* Reserve mbufs for incoming ATT packets */
|
||||
#define NOTIFY_PIPELINE_DEPTH 8 /* Number of notifications to keep in flight */
|
||||
#define PREFERRED_MTU_VALUE 512
|
||||
#define LL_PACKET_TIME 2120
|
||||
#define LL_PACKET_LENGTH 251
|
||||
#define MTU_DEF 512
|
||||
|
||||
static const char *tag = "bleprph_throughput";
|
||||
static SemaphoreHandle_t notify_sem;
|
||||
static TaskHandle_t notify_task_handle = NULL;
|
||||
static bool notify_state;
|
||||
static int notify_test_time = 60;
|
||||
static uint16_t conn_handle;
|
||||
@@ -261,12 +261,13 @@ notify_task(void *arg)
|
||||
break;
|
||||
}
|
||||
|
||||
while (notify_time < (notify_test_time * 1000)) {
|
||||
/* We are anyway using counting semaphore for sending
|
||||
* notifications. So hopefully not much waiting period will be
|
||||
* introduced before sending a new notification. Revisit this
|
||||
* counter if need to do away with semaphore waiting. XXX */
|
||||
xSemaphoreTake(notify_sem, portMAX_DELAY);
|
||||
while (notify_state && notify_time < (notify_test_time * 1000)) {
|
||||
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
|
||||
|
||||
/* Stop immediately if central unsubscribed */
|
||||
if (!notify_state) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (dummy == 200) {
|
||||
dummy = 0;
|
||||
@@ -285,17 +286,22 @@ notify_task(void *arg)
|
||||
|
||||
rc = ble_gatts_notify_custom(conn_handle, notify_handle, om);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Error while sending notification; rc = %d", rc);
|
||||
if (rc == BLE_HS_ENOMEM) {
|
||||
ESP_LOGD(tag, "Notification queue full (expected backpressure)");
|
||||
} else {
|
||||
ESP_LOGE(tag, "Error while sending notification; rc = %d", rc);
|
||||
}
|
||||
notify_count -= 1;
|
||||
xSemaphoreGive(notify_sem);
|
||||
/* Yield to let mbufs free up */
|
||||
vTaskDelay(1);
|
||||
}
|
||||
} else {
|
||||
xSemaphoreGive(notify_sem);
|
||||
notify_count -= 1;
|
||||
/* Yield briefly to let mbufs free up */
|
||||
vTaskDelay(1);
|
||||
if (notify_task_handle) {
|
||||
xTaskNotifyGive(notify_task_handle);
|
||||
}
|
||||
notify_count -= 1;
|
||||
/* Yield briefly to let mbufs free up */
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
end_time = esp_timer_get_time();
|
||||
@@ -303,13 +309,20 @@ notify_task(void *arg)
|
||||
notify_count += 1;
|
||||
}
|
||||
|
||||
printf("\n*********************************\n");
|
||||
ESP_LOGI(tag, "Notify throughput = %d bps, count = %d",
|
||||
(notify_count * NOTIFY_THROUGHPUT_PAYLOAD * 8) / notify_test_time, notify_count);
|
||||
printf("\n*********************************\n");
|
||||
ESP_LOGI(tag, " Notification test complete for stipulated time of %d sec", notify_test_time);
|
||||
/* Use actual elapsed time for accurate throughput calculation */
|
||||
{
|
||||
int actual_secs = (int)(notify_time / 1000);
|
||||
if (actual_secs > 0 && notify_count > 0) {
|
||||
printf("\n*********************************\n");
|
||||
ESP_LOGI(tag, "Notify throughput = %d bps, count = %d",
|
||||
(int)(((uint64_t)notify_count * NOTIFY_THROUGHPUT_PAYLOAD * 8) / actual_secs), notify_count);
|
||||
printf("\n*********************************\n");
|
||||
ESP_LOGI(tag, " Notification test complete (%d sec elapsed)", actual_secs);
|
||||
}
|
||||
}
|
||||
notify_test_time = 0;
|
||||
notify_count = 0;
|
||||
notify_time = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -356,6 +369,12 @@ gatts_gap_event(struct ble_gap_event *event, void *arg)
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
ESP_LOGI(tag, "disconnect; reason = %d", event->disconnect.reason);
|
||||
|
||||
/* Stop notification task loop cleanly */
|
||||
notify_state = false;
|
||||
if (notify_task_handle) {
|
||||
xTaskNotifyGive(notify_task_handle);
|
||||
}
|
||||
|
||||
/* Connection terminated; resume advertising */
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ble_gap_ext_adv_stop(0);
|
||||
@@ -376,11 +395,6 @@ gatts_gap_event(struct ble_gap_event *event, void *arg)
|
||||
|
||||
case BLE_GAP_EVENT_ADV_COMPLETE:
|
||||
ESP_LOGI(tag, "adv complete ");
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
gatts_advertise();
|
||||
#endif
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_SUBSCRIBE:
|
||||
@@ -389,17 +403,22 @@ gatts_gap_event(struct ble_gap_event *event, void *arg)
|
||||
event->subscribe.cur_notify, event->subscribe.attr_handle);
|
||||
if (event->subscribe.attr_handle == notify_handle) {
|
||||
notify_state = event->subscribe.cur_notify;
|
||||
if (arg != NULL) {
|
||||
ESP_LOGI(tag, "notify test time = %d", *(int *)arg);
|
||||
notify_test_time = *((int *)arg);
|
||||
}
|
||||
if (notify_state) {
|
||||
/* Always reset test time on new subscription.
|
||||
* The central controls the actual duration by unsubscribing.
|
||||
* Use a large default so the peripheral never stops on its own. */
|
||||
notify_test_time = 3600;
|
||||
ESP_LOGI(tag, "Notifications enabled, test time = %d sec", notify_test_time);
|
||||
/* Prime the notification pipeline to allow multiple in-flight
|
||||
* notifications. This enables the controller to fill connection
|
||||
* events with back-to-back PDUs for maximum throughput. */
|
||||
for (int i = 0; i < NOTIFY_PIPELINE_DEPTH; i++) {
|
||||
xSemaphoreGive(notify_sem);
|
||||
if (notify_task_handle) {
|
||||
xTaskNotifyGive(notify_task_handle);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(tag, "Notifications disabled");
|
||||
}
|
||||
} else if (event->subscribe.attr_handle != notify_handle) {
|
||||
notify_state = event->subscribe.cur_notify;
|
||||
@@ -408,16 +427,20 @@ gatts_gap_event(struct ble_gap_event *event, void *arg)
|
||||
|
||||
case BLE_GAP_EVENT_NOTIFY_TX:
|
||||
ESP_LOGD(tag, "BLE_GAP_EVENT_NOTIFY_TX success !!");
|
||||
if ((event->notify_tx.status == 0) ||
|
||||
(event->notify_tx.status == BLE_HS_EDONE)) {
|
||||
/* Send new notification i.e. give Semaphore. By definition,
|
||||
* sending new notifications should not be based on successful
|
||||
* notifications sent, but let us adopt this method to avoid too
|
||||
* many `BLE_HS_ENOMEM` errors because of continuous transfer of
|
||||
* notifications.XXX */
|
||||
xSemaphoreGive(notify_sem);
|
||||
} else {
|
||||
ESP_LOGE(tag, "BLE_GAP_EVENT_NOTIFY_TX notify tx status = %d", event->notify_tx.status);
|
||||
|
||||
/* Always return the pipeline token to prevent the task from blocking permanently,
|
||||
* even if the notification failed (e.g., due to disconnect or buffer full). */
|
||||
if (notify_task_handle) {
|
||||
xTaskNotifyGive(notify_task_handle);
|
||||
}
|
||||
|
||||
if ((event->notify_tx.status != 0) &&
|
||||
(event->notify_tx.status != BLE_HS_EDONE)) {
|
||||
if (event->notify_tx.status == BLE_HS_ENOMEM) {
|
||||
ESP_LOGD(tag, "BLE_GAP_EVENT_NOTIFY_TX flow control (ENOMEM)");
|
||||
} else {
|
||||
ESP_LOGE(tag, "BLE_GAP_EVENT_NOTIFY_TX notify tx status = %d", event->notify_tx.status);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -477,13 +500,8 @@ gatts_on_reset(int reason)
|
||||
void gatts_host_task(void *param)
|
||||
{
|
||||
ESP_LOGI(tag, "BLE Host Task Started");
|
||||
/* Create a counting semaphore for Notification. Can be used to track
|
||||
* successful notification txmission. Optimistically take some big number
|
||||
* for counting Semaphore */
|
||||
notify_sem = xSemaphoreCreateCounting(100, 0);
|
||||
/* This function will return only when nimble_port_stop() is executed */
|
||||
nimble_port_run();
|
||||
vSemaphoreDelete(notify_sem);
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
|
||||
@@ -512,7 +530,7 @@ void app_main(void)
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
|
||||
/* Initialize Notify Task */
|
||||
BaseType_t task_rc = xTaskCreate(notify_task, "notify_task", 4096, NULL, 10, NULL);
|
||||
BaseType_t task_rc = xTaskCreate(notify_task, "notify_task", 4096, NULL, 10, ¬ify_task_handle);
|
||||
if (task_rc != pdPASS) {
|
||||
ESP_LOGE(tag, "Failed to create notify_task (rc=%d)", task_rc);
|
||||
return ;
|
||||
|
||||
@@ -2,14 +2,32 @@
|
||||
# in this example and some example specific misc sizes are increased.
|
||||
|
||||
#
|
||||
# BT config
|
||||
# BT config (universal across all ESP32 variants)
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=n
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
|
||||
# === BT 5.0 / 2M PHY support ===
|
||||
# Ignored automatically on classic ESP32 which lacks BT 5.0
|
||||
CONFIG_BT_NIMBLE_EXT_ADV=n
|
||||
CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y
|
||||
CONFIG_EXAMPLE_EXTENDED_ADV=n
|
||||
|
||||
# === ATT / GATT ===
|
||||
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=20
|
||||
|
||||
# === Memory Pool Tuning ===
|
||||
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
|
||||
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=80
|
||||
CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=592
|
||||
CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=80
|
||||
|
||||
# Increase controller-to-host buffers
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=40
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_ACL_SIZE=255
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255
|
||||
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL=4
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y
|
||||
|
||||
# Disable NimBLE INFO logging for throughput tests
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL=2
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# === High-Performance Tuning ===
|
||||
# This config is only applied when building for the ESP32-C6 target
|
||||
# It is necessary for the C6 to reach 750+ kbps and 1.3+ Mbps throughput.
|
||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
Reference in New Issue
Block a user