Arduino BLE - Getting the most out of BLE 5.0

Hi, I'm working on a project that needs to send back-to-back characteristic writes to send a large payload of data (over 1MB) over BTLE. I'm using the Artemis chip, which supports BLE 5.0, including an MTU length up to 512 and an iOS device on the other side that also supports 512 length MTUs.

Along the way I've run in to a few troubles.

  1. I can use wireshark to sniff the BLE traffic to see what the MTU length was, but there was no way to see what it was from the code, and if I wrote beyond the length of the negotiated MTU then the left-overs were truncated... See here in wireshark:

What I thought would happen is the underlying BLE stack would use multiple packets to send my entire value... but unfortunately it just truncates and gives no error or warning and on the receiving end I get 242 bytes of my transmission only. Is there a solution to this anyone knows of?

I implemented my own that works well in BLEDevice.cpp by making these two additions:

//in BLEDevice.cpp
int BLEDevice::getMtuLength() const
	return ATT.mtu(ATT.connectionHandle(_addressType, _address));

//in BLEDevice.h
int getMtuLength() const;

  1. Why does ArduinoBLE let you create a characteristic of 512 length if there's no way to ever use that much length (if it is going to be truncated)? And I've tried changing all of the MTU lengths in att.c to 512 and no matter what the negotiated size will be 242 (even when the central size which is running on iOS offers 517 as can be seen in the screenshot above):
//512 is the max you can set this to and I don't know what the difference is between the last two params (valueLength and valueSize)...
BLECharacteristic bigDataChar("f5b64b90-14ed-11ec-82a1-0242ac130000", BLENotify, 512, 512);

Does anyone know what the best way would be to send back-to-back writes to this char and get maximum throughput with ArduinoBLE library?
Here's what I've tried... When the characteristic is subscribed to the event handler for that does this:

void bigDataCharSubscribed(BLEDevice central, BLECharacteristic characteristic) {
  int mtuLength = central.getMtuLength();//My new function I wrote above...

  SerialPrint(F("bigDataCharSubscribed event, subscribed and MTU is: "));

  for(int i=0;i<100;i++){
    char tempData[mtuLength];
    char buf[8];
    memset( tempData, 'a', sizeof(char)*mtuLength );
    *(tempData + mtuLength - 8) = '\0';
    itoa(i, buf, 10);
    characteristic.writeValue(tempData, strlen(tempData), true);

Any other tips for getting the most throughput out of Arduino BLE running on the Artemis?

I've discovered I think part of my own answer... The artemis uses mbed OS, which uses the Cordio BLE stack, so the HCI transport layer talks to that at the lowest level when sending any BLE packets... So, ArduinoBLE allows a characteristic to be 512 bytes, but the HCI layer uses a maxPkt, which is a uint8_t, so as you can see in the code below it appears to try and send whatever is leftover in the characteristic value that doesn't fit in the MTU, it is using a uint8 to store that max length, so we lose the extra bits there....

In the code below where it does the while (_pendingPkt >= _maxPkt) ... both of those are uint8_t, so it won't ever send the full 512 our characteristic value holds. Both of these are harder to fix since we probably have to change things in the mbed OS cordio library (which is I think a linked library, not something that compiles when you build your arduino project).

//from HCI.cpp
int HCIClass::sendAclPkt(uint16_t handle, uint8_t cid, uint8_t plen, void* data)
  while (_pendingPkt >= _maxPkt) {// **These are both uint8_t, so any lengths over 255 won't be sent**

  struct __attribute__ ((packed)) HCIACLHdr {
    uint8_t pktType;
    uint16_t handle;
    uint16_t dlen;
    uint16_t plen;
    uint16_t cid;
  } aclHdr = { HCI_ACLDATA_PKT, handle, uint8_t(plen + 4), plen, cid };

  uint8_t txBuffer[sizeof(aclHdr) + plen];
  memcpy(txBuffer, &aclHdr, sizeof(aclHdr));
  memcpy(&txBuffer[sizeof(aclHdr)], data, plen);

  if (_debug) {
    dumpPkt("HCI ACLDATA TX -> ", sizeof(aclHdr) + plen, txBuffer);

  HCITransport.write(txBuffer, sizeof(aclHdr) + plen);

  return 0;

Notice the setMaxMtu() uses pktLen, which is uint16_t, but maxPkt is uint8_t, so again because of this we'll never be able to negotiate an MTU with the central greater than 255 - 9.
int HCIClass::readLeBufferSize(uint16_t& pktLen, uint8_t& maxPkt)

//Also from HCI.cpp
  int result = sendCommand(OGF_LE_CTL << 10 | OCF_LE_READ_BUFFER_SIZE);

  if (result == 0) {
    struct __attribute__ ((packed)) HCILeBufferSize {
      uint16_t pktLen;
      uint8_t maxPkt;
    } *leBufferSize = (HCILeBufferSize*)_cmdResponse;

    pktLen = leBufferSize->pktLen;
    _maxPkt = maxPkt = leBufferSize->maxPkt;

#ifndef __AVR__
    ATT.setMaxMtu(pktLen - 9); // max pkt len - ACL header size

  return result;

I have a similar problem with the Nano 33 IoT. According to it's specs it has BT4.2 which should support 512 bytes. As it is it only sends 20 bytes. I can't find a handle on how to tell it to break up the 512 bytes (or rather 120 bytes in case of my application) into chunks of 20 bytes.

In the utility\att.h I saw, that there is a function called setMaxMTU. Unfortunately I cdon't know how to make make it work. Simply changing the MTU size within the att.cpp didn't solve it for me.

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