How to read long strings > 23 Bytes using BLE

I'm trying to read a long >23 character, comma separated list using BLE from my ItsyBitsy M0.


 void setup() {
void loop() {

I have also uploaded my MIT-AI2 code to successfully parse this short string into its components. Note that Strings longer than 23 Bytes will fail.

As long as I keep the text string less than 20 odd characters, I can read this data into a
string in MIT-AI2 and easily parse it to extract the 5 fields I am sending up from the ItsyBitsy.

However, when I exceed the 23 Byte BLE MTU limit, all I get is a string with the last 23 characters,
all the leading characters are lost.

Is there any way around this BLE 23 Byte buffer limit to enable longer text strings to be sent by BLE
Bluetooth using serial communication between the ItsyBitsy & MIT-AI2?

There is no problem sending & receiving long text strings with Classic Bluetooth using the .BytesAvailableToReceive block.



Thanks Juan

I see where I can request a higher MTU in MIT-AI2, However I can not see how I can mirror this request in my Arduino C code. I'm using an Adafruit ItsyBitsy32U [ATmega32U AVR chip] and a cheap MLT-BT05 Serial Bluetooth Breakout, with simple serial communication. To change the MTU C code it needs one of the BLE Arduino Libraries, none of which are compatible with my micro processor. It would appear you need a micro processor with Bluetooth built in, such as an ESP32 based processor.

I think I am stuck - so it appears I'm back to Bluetooth Classic or convoluted code to live within the 20 Byte limit with BLE.

I'm still hoping for a miracle here, if anyone has one to offer.

Thanks again, Netless

The code doesn't have to be too convoluted.

At the transmitting side, send small packets until your last packet, and end the last packet with a message delimiter like \n.

On the receiving side (AI2), keep an iniitally empty global variable for an input buffer.

As each string arrives,
   set input buffer to JOIN(input buffer, incoming text string)
   if contains(input buffer, \n) then
      split input buffer at first \n
      set global message to item 1 of the split and process it
      set input buffer to item 2 of the split
   end if
end for each

Thanks for the response ABG - Good idea, so I have drafted an MIT-AI2 implementation along these lines to try and parse some data I am trying to send up from my micro processor with the following c code.

void setup() {

  // Open serial port
  // begin bluetooth serial port communication


// Now for the loop
void loop() {

    float Amps = random(-20000, 20000)/1000.0;
    float sVolts = random(7000, 15000)/1000.00;
    float AmpH = random(-50000, 50000)/1000.0;
    float Temp = random(-2000, 45000)/1000.0;
    float rAmpV = random(0, 3300)/1000.0;
    Serial1.print("dta");     Serial1.print(",");
    Serial1.print(Amps, 0);   Serial1.print(",");
    Serial1.print(sVolts, 0); Serial1.print(",");
    Serial1.print(AmpH, 0);   Serial1.print(",");
    Serial1.print(Temp, 0);   Serial1.print(",");
    Serial1.println(rAmpV, 0);
    Serial.print("dta");      Serial.print(",");
    Serial.print(Amps, 0);    Serial.print(",");
    Serial.print(sVolts, 0);  Serial.print(",");
    Serial.print(AmpH, 0);    Serial.print(",");
    Serial.print(Temp, 0);    Serial.print(",");
    Serial.println(rAmpV, 0);

My MIT-AI2 code is also attached - almost working except for

  1. Now my 20 Byte MTU limit appears to have been replaced by a 38 Char limit on the string I can concatenate - I'd like to have a couple of decimal places in my results!

  2. Reasonably frequently, the code appears to miss a "\n" and join two data pulses together. This also appears to result in a list access error.

Anyway the MIT-AI2 code is below, not very elegant, but at least brief. Any comments on how to fix these limitations and errors would be much appreciated.




To solve both the long data item problem and the short MTU problem, you would have to switch to sending individually labeled values, one per message:

// Now for the loop
void loop() {

    float Amps = random(-20000, 20000)/1000.0;
    float sVolts = random(7000, 15000)/1000.00;
    float AmpH = random(-50000, 50000)/1000.0;
    float Temp = random(-2000, 45000)/1000.0;
    float rAmpV = random(0, 3300)/1000.0;
    Serial1.print("amps:");    Serial1.println(Amps, 0); 
    Serial1.print("svolts:");  Serial1.println(sVolts, 0); 
    Serial1.print("amph:");   Serial1.println(AmpH, 0);
    Serial1.print("temp:");    Serial1.println(Temp, 0);
    Serial1.print("rampv:");  Serial1.println(rAmpV, 0);

The tag: part must match corresponding tag: lookups in the AI2 code:


I had to fix some things in your blocks:

  • you were assembling incoming fragments backwards in the global message buffer
  • you were not feeding the rest of the global input buffer back into the input collection process.
  • you lacked for each blocks to deal with multiple input messages/fragments.
  • your labels needed more informative names, for my sanity
  • the split at \n had to be only at first \n, to defer processing of other messages until we can reach them in the input buffer.
1 Like


Thanks for your comprehensive response to my issues - I can now see how you are handling parts lists - something new to me with MIT-AI2 - It is always great to see fragments of someone else's code to help you patch over coding areas where you are out of ideas & stuck. Thanks for your effort here, I've now got some ideas & new programming concepts to move forward with.

In case your wondering what this is for - your probably not, but here it is anyway - I've built a battery meter based on a WSC1800 Hall Effect sensor to measure Amps, Voltage & particularly calculate AmpHours consumed. This part of the project is aimed at sending the data up to a nice Android interface, rather than the dumb Android serial monitor I had been using.

I also occasionally send back control messages eg "AmpHours Reset", as well as the ongoing 1 Hz data stream, hence you will have noticed my message tag "dta" as opposed to "msg" to indicate what type of information is coming up the serial line from the microprocessor.

I'll need to play around with incorporating these two different data streams with my newfound "tag" skills - There are a few interesting timing issues here as well, if your intermittent control message is triggered during an existing data stream episode.

Anyway, you have given me a new avenue to explore and it is very much appreciated.



Hi John

You do not need to send the labels! Send the values on their own, save most of the bytes.

Hi ChrisWard

Good observation - I know the order the data is coming up. In my classic BT code, that is exactly what I do, just send the 5 data values, only, up in a comma delineated string to form a simple list.

Serial1.print("D"); Serial1.print(","); //"D" to indicate I'm sending up 1 Hz data
Serial1.print(Amps, 2); Serial1.print(","); //in a single string terminated \n
Serial1.print(sVolts, 2); Serial1.print(","); //Form into a 6 part list
Serial1.print(AmpH, 2); Serial1.print(",");
Serial1.print(Temp, 2); Serial1.print(",");
Serial1.println(rAmpV, 2);

If I'm sending a return control message, it looks like

Serial1.print("M"); Serial1.print(","); //A simple return control message
Serial1.print("M"); Serial1.println("Amp Hours Reset"); //"M" to indicate a message is coming
//Form into a 2 part list

I'd like to do that here, but I've hit the 20 Byte BLE MTU limit.

What I'm thinking now after ABG's post is to append "D" to each data pulse, "M" to a message eg for a data pulse.

Serial1.print("D:"); Serial1.print("amps:"); Serial1.println(Amps, 2);

and use the first list element to split the processing between data & messages. I hoping that by keeping data labels, it won't matter if a return control message inadvertently slips into one of the 1 Hz data streams - I absolutely know which data value pairs with which measurement.

I can cut the length of the data labels down, but sending them seems like cheap insurance, and I'm not pressed for time in my 1 Hz microprocessor hardware loop.

Thanks for having a look at this.


PS This is what the classic BT interface looks like

If you are sending messages both ways and you needs quick responses,
delay() calls will get in the way.

A different architectural approach is to set aside global variable with deadlines for sending the next datum (reading) of each type, expressed in SystemTime milliseconds.

Each cycle,
  for each datum
    if that datum's deadline < Now (ms) then
       send that datum
       set that datum's new deadline to Now(ms) + that datum's preferred interval (ms)
    end if
  end for each datum
end for each cycle

A simple example Sketch using a millisecond time interval instead of Delay():


unsigned int igUpdateTime;
char val[];

void setup()
         igUpdateTime = millis();

void loop()
         //Excute loop every 1/2 second
	     if((millis() - igUpdateTime) > 500) 
              igUpdateTime = millis();

              if (Serial.available() > 0)
                   //Read Data From App
                   val =;

                   switch (val) {

                     case '1':
                              digitalWrite(led1, HIGH);

                     case '2':
                              digitalWrite(led1, LOW);

                     case '3':
                              digitalWrite(led2, HIGH);

                     case '4':
                              digitalWrite(led2, LOW);

Hi ABG & ChrisWard

I'm not using delay in the real code. The 1 Hz data stream is being triggered by a hardware interrupt from the RTC [real time clock]- next to the Bluetooth breakout in the photo. I used delay() just to get a very simplified code fragment going - I guess I could have used the "BlinkWithoutDelay" method you suggest Chris.

This is the bones of my RTC interrupt procedure

#include <Wire.h>
#include <RTClib.h>
RTC_DS3231 rtc;

void setup() {
rtc.writeSqwPinMode(DS3231_SquareWave1Hz); // enable the 1 Hz output
pinMode(SQW_Pin, INPUT_PULLUP); // set up to handle interrupt from 1 Hz pin
attachInterrupt(digitalPinToInterrupt(SQW_Pin), SQW_Interrupt, FALLING);

void loop() {

SQW_Flag = SQW_IntFlag; //Get a copy of the SQW_IntFlag
SQW_IntFlag = 0; //Reset the SQW_IntFlag

void SQW_Interrupt (void) { //Function called when interrupt detected

I can post the actual C code for this project if you are interested - its not that long.

Thanks again for your input - much appreciated