BLE GATT message stuck at 20 byte MTU - Adafruit Board, Bluefruit Library

I'm using an Adafruit Feather Sense nRF52840 board to send BLE data via GATT to a smartphone application. The goal is to achieve at least 4.5 kB/s, but so far the max throughput is stuck at ~1.2 kB/s.

I've tried everything I can find in the Adafruit Bluefruit nRF52 Libary examples and in the 'Bluefruit.h' library. The main thing I have tried is to increase the MTU size from the standard 20 bytes to 184 bytes. The Adafruit board reports the change as working, yet the message size will not go >20 bytes. My code will be posted below.

As a sanity check, the entire Arduino sketch works on an ESP32, the only difference between scripts being the BLE libraries. Therefore the issue is not with the rest of the sketch, or the android app (and just in case, I have double checked by using nRFConnect).

Commands I have used (separately) to increase the MTU size:

(1)

connection->requestMtuExchange(184);

(2)

Bluefruit.configPrphConn(mtu_max = 184, BLE_GAP_EVENT_LENGTH_DEFAULT, BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT, BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT);


I have confirmed the change with the following code:

    Serial.print("MTU Size: ");
    Serial.println(Bluefruit.Connection(0)->getMtu());

which reports:

MTU Size: 184

So the MTU size is changing. Despite this, the sent BLE message is never >20 bytes as confirmed by my application and nRFConnect.

Normally the MTU size change is requested by the central (smartphone app) which is how this works with the ESP32 without problem. In any case I tried to change it here without success.

I have also tried to change the connection interval, as the .notify() command which updates the BLE message takes ~13ms to complete, a very long time. On the ESP32 it is <1 ms. For some reason, when I send a BLE message <20 byte this is the time, but when attempting to send a longer message, the command takes less time (between 0-4ms).

Again, the connection interval can be controlled by the central, at least on Android, by the connection priority setting be set to "high". This functions on the ESP32, and can be easily tested using the nRFConnect. I observed no changes when using the Feather Sense.

Command to decrease the connection interval

Bluefruit.Periph.setConnInterval(6, 8);

No change was observed by using this line.

I'm not sure what to do at this point. This board needs to work as the ESP32 is too power hungry for tthe project, but for some reason the throughput is being throttled making it unusable. Previously I was able to send 9 kB/s through the board, but this was with corrupted data, so hopefully such throughput is still possible.

There is very little on BLE GATT for the Adafruit nRF52 boards, hopefully someone can help :slightly_smiling_face:

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

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:

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

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:

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

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.