Nano 33 BLE analogRead() and BLE transfer problem

I have a project that need to read ADC value in 1.5K SPS,

and then use BLE transfer the data to my laptop.

I used "analogRead()" to read ADC value.

My BLE package size is 217 Byte that including 24 samples and 9 axis raw data

So the ideal BLE transfer speed should be 62.5 Hz or faster

But I found that the max transfer speed only 31.6 Hz

How can I modify and optimize my sketch, any advice is appreciated.

#include <ArduinoBLE.h>
#include <Arduino_LSM9DS1.h>

#define BLE_BUFFER_SIZES             217
#define BLE_DEVICE_NAME                "My sensors"
#define BLE_LOCAL_NAME                "My sensors"

BLEService BLESensors("f2361993-bcce-44d6-bf2b-29d801d662b5");
BLECharacteristic multiBLE("00000001-0000-1000-8000-00805f9b34fb", BLERead | BLENotify | BLEBroadcast, BLE_BUFFER_SIZES);

int EMG_pin[2] = {A0, A1};

int sensorValue[2] = {0, 0};

unsigned long previousTimes;
unsigned long currentMicroTimes = 0;
int preMicroTime = 0,proMicroTime = 0;
int Interval = 0;
int Timebuf = 0;
int Microtemp = 0;
unsigned long currentSensorMicroTimes = 0;
unsigned long previoussEMGTimes = 0;
int sEMGpacCount = 0;
int packageCount = 0;

byte previousIMUTimes[5] = {0x00,0x00,0x00,0x00,0x00};

int Hour = 0,Min = 0,Sec = 0,Msec = 0, Count = 0, EMGCount = 0;
float EMGSecTime = 0.0;
float SecTemp;

int sEMGildx = 48;

byte IMUheader[2] = {0x55,0xAA};
byte EMGheader[2] = {0xAA,0x55};
byte body[6] = {0x01,0x02,0x03,0x04,0x05,0x06};

int EMGcounter = 0;
int EMGtimecounter = 0;

float Acc[3], Gyro[3], Mag[3];
byte Signal[3];

byte AccXb[4] = {0x00,0x00,0x00,0x00};
byte AccYb[4] = {0x00,0x00,0x00,0x00};
byte AccZb[4] = {0x00,0x00,0x00,0x00};
byte GyroXb[4] = {0x00,0x00,0x00,0x00};
byte GyroYb[4] = {0x00,0x00,0x00,0x00};
byte GyroZb[4] = {0x00,0x00,0x00,0x00};
byte MagXb[4] = {0x00,0x00,0x00,0x00};
byte MagYb[4] = {0x00,0x00,0x00,0x00};
byte MagZb[4] = {0x00,0x00,0x00,0x00};
byte sEMG[72] = {0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00};
byte EMGtime[4] = {0x00,0x00,0x00,0x00};

byte multiSensorpacket[217] = {IMUheader[0],IMUheader[1],
                      previousIMUTimes[0],previousIMUTimes[1],previousIMUTimes[2],previousIMUTimes[3],previousIMUTimes[4],
                      body[0],
                      AccXb[0],AccXb[1],AccXb[2],AccXb[3],
                      AccYb[0],AccYb[1],AccYb[2],AccYb[3],
                      AccZb[0],AccZb[1],AccZb[2],AccZb[3],
                      GyroXb[0],GyroXb[1],GyroXb[2],GyroXb[3],
                      GyroYb[0],GyroYb[1],GyroYb[2],GyroYb[3],
                      GyroZb[0],GyroZb[1],GyroZb[2],GyroZb[3],
                      MagXb[0],MagXb[1],MagXb[2],MagXb[3],
                      MagYb[0],MagYb[1],MagYb[2],MagYb[3],
                      MagZb[0],MagZb[1],MagZb[2],MagZb[3],
                      (byte)Count,
                      EMGheader[0],EMGheader[1],
                      body[0],
                                            EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                                            EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                                            EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                                            EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      EMGtime[0],EMGtime[1],EMGtime[2],EMGtime[3],
                      sEMG[0],sEMG[1],sEMG[2],
                      sEMG[3],sEMG[4],sEMG[5],
                      sEMG[6],sEMG[7],sEMG[8],
                      sEMG[9],sEMG[10],sEMG[11],
                      sEMG[12],sEMG[13],sEMG[14],
                      sEMG[15],sEMG[16],sEMG[17],
                                            sEMG[18],sEMG[19],sEMG[20],
                      sEMG[21],sEMG[22],sEMG[23],
                      sEMG[24],sEMG[25],sEMG[26],
                      sEMG[27],sEMG[28],sEMG[29],
                      sEMG[30],sEMG[31],sEMG[32],
                      sEMG[33],sEMG[34],sEMG[35],
                                            sEMG[36],sEMG[37],sEMG[38],
                      sEMG[39],sEMG[40],sEMG[41],
                      sEMG[42],sEMG[43],sEMG[44],
                      sEMG[45],sEMG[46],sEMG[47],
                      sEMG[48],sEMG[49],sEMG[50],
                      sEMG[51],sEMG[52],sEMG[53],
                      sEMG[54],sEMG[55],sEMG[56],
                      sEMG[57],sEMG[58],sEMG[59],
                                            sEMG[60],sEMG[61],sEMG[62],
                      sEMG[63],sEMG[64],sEMG[65],
                      sEMG[66],sEMG[67],sEMG[68],
                      sEMG[69],sEMG[70],sEMG[71],
                      (byte)EMGCount
                      };

byte *pointAccXb;
byte *pointAccYb;
byte *pointAccZb;
byte *pointGyroXb;
byte *pointGyroYb;
byte *pointGyroZb;
byte *pointMagXb;
byte *pointMagYb;
byte *pointMagZb;
byte *pointLTREMG;
byte *pointLTBEMG;
byte *pointEMGtime;

void setup()
{
    Serial.begin(115200);
    //while(!Serial);

    
    if (!BLE.begin()) 
    {
        while (1);    
    }
    else
    {
        BLE.setDeviceName(BLE_DEVICE_NAME);
        BLE.setLocalName(BLE_LOCAL_NAME);
        BLE.setAdvertisedService(BLESensors);
        
        
        BLESensors.addCharacteristic(multiBLE);
        
        BLE.addService(BLESensors);
        
        BLE.advertise();
        
        if (!IMU.begin())
        { 
          Serial.println("Failed to initialize IMU!"); 
          while (1);
        }

        IMU.setAccelFS(3);   
        IMU.setAccelODR(2);
        IMU.setAccelOffset(0, 0, 0);
        IMU.setAccelSlope (1, 1, 1); 
        
        IMU.setGyroFS(3);   
        IMU.setGyroODR(2);
        IMU.setGyroOffset(0.327296, 2.791258, 3.269522);
        IMU.setGyroSlope (1, 1, 1);
        
        IMU.setMagnetFS(3);  
        IMU.setMagnetODR(6); 
        IMU.setMagnetOffset(0,0,0);  
        IMU.setMagnetSlope (1,1,1); 

        IMU.accelUnit = GRAVITY;
        IMU.gyroUnit= DEGREEPERSECOND;
        IMU.magnetUnit = MICROTESLA;
        
        Serial.println("AccX, AccY, AccZ, GyX, GyY, GyZ, MagX, MagY, MagZ");
    }

    pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{   
    BLEDevice central = BLE.central();
    if(central)
    {
        TimeReset();
        bool dataGotFlag = false;        
        
        while(central.connected())
        {   
            digitalWrite(LED_BUILTIN, HIGH);

            reTime();
      
            if(IMU.accelAvailable() && IMU.gyroAvailable())
            {
                IMU.readAccel(Acc[0], Acc[1], Acc[2]);
                IMU.readGyro(Gyro[0], Gyro[1], Gyro[2]);
                
                pointAccXb = (byte *)&Acc[0];
                pointAccYb = (byte *)&Acc[1];
                pointAccZb = (byte *)&Acc[2];
  
                pointGyroXb = (byte *)&Gyro[0];
                pointGyroYb = (byte *)&Gyro[1];
                pointGyroZb = (byte *)&Gyro[2];
                
                dataGotFlag = true;
            }
  
            if(IMU.magnetAvailable())
            {
                IMU.readMagnet(Mag[0], Mag[1], Mag[2]);
  
                pointMagXb = (byte *)&Mag[0];
                pointMagYb = (byte *)&Mag[1];
                pointMagZb = (byte *)&Mag[2];
                
                dataGotFlag = true;
            }
  
            if(dataGotFlag)
            {           
               Serial.printf(
                    "%d,%.4f,%d,%d,%d,%d,%f,%f,%f,%f,%f,%f,%f,%f,%f\r\n",
                    Count,EMGSecTime, Hour ,Min ,Sec ,Msec,
                    Acc[0], 
                    Acc[1], 
                    Acc[2],
                    Gyro[0], 
                    Gyro[1], 
                    Gyro[2],
                    Mag[0], 
                    Mag[1], 
                    Mag[2]); 
                    
                multiSensorpacket[2] = (byte)Hour;
                multiSensorpacket[3] = (byte)Min;
                multiSensorpacket[4] = (byte)Sec;
                multiSensorpacket[5] = (byte)(Msec/256);
                multiSensorpacket[6] = (byte)(Msec%256);
  
                TurnToByte();
                
                CountNum();
                multiSensorpacket[44] = (byte)Count;
                
                dataGotFlag = false;
            }
  
            previoussEMGTimes = currentSensorMicroTimes;

            for(int j = 0; j < 24; j++)
            {
                ADCread();
            }
            
            multiBLE.writeValue(multiSensorpacket,217); 
            Serial.print(EMGSecTime);
            Serial.print(",");
            Serial.println(packageCount);
            delayMicroseconds(15000);
  
            EMGcounter = 0;
            EMGtimecounter = 0;
            sEMGpacCount = 0;
            CountEMGNum();
            multiSensorpacket[216] = (byte)EMGCount;    
            packageCount++;
 
            }



        }
        digitalWrite(LED_BUILTIN, LOW);
    }
}

void Intervalcal()
{     
    if(Timebuf != 0 && preMicroTime > Timebuf)
    {
      Interval = preMicroTime - Timebuf;
      Microtemp = Microtemp + (Interval % 1000);

      if(Microtemp < 1000 && Microtemp > 0)
      {
          Interval = Interval/1000;
      }
      else
      {
          Interval = Interval/1000 + Microtemp/1000;
          Microtemp = Microtemp % 1000;
      }
      
    }

    Timebuf = preMicroTime;
}

void Timer()
{     
      if(Msec + Interval < 1000)
      {
        Msec += Interval;
      }
      else if(Msec + Interval > 1000)
      {
        if(Sec < 59)
        {
          Sec += 1;
          Msec = Msec + Interval - 1000;
        }
        else if(Sec == 59)
        {
          if(Min < 59)
          {
            Min += 1;
            Sec = 0;
            Msec = Msec + Interval - 1000;
          }
          else if(Min == 59)
          {
            Hour += 1;
            Min = 0;
            Msec = Msec + Interval - 1000;
          }
         }
       }
}

void SecTimer()
{     
  EMGSecTime = Hour*3600 + Min*60 + Sec + (float(Msec)/1000) + (float(Microtemp)/1000000);
  pointEMGtime = (byte *)&EMGSecTime;

  for(int i = 3; i >= 0; i--)
  {
    EMGtime[i] = pointEMGtime[3-i];
  }
}

void TimeReset()
{
  Hour = 0;
  Min = 0;
  Sec = 0;
  Msec = 0;
  Interval = 0;
  Microtemp = 0;
  Timebuf = 0;
}

void CountNum()
{
  if(Count < 255)
  {
    Count++;
  }
  else
  {
    Count = 0;  
  } 
}

void CountEMGNum()
{
  if(Count < 255)
  {
    EMGCount++;
  }
  else
  {
    EMGCount = 0;  
  } 
}

void TurnToByte()
{
  for(int i = 3; i >= 0; i--)
  {
    AccXb[i] = pointAccXb[3-i];
    AccYb[i] = pointAccYb[3-i];
    AccZb[i] = pointAccZb[3-i];
    GyroXb[i] = pointGyroXb[3-i];
    GyroYb[i] = pointGyroYb[3-i];
    GyroZb[i] = pointGyroZb[3-i];
    MagXb[i] = pointMagXb[3-i];
    MagYb[i] = pointMagYb[3-i];
    MagZb[i] = pointMagZb[3-i];
  }

  multiSensorpacket[8] = AccXb[0];
  multiSensorpacket[9] = AccXb[1];
  multiSensorpacket[10] = AccXb[2];
  multiSensorpacket[11] = AccXb[3];
  multiSensorpacket[12] = AccYb[0];
  multiSensorpacket[13] = AccYb[1];
  multiSensorpacket[14] = AccYb[2];
  multiSensorpacket[15] = AccYb[3];
  multiSensorpacket[16] = AccZb[0];
  multiSensorpacket[17] = AccZb[1];
  multiSensorpacket[18] = AccZb[2];
  multiSensorpacket[19] = AccZb[3];
  multiSensorpacket[20] = GyroXb[0];
  multiSensorpacket[21] = GyroXb[1];
  multiSensorpacket[22] = GyroXb[2];
  multiSensorpacket[23] = GyroXb[3];
  multiSensorpacket[24] = GyroYb[0];
  multiSensorpacket[25] = GyroYb[1];
  multiSensorpacket[26] = GyroYb[2];
  multiSensorpacket[27] = GyroYb[3];
  multiSensorpacket[28] = GyroZb[0];
  multiSensorpacket[29] = GyroZb[1];
  multiSensorpacket[30] = GyroZb[2];
  multiSensorpacket[31] = GyroZb[3];
  multiSensorpacket[32] = MagXb[0];
  multiSensorpacket[33] = MagXb[1];
  multiSensorpacket[34] = MagXb[2];
  multiSensorpacket[35] = MagXb[3];
  multiSensorpacket[36] = MagYb[0];
  multiSensorpacket[37] = MagYb[1];
  multiSensorpacket[38] = MagYb[2];
  multiSensorpacket[39] = MagYb[3];
  multiSensorpacket[40] = MagZb[0];
  multiSensorpacket[41] = MagZb[1];
  multiSensorpacket[42] = MagZb[2];
  multiSensorpacket[43] = MagZb[3];
}

void ADCread()
{
    sensorValue[0] = analogRead(EMG_pin[0]);
    sensorValue[1] = analogRead(EMG_pin[1]);

    byte EMG1sigMSB = sensorValue[0]/256;
    EMG1sigMSB << 4;
    byte EMG1sigLSB = sensorValue[0]%256;
    byte EMG2sigMSB = sensorValue[1]/256;
    byte EMG2sigLSB = sensorValue[1]%256;

    Signal[0] = EMG1sigMSB | EMG2sigMSB;
    Signal[1] = EMG1sigLSB;
    Signal[2] = EMG2sigLSB;

    multiSensorpacket[sEMGildx + EMGcounter + EMGtimecounter] = EMGtime[0];
    multiSensorpacket[sEMGildx + EMGcounter + EMGtimecounter +1] = EMGtime[1];
    multiSensorpacket[sEMGildx + EMGcounter + EMGtimecounter +2] = EMGtime[2];
    multiSensorpacket[sEMGildx + EMGcounter + EMGtimecounter +3] = EMGtime[3];

    sEMG[EMGcounter] = Signal[0];
    sEMG[EMGcounter+1] = Signal[1];
    sEMG[EMGcounter+2] = Signal[2];

    EMGcounter = EMGcounter + 3;
    EMGtimecounter = EMGtimecounter + 1;
}

void reTime()
{
        currentSensorMicroTimes = micros();      
        preMicroTime = micros();
        Intervalcal();
}

void preTime()
{
        proMicroTime = micros();
        Timer();
        SecTimer();
}

Could you please describe your application a little bit? e.g.,

  • why you think you need each data value
  • why you chose the data types
  • what do you want to do with the data

BLE has been designed for low energy and not bandwidth. The Arduino Nano 33 BLE has a powerful processor that can process a lot of data. You can use the processor to handle the data and then only send application level information. e.g., a cycle sensor might use the accelerometer to compute the rotation of a wheel but then only send a value of revolutions every second or so.

If you want to automatically sample ADC data, have a look at the following posts.

https://forum.arduino.cc/t/increase-the-adc-sample-rate/701813

It shows how to use a timer to sample data without calling analogRead. This is not intended for beginners. Let me know if you have any questions.

I want to connect my NANO 33 BLE with AD620 module which can capture EMG signal,

and then transfer EMG data through BLE to my laptop and draw the wave.

Is there a better way for me to achieve my demand.

Current problem is the BLE transfer speed too slow or I used wrong way to sent my data?

Can you please provide a link to the EMG module and the datasheet? Google shows me the AD820 Operational amplifier.

To find out what might be possible I need some more information about the data you want to display.

  • How many samples do you want to display?
  • What sampling frequency do you need?
  • What resolution do you need for the amplitude?

e.g., 100 samples per second for 30 seconds with a resolution of 8 bits.

  • Do you want to see the data live or would you be happy to see the data after it has been captured?
  • Why do you want to see the data? Just for fun or are you a medical doctor who needs to analyze the data?

Why does your example code above try to send all the other data e.g., Acc, Gyro, Mag and time? As I said before BLE has been designed for low power. You need to be selective of what data needs to be send.

Oh, I'm sorry. It's not AD820 but AD620, like the link below
https://www.robotics.org.za/AD620-MOD

I'm a researcher of biomedical engineering, so I want to see the data live that can let
me know the muscle strength changes of subject immediate.

I'll also save the data in .CSV to analyze it.
T1_5min_CSV.zip (3.8 MB)
Attachment is my ideal results file.

Because of the commercial sEMG is used the sampling frequency of 1500 Hz, so I want to follow it to do my project.

I want to display 1500 samples per second for 5 minute with a resolution of 10 bits.
In my program, I will catch 6 sample a time to draw line, so I have to draw the wave in 250 Hz.
But I'm not sure whether it is the best way for me, I will try and error.
Or can you give me some advise?

I also want to analyze the IMU data and need the time to synchronize the data.
I tried to use separate characteristic to send the data, but I found the speed was too slow, so I put them in a package to send.

I wrote an example for somebody working on a similar application. Maybe it can be helpful.

https://forum.arduino.cc/t/arduino-ble-nano-data-loss-during-bluetooth-transmission/881712/7

To optimize the bandwidth you can increase the samples per BLE data packet to reduce the overhead.

I had testing this example and it worked well

But I fount the transfer speed still too slow.

I used LightBlue App to receive the data, and the time interval between two package was 30ms in the example, in other words the transfer speed was about 33Hz.

In the example,a package has 12 sample, so I can only get 33*12 = 396 sample/second

I wonder know wheather the result is normal or it's my misunderstanding.

How did you measure that? Did you use my example as is?