Sending an Array of Sensor Data Nano 33 BLE to iOS Application

Hi All,

I want to send sensor data from the Arduino Nano 33 BLE (Gyroscope, Accelerometer) to a iOS Application that I am building myself. Currently I am using the ArduinoBLE Library. I am able to send a single value to the application and able to parse the data it into a readable value on the app side but I have noticed not all the values are picked up by the application. For Example, if the code on Arduino is sending a value every second, my app will maybe pick it up every 2 or 3 seconds. I am thinking a better way to make sure data is not lost is to send an Array of data or is there a different library to use? If I am sending an array of data, do I just use a loop to append the data into my Array and then send that or is there a different way to handle Arrays. I am new to Arduino so forgive my ignorance on the subject and thank you for the feedback.

In BLE the application layer does not send data. Your Arduino is a peripheral/server. The peripheral does not decide what data the client/central wants. It can tell the client that it has new data, but the client is free to ignore that. It is like you reading some news web page instead of them dropping the paper in your front yard every day.

This makes the design more energy efficient and scalable. You can create a sensor that updates the data every second but later you can create many different clients that read the data at whatever speed they want. They can connect once and only read the value once or every ten minutes if that is good enough for them. You do not need or might not be able to change the sensor for that. You might have sold the sensor already to your customers.

Additionally, you must assume that along the way the connection will get disturbed and data can get lost. That is a major feature of wireless communication.

One solution is to rethink your application. Have a look at the CSC (Cycling Speed and Cadence) measurement defined by the Bluetooth org.
Instead of "sending" the wheel revolutions per second every second it provides the cumulative wheel and crank revolutions. That way the central can read the data at whatever speed it wants. The revolution per time interval will get more averaged with slower intervals but allows lower power on the central and the peripheral by having fewer transmissions.

There might be some reasons to keep some history values and send them via an array, but I would do this as an additional option.

If you could share your code, we could have a look if there are any additional reasons why your application is not reading all value. For instance, did you use the notify feature for characteristics?

Please use code tags and make sure the code can be read without downloading a file. This will ensure more people will answer your post. It should look like this.

Your example code

The "How to post ..." at the beginning of each sub forum will tell you how to help people to help you. :slight_smile:

Thanks for the correction, I had used the incorrect terminology but yes I understand how BLE works by having the central grab data instead of being sent data. I have attached the code below, the app successfully subscribes to the notifications when the characteristics are updated by the Arduino. When I watch the console on the Arduino, the values update every second where as on the app it is slower. I have the app reading the data as soon as the values are updated. I am not sure if Transmission/Propagation delay would be large enough to cause this, I am fairly new to BLE. I don’t think averages of the values would suffice, I would be looking for specific patterns of values returned by the accelerometer and gyroscope, such as a sudden spike in acceleration for 2 seconds or a quick change in orientation, maybe even combination of both.

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

BLEService gyroService("1101"); //Declare service for Gyroscope
BLEFloatCharacteristic gyroscopeValuesChar("2101", BLERead | BLENotify);



void setup() {
  Serial.begin(9600);
  while (!Serial);

  pinMode(LED_BUILTIN, OUTPUT); //Intializes the built in LED to indicate when a central device has connected
  if (!BLE.begin()) 
  {
    Serial.println("BLE failed");
    while (1);
  }
  
  if (!IMU.begin()) {
    Serial.println("Failed initializing IMU");
    while (1);
  }
  
  BLE.setLocalName("Gyroscope");                       
  BLE.setAdvertisedService(gyroService);                       
  gyroService.addCharacteristic(gyroscopeValuesChar);   //Adds the gryoscope characteristics 
  BLE.addService(gyroService);                                     //Adds the gyroscope service 
    
  BLE.advertise();                                      //Starts advertising the peripheral device over bluetooth
  Serial.println("Waiting for connection..");
}

void loop() 
{
  BLEDevice central = BLE.central();                    //Waits for BLE Central device to connect

  if (central)                                          
  {
    Serial.print("Connected to central: ");
    Serial.println(central.address());
    digitalWrite(LED_BUILTIN, HIGH);                    //Turn on peripheral LED to indicate valid connection with Central Device

    while (central.connected()) {                       //While the Peripheral Device is connected to the Central Device
      float x,y,z;                                      //Declare variables to hold gyroscope values 
      if(IMU.gyroscopeAvailable()) {                    //If the gyroscope sensor is available, read the values into variables.
        IMU.readGyroscope(x,y,z);
         Serial.print(x);
         Serial.print('\t');
         Serial.print(y);
         Serial.print('\t');
         Serial.println(z);
      }
      gyroscopeValuesChar.writeValue(x);  
      delay(1000);

    }
  }
  digitalWrite(LED_BUILTIN, LOW);
  Serial.print("Disconnected from central: ");
  Serial.println(central.address());
}

BLEService gyroService("1101"); //Declare service for Gyroscope
BLEFloatCharacteristic gyroscopeValuesChar("2101", BLERead | BLENotify);

With BLE you need to be aware of some of rules that need to be applied when it comes to defining a service and a characteristic as there are predefined services which come with their own 16bit UUID:

Then within each of these predefined services there are predefined characteristics which also use 16bit UUID's.

As such, it is not recommended to use 16 UUID's for your custom design as it can cause unintended consequences if using a 3rd party app to read this data. In your case, as you are creating your own customised app, you may be ok so long as it is just your own use etc.

Instead, the norm is to use 128bit UUID's for your custom services and characteristics.

Now, as you are wanting to send Gyro and Acceleration data, I presume you want to send X-, Y- & Z- axis values all at the same time. If so, you need to consider using a byte array for this.

You can do this using the "BLECharacteristic" command as follows (this is just an example):

// create movement data characteristic (this is a Byte Array) and allow remote device to use Notify
BLECharacteristic moveDataCharacteristic("19B10011-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify, 24, true);

The above has defined "moveDataCharacteristic" as a 24 byte array where 4 bytes are used for each float value. So all 6 values (xyz acceleration and xyz gyro) will be sent concurrently. Note, that the "true" bolted on as the last parameter is optional and is used to simply say that this array is a fixed size (i.e. array size cannot vary up to the max size of 24).

If you want to have a separate characteristic for acceleration and for gyro then create a byte array with size 12 for each characteristic, etc.

Your issue is the delay() function. It stops the BLE stack from working correctly. Try setting the delay to much longer and then connect to your Arduino with an app like BLE Scanner. You will see it takes a long time to even show you the characteristic.

You should use the millis() function to control timing. Check out the File → Examples → 02.Digital → BlinkWithoutDelay

Here is your example updated.

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

BLEService gyroService( "1101" ); //Declare service for Gyroscope
BLEFloatCharacteristic gyroscopeValuesChar( "2101", BLERead | BLENotify );



void setup()
{
  Serial.begin( 9600 );
  while ( !Serial );

  pinMode( LED_BUILTIN, OUTPUT ); //Intializes the built in LED to indicate when a central device has connected
  if ( !BLE.begin() )
  {
    Serial.println( "BLE failed" );
    while ( 1 );
  }

  if ( !IMU.begin() )
  {
    Serial.println( "Failed initializing IMU" );
    while ( 1 );
  }

  BLE.setLocalName( "Gyroscope" );
  BLE.setAdvertisedService( gyroService );
  gyroService.addCharacteristic( gyroscopeValuesChar ); //Adds the gryoscope characteristics
  BLE.addService( gyroService );                                   //Adds the gyroscope service

  BLE.advertise();                                      //Starts advertising the peripheral device over bluetooth
  Serial.println( "Waiting for connection.." );
}

void loop()
{
  static unsigned long previousMillis = 0;
#define SENSOR_UPDATE_INTERVALL 1000

  BLEDevice central = BLE.central();                    //Waits for BLE Central device to connect

  if ( central )
  {
    Serial.print( "Connected to central: " );
    Serial.println( central.address() );
    digitalWrite( LED_BUILTIN, HIGH );                  //Turn on peripheral LED to indicate valid connection with Central Device

    while ( central.connected() )                       //While the Peripheral Device is connected to the Central Device
    {
      unsigned long currentMillis = millis();
      if ( currentMillis - previousMillis > SENSOR_UPDATE_INTERVALL )
      {
        previousMillis = currentMillis;
        float x, y, z;                                    //Declare variables to hold gyroscope values
        if ( IMU.gyroscopeAvailable() )                   //If the gyroscope sensor is available, read the values into variables.
        {
          IMU.readGyroscope( x, y, z );
          Serial.print( x );
          Serial.print( '\t' );
          Serial.print( y );
          Serial.print( '\t' );
          Serial.println( z );
          gyroscopeValuesChar.writeValue( x );
        }
      }
    }
  }
  digitalWrite( LED_BUILTIN, LOW );
  Serial.print( "Disconnected from central: " );
  Serial.println( central.address() );
}

Additionally I noted you are using 16-bit UUIDs. These are reserved for the BLE.org defined services and characteristics. You should use 128-bit random UUIDs. Just create yourself a script or use an online UUID creator.

I used the mills() function and that fixed the delay and missing values I was getting. Thank you very much for that information. I will be looking into using BLECharacteristic to send a Byte Array of values since I do want to send the XYZ values all together, I can have multiple characteristics so 2 12 bytes should work. I just need to figure out how to parse the array on the application side. Swift reads the value as a raw value, so parsing it into a Float Array is on the programmer. I know this isn't a swift forum but if anyone has any experience with that, it would be helpful, but I got what I came here for. Thank you!