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