Low throughput with GATT - MTU won't increase, conninterval won't decrease

For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
LePetitRenard
 
Posts: 15
Joined: Tue Oct 25, 2022 10:24 am

Low throughput with GATT - MTU won't increase, conninterval won't decrease

Post by LePetitRenard »

I have a BLE GATT project on the Feather Sense nRF52840 where it connects to a smartphone app. My goal is to achieve 4.5 kB/s throughput. This has already been accomplished on an ESP32 so the smartphone app is working as is most of the Arduino code. However, the Feather Sense won't give a higher throughput than ~1.2 kB/s.

I know of two ways to increase throughput: (1) a larger MTU size or (2) shorter connection interval. But, I can't get the code for either of these things to work.

(1) My app is showing that only 20 bytes are being received for each BLE message, so the MTU size isn't being increased. The android app itself requests an increased MTU size (which worked with the ESP32), and I have already implemented

Code: Select all

connection->requestMtuExchange(184);
within the 'connect_callback' function. Neither of these has made a difference.

(2) Each 'myCharacteristic.notify(myBLEpacket, sizeof(myBLEpacket));' command takes ~13ms to complete. This is very long, especially when compared to the ESP32. I have tried to lower the connection interval by

Code: Select all

Bluefruit.Periph.setConnInterval(6, 8);
This had no effect. The 'ConnectionPriority' is already set to 'High' on the android side, and again, the ESP32 has no issue here. I have noticed that when I attempt a larger MTU size, even though it doesn't work, the time required for .notify() drops to <1 ms. Maybe this is because a certain delay is needed for this code and more time is being spent creating a longer BLE message here?

I had previous issues with updating the GATT characteristic with string type information, where corrupted data was sent through. Though the data was wrong, the throughput was 9 kB/s so such throughput is possible.

Besides this I have set 'Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);' and devices are on a table beside one another so distance is not a factor.

Help would be much appreciated. Thanks!

User avatar
LePetitRenard
 
Posts: 15
Joined: Tue Oct 25, 2022 10:24 am

Re: Low throughput with GATT - MTU won't increase, conninterval won't decrease

Post by LePetitRenard »

I have spent more time with this issue, but have not been able to resolve it.

I found another command in the 'Bluefruit.h' library which changes the MTU size:

Code: Select all

  void AdafruitBluefruit::configPrphConn(uint16_t mtu_max, uint16_t event_len, uint8_t hvn_qsize, uint8_t wrcmd_qsize)
I have used it as follows:

Code: Select all

  Bluefruit.configPrphConn(184, BLE_GAP_EVENT_LENGTH_DEFAULT, BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT, BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT);
and I have confirmed it is working by:

Code: Select all

    Serial.print("MTU Size: ");
    Serial.println(Bluefruit.Connection(0)->getMtu());
Which prints in the Serial Monitor as :
MTU Size: 184
Still, none of the BLE messages being sent are larger than 20 bytes in length, and this is confirmed on my own app and nRFConnect. Any ideas?

User avatar
LePetitRenard
 
Posts: 15
Joined: Tue Oct 25, 2022 10:24 am

Re: Low throughput with GATT - MTU won't increase, conninterval won't decrease

Post by LePetitRenard »

I have partially solved this, at least to a working condition. Will post a full write-up in coming days for future reference.

User avatar
LePetitRenard
 
Posts: 15
Joined: Tue Oct 25, 2022 10:24 am

Re: Low throughput with GATT - MTU won't increase, conninterval won't decrease

Post by LePetitRenard »

This is a brief tutorial on how to get high throughput via GATT BLE using Bluefruit and the nRF52840 series of Adafruit chips as peripherals. This has only been tested on a Feather Sense, but it should work elsewhere. Most Bluefruit examples use UART so the below took some figuring out:

If you are coming from ESP32 or Nano BLE boards like myself, the switch may not be not clear. To summarize the differences:

1. You must specify the maximum potential size of your Characteristic data in your BLE set-up.
1.1 .'setFixedLength()'
2. You must specifiy the current size of the data you want to update your Characteristic with when updating
2.1 '.notify(data, sizeof(data));'
3. You must increase the MTU size to gain higher throughput.
3.1 'requestMTUExchange()' or have the central request an MTU increase
3.2 For whatever reason (as of 11/2022, and AFAIK), the nRF52840 has a longer connection interval than ESP32 so MTU it's the only method



Here are the above 3 elements in different parts of code. First we start with a basic BLE initialization, and it's here we set the maximum possible size of our characteristic:

Code: Select all


void setup() {

  /* ----------------------------------------------------------------
      Initialize Adafruit Feather Sense
     ----------------------------------------------------------------
  */
  Serial.begin(115200);
  while ( !Serial ) delay(10);   // for nrf52840 with native usb


  /* ----------------------------------------------------------------
      Initialize Bluetooth Low Energy (BLE)
     ----------------------------------------------------------------
  */

  Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
  Bluefruit.configUuid128Count(15);

  Bluefruit.begin();                                          // Start Bluetooth
  Bluefruit.setTxPower(4);                                    // Check bluefruit.h for supported values
  Bluefruit.Periph.setConnectCallback(connect_callback);
  Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
  Bluefruit.Periph.setConnInterval(6, 8); // 7.5 - 15 ms 


  BLEservice.begin();                                               // Add service to peripheral

  myCharacteristic.setProperties(CHR_PROPS_NOTIFY);                 // Set characteristic to NOTIFY
  myCharacteristic.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS);  // Read is open, Write is closed
  myCharacteristic.setFixedLen(sizeof(myCharacteristic));           // <- THIS IS NECESSARY
  myCharacteristic.begin();                                         // Initialize Characteristic

  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();
  Bluefruit.Advertising.addService(BLEservice);

  Bluefruit.ScanResponse.addName();

  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 244);    // in unit of 0.625 ms
  Bluefruit.Advertising.setFastTimeout(30);      // number of seconds in fast mode
  Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds

}
You must set myCharacteristic.setFixedLen()' to the maximum potential size of your message;

Then to update your BLE characteristic, you can use a function or put this code in your loop

Code: Select all

void updateBLE() {
  
  myCharacteristic.notify(myData, sizeof(myData));                 // Set characteristic message, and notify client

}

You must set the *size* of the data you want to update the BLE Characteristic with, even if it's a
c string


Next, we increase the MTU size. This can be done in 2 ways. The first, as is normally done with ESP32/Nano, is to have the central request an increased MTU. Bluefruit gives the possibility to do this easily from the peripheral side in the connect callback function:

Code: Select all

void connect_callback(uint16_t conn_handle)
{
  // Get the reference to current connection
  BLEConnection* connection = Bluefruit.Connection(conn_handle);

  char central_name[32] = { 0 };
  connection->getPeerName(central_name, sizeof(central_name));

  Serial.println("Request to change MTU size to 100 bytes");
  connection->requestMtuExchange(sizeof(myData) + 3);                              // Change MTU SIZE

  Serial.print("Connected to ");
  Serial.println(central_name);

  // delay a bit for all the request to complete
  delay(1000);

}
Obviously you can specifiy the MTU size you want, but whatever size it is, the MTU must be at least 3 bigger than the length/size of your message.

What made this odd is compared with previous BLE experience with ESP32 and Nano BLE, you must specify the characteristic size twice. If you do not you will get strange sorts of behavior. If you do not set '.SetFixedLength()', you can't have a higher MTU than 23 (20 of data) even with an MTU request change, or it may not work at all. If your '.setFixedLength()' value is too low, only message lengths up to that specified will be sent through. Somewhat likewise, if your message size (as per .notify) is smaller than your .setFixedLength() value, values up to the the message size will be sent through accurately, and sometimes a couple bytes beyond that. I'm without explanantion for this, but have observed it.

Something you be beware of is that each time the BLE characteristic is updated, it will send as many characters as is specified by '.setFixedLength' every time, even if the message is smaller. It will either "make up" those unspecified bytes beyond your message size, or use old ones.

This is especially important if you plan to send any variable type whose size/length may change over time (i.e. c strings)

One last difference to note is the ESP32 can achieve the same BLE speeds with one standard MTU sized message (23 bytes) as it takes the Feather Sense an MTU of 180+ to achieve. This must be related to the connection interval, but for whatever reason, the commands for this haven't been able to lower it fast it enough to compete with the ESP32.

5 kB/s and higher should be achievable

Locked
Please be positive and constructive with your questions and comments.

Return to “Wireless: WiFi and Bluetooth”