Guidance on wearable system for performance

Hi,

I'm prototyping a wearable system for musical performance using the built-in accelerometer ("IMU") sensors on Arduino Nano 33 BLE devices. The aim is to send raw accelerometer data over BLE to another Arduino Nano 33 BLE connected via USB to a computer, and rear that data in a program I wrote in PureData.

As per the rules, here is exactly what equipment I'm using:

It's all working, but the data throughput is not great, which results in too much latency.

I would welcome any advice in lowering the latency.

I expect that the PureData programme is not relevant, but I'm happy to include it if anyone wants to see it.

Many thanks!

  • martin
    Below is the code for both Arduinos.

Peripheral:

// peripheralMD106.ino 2022 10203
// largely taken from here with minor changes: https://forum.arduino.cc/t/what-is-the-most-efficient-way-to-send-accelerometer-data-over-ble-to-a-raspberry-pi/926450/12


/*
 * Example to send all data in one struct
 */

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

//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDS
//----------------------------------------------------------------------------------------------------------------------

#define BLE_UUID_IMU_SERVICE              "1101"
#define BLE_UUID_ACC_CHAR                 "2101"
#define BLE_UUID_GYRO_CHAR                "2102"
#define BLE_UUID_MAG_CHAR                 "2103"

//----------------------------------------------------------------------------------------------------------------------
// APP & I/O
//----------------------------------------------------------------------------------------------------------------------

//#define NUMBER_OF_SENSORS 3

#define ACC_SENSOR_UPDATE_INTERVAL                (500) // node-red-dashboard can't handle 1 ms, can handle 100 ms.
#define MAG_SENSOR_UPDATE_INTERVAL                (500)
#define GYRO_SENSOR_UPDATE_INTERVAL               (500)

union sensor_data {
  struct __attribute__((packed)) {
    float values[3]; // float array for data (it holds 3)
    bool updated = false;
  };
  uint8_t bytes[3 * sizeof(float)]; // size as byte array 
};

union sensor_data accData;
union sensor_data gyroData;
union sensor_data magData;


//const int BLE_LED_PIN = LED_BUILTIN;
//const int RSSI_LED_PIN = LED_PWR;
//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------

#define BLE_DEVICE_NAME                           "MDperi"
#define BLE_LOCAL_NAME                            "mdperi"

BLEService IMUService(BLE_UUID_IMU_SERVICE);
BLECharacteristic accCharacteristic(BLE_UUID_ACC_CHAR, BLERead | BLENotify, sizeof accData.bytes);
BLECharacteristic gyroCharacteristic(BLE_UUID_GYRO_CHAR, BLERead | BLENotify, sizeof gyroData.bytes);
BLECharacteristic magCharacteristic(BLE_UUID_MAG_CHAR, BLERead | BLENotify, sizeof magData.bytes);

#define BLE_LED_PIN                               LED_BUILTIN

//----------------------------------------------------------------------------------------------------------------------
// SETUP
//----------------------------------------------------------------------------------------------------------------------

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

  delay(1000);
  // Serial.println("BLE Example - IMU Service (ESS)");
  pinMode(BLE_LED_PIN, OUTPUT);
  digitalWrite(BLE_LED_PIN, LOW);
  
  if (!IMU.begin()) {
    // Serial.println("Failed to initialize IMU!");
    while (1);
  }
  if (!setupBleMode()) {
    while (1);
  } else {
    // Serial.println("BLE initialized. Waiting for clients to connect.");
  }
  
  // write initial value
  for (int i = 0; i < 3; i++) {
    accData.values[i] = i;
    gyroData.values[i] = i;
    magData.values[i] = i;
  }
}

void loop() {
  
  bleTask();
  if (accSensorTask()) {
    accPrintTask();
  }
  if (gyroSensorTask()){
    gyroPrintTask();
  }
  if (magSensorTask()){
    magPrintTask();
  }
}

//----------------------------------------------------------------------------------------------------------------------
// SENSOR TASKS
/*
 * We define bool function for each sensor.
 * Function returns true if sensor data are updated.
 * Allows us to define different update intervals per sensor data.
 */
//----------------------------------------------------------------------------------------------------------------------

bool accSensorTask() {
  static long previousMillis2 = 0;
  unsigned long currentMillis2 = millis();
  static float x = 0.00, y = 0.00, z = 0.00;
  if (currentMillis2 - previousMillis2 < ACC_SENSOR_UPDATE_INTERVAL) {
    return false;
  }
  previousMillis2 = currentMillis2;
  if(IMU.accelerationAvailable()){
    IMU.readAcceleration(x, y, z);
    accData.values[0] = x; // 0.11
    accData.values[1] = y; // 1.13
    accData.values[2] = z; // -1.13
    accData.updated = true;
  }
  return accData.updated;
}

bool gyroSensorTask() {
  static long previousMillis2 = 0;
  unsigned long currentMillis2 = millis();
  static float x = 0.00, y = 0.00, z = 0.00;
  if (currentMillis2 - previousMillis2 < GYRO_SENSOR_UPDATE_INTERVAL) {
    return false;
  }
  previousMillis2 = currentMillis2;
  if(IMU.gyroscopeAvailable()){
    IMU.readGyroscope(x, y, z);
    gyroData.values[0] = x; // 0.11
    gyroData.values[1] = y; // 1.13
    gyroData.values[2] = z; // -1.13
    gyroData.updated = true;
  }
  return gyroData.updated;
}

bool magSensorTask() {
  static long previousMillis3 = 0;
  unsigned long currentMillis3 = millis();
  static float x = 0.00, y = 0.00, z = 0.00;
  if (currentMillis3 - previousMillis3 < MAG_SENSOR_UPDATE_INTERVAL) {
    return false;
  }
  previousMillis3 = currentMillis3;
  if(IMU.magneticFieldAvailable()){
    IMU.readMagneticField(x, y, z);
    magData.values[0] = x; // 0.11
    magData.values[1] = y; // 1.13
    magData.values[2] = z; // -1.13
    magData.updated = true;
  }
  return magData.updated;
}

//----------------------------------------------------------------------------------------------------------------------
//  BLE SETUP
/*
 * Determine which services/characteristics to be advertised.
 * Determine the device name.
 * Set event handlers.
 * Set inital value for characteristics.
 */
//----------------------------------------------------------------------------------------------------------------------

bool setupBleMode() {
  if (!BLE.begin()) {
    return false;
  }

  // set advertised local name and service UUID:
  BLE.setDeviceName(BLE_DEVICE_NAME);
  BLE.setLocalName(BLE_LOCAL_NAME);
  BLE.setAdvertisedService(IMUService);

  // BLE add characteristics
  IMUService.addCharacteristic(accCharacteristic);
  IMUService.addCharacteristic(gyroCharacteristic);
  IMUService.addCharacteristic(magCharacteristic);

  // add service
  BLE.addService(IMUService);

  // set the initial value for the characteristic:
  accCharacteristic.writeValue(accData.bytes, sizeof accData.bytes);
  gyroCharacteristic.writeValue(gyroData.bytes, sizeof gyroData.bytes);
  magCharacteristic.writeValue(magData.bytes, sizeof magData.bytes);

  // set BLE event handlers
  BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);

  // start advertising
  BLE.advertise();

  return true;
}

void bleTask()
{
  const uint32_t BLE_UPDATE_INTERVAL = 2;
  static uint32_t previousMillis = 0;
  uint32_t currentMillis = millis();
  if (currentMillis - previousMillis >= BLE_UPDATE_INTERVAL) {
    previousMillis = currentMillis;
    BLE.poll();
  }
  if (accData.updated) {
    // Bluetooth does not define accelerometer
    // Units in G
    int16_t accelerometer_X = round(accData.values[0] * 100.0);
    int16_t accelerometer_Y = round(accData.values[1] * 100.0);
    int16_t accelerometer_Z = round(accData.values[2] * 100.0);
    accCharacteristic.writeValue(accData.bytes, sizeof accData.bytes);
    accData.updated = false;
  }

  if (gyroData.updated) {
    // Bluetooth does not define gyroscope
    // Units in dps
    int16_t gyro_X = round(gyroData.values[0] * 100.0);
    int16_t gyro_Y = round(gyroData.values[1] * 100.0);
    int16_t gyro_Z = round(gyroData.values[2] * 100.0);
    gyroCharacteristic.writeValue(gyroData.bytes, sizeof gyroData.bytes);
    gyroData.updated = false;
  }

  if (magData.updated) {
    // Bluetooth does not define accelerometer UUID
    // Units in uT
    int16_t mag_X = round(magData.values[0] * 100.0);
    int16_t mag_Y = round(magData.values[1] * 100.0);
    int16_t mag_Z = round(magData.values[2] * 100.0);
    magCharacteristic.writeValue(magData.bytes, sizeof magData.bytes);
    magData.updated = false;
  }
}

//----------------------------------------------------------------------------------------------------------------------
// PRINT TASKS
/*
 * Print tasks per sensor type.
 * Useful to test accuracy of sensor data before sending over BLE.
 */
//----------------------------------------------------------------------------------------------------------------------

void accPrintTask() {
  Serial.print("AccX = ");
  Serial.print(accData.values[0]);
  Serial.println(" G");

  Serial.print("AccY = ");
  Serial.print(accData.values[1]);
  Serial.println(" G");

  Serial.print("AccZ = ");
  Serial.print(accData.values[2]);
  Serial.println(" G");

  Serial.print("Acc. Subscription Status: ");
  Serial.println(accCharacteristic.subscribed());
}

void gyroPrintTask() {
  Serial.print("gyroX = ");
  Serial.print(gyroData.values[0]);
  Serial.println(" dps");

  Serial.print("gyroY = ");
  Serial.print(gyroData.values[1]);
  Serial.println(" dps");

  Serial.print("gyroZ = ");
  Serial.print(gyroData.values[2]);
  Serial.println(" dps");

  Serial.print("Gyro. Subscription Status: ");
  Serial.println(gyroCharacteristic.subscribed());
}

void magPrintTask() {
  Serial.print("magX = ");
  Serial.print(magData.values[0]);
  Serial.println(" uT");

  Serial.print("magY = ");
  Serial.print(magData.values[1]);
  Serial.println(" uT");

  Serial.print("magZ = ");
  Serial.print(magData.values[2]);
  Serial.println(" uT");

  Serial.print("Mag. Subscription Status: ");
  Serial.println(magCharacteristic.subscribed());
}

//----------------------------------------------------------------------------------------------------------------------
// Event Handlers
/*
 * These are handlers that inform connection status
 * Useful when testing, might be removed later on.
 */
//----------------------------------------------------------------------------------------------------------------------

void blePeripheralConnectHandler(BLEDevice central) {
  digitalWrite(BLE_LED_PIN, HIGH);
  Serial.print(F( "Connected to central: " ));
  Serial.println(central.address());
}

void blePeripheralDisconnectHandler(BLEDevice central) {
  digitalWrite(BLE_LED_PIN, LOW);
  Serial.print(F("Disconnected from central: "));
  Serial.println(central.address());
}




Central:

// ---------------------------------------------------------------------------
// centralMD104.ino 2022 10 03
// MD: I'm trying to read accel data on one Arduino Nano 33 BLe (peripheral)
// and read it on another Arduino nano 33 BLE  connected with USB (serial),
// sending that data to a PD patch.
//
// THIS SEEMS TO BE WORKING! :)
// ---------------------------------------------------------------------------

#include <ArduinoBLE.h>

#define BLE_UUID_IMU_SERVICE              "1101"
#define BLE_UUID_ACC_CHAR                 "2101"
#define BLE_UUID_GYRO_CHAR                "2102"
#define BLE_UUID_MAG_CHAR                 "2103"


union sensor_data {
  struct __attribute__((packed)) {
    float values[3]; // float array for data (it holds 3)
    bool updated = false;
  };
  uint8_t bytes[3 * sizeof(float)]; // size as byte array 
};

union sensor_data accData;
union sensor_data gyroData;
union sensor_data magData;


void setup() {

  Serial.begin(115200);
  while (!Serial);

  // begin BLE initialization
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");
    while (1);
  }
}

void loop()
{
  connectToPeripheral();
}

void connectToPeripheral()
{
  BLEDevice peripheral;

  do
  {
    Serial.println("scanning for service");
    // start scanning for peripherals
    BLE.scanForUuid(BLE_UUID_IMU_SERVICE); 
    peripheral = BLE.available();
    delay(3);

  } while (!peripheral);

  if (peripheral)
  {
    // discovered a peripheral, print out address, local name, and advertised service
    Serial.print("Found  ");
    Serial.print(peripheral.address());
    Serial.print(" '");
    Serial.print(peripheral.localName());
    Serial.print("' ");
    Serial.print(peripheral.advertisedServiceUuid());
    Serial.println();

    // stop scanning
    BLE.stopScan();

    getDataPeripheral(peripheral);

  }
}

void getDataPeripheral(BLEDevice peripheral)
{
  // connect to the peripheral
  //Serial.println("Connecting ...");

  if (peripheral.connect())
  {
    //Serial.println("Connected");
  } else {
    //Serial.println("Failed to connect!");
    return;
  }

  // discover peripheral attributes
  //Serial.println("Discovering attributes ...");
  if (peripheral.discoverAttributes()) {
    //Serial.println("Attributes discovered");
  } else
  {
    //Serial.println("Attribute discovery failed!");
    peripheral.disconnect();
    return;
  }

  //BLECharacteristic gestureCharacteristic = peripheral.characteristic(deviceServiceCharacteristicUuid);
  BLECharacteristic accCharacteristic = peripheral.characteristic(BLE_UUID_ACC_CHAR);
  BLECharacteristic gyroCharacteristic = peripheral.characteristic(BLE_UUID_GYRO_CHAR);
  BLECharacteristic magCharacteristic = peripheral.characteristic(BLE_UUID_MAG_CHAR);

  // subscribe to acceleration characteristic
  //Serial.println("Subscribing to simple key characteristic ...");
  if (!accCharacteristic) {
    //Serial.println("no gesture characteristic found!");
    peripheral.disconnect();
    return;
  } else if (!accCharacteristic.canSubscribe()) {
    //Serial.println("gesture characteristic is not subscribable!");
    peripheral.disconnect();
    return;
  } else if (!accCharacteristic.subscribe()) {
    //Serial.println("subscription failed!");
    peripheral.disconnect();
    return;
  } else {
    Serial.println("Subscribed to acceleration");
  }

    // subscribe to gyroscope characteristic
  //Serial.println("Subscribing to simple key characteristic ...");
  if (!gyroCharacteristic) {
    //Serial.println("no gesture characteristic found!");
    peripheral.disconnect();
    return;
  } else if (!gyroCharacteristic.canSubscribe()) {
    //Serial.println("gesture characteristic is not subscribable!");
    peripheral.disconnect();
    return;
  } else if (!gyroCharacteristic.subscribe()) {
    //Serial.println("subscription failed!");
    peripheral.disconnect();
    return;
  } else {
    Serial.println("Subscribed to gyroscope");
  }


  // subscribe to magneto characteristic
  //Serial.println("Subscribing to simple key characteristic ...");
  if (!magCharacteristic) {
    //Serial.println("no gesture characteristic found!");
    peripheral.disconnect();
    return;
  } else if (!magCharacteristic.canSubscribe()) {
    //Serial.println("gesture characteristic is not subscribable!");
    peripheral.disconnect();
    return;
  } else if (!magCharacteristic.subscribe()) {
    //Serial.println("subscription failed!");
    peripheral.disconnect();
    return;
  } else {
    Serial.println("Subscribed to magneto");
  }


  while (peripheral.connected()) {

    // while the peripheral is connected

    // check if the value of the gesture characteristic has been updated

    //Serial.println("inside peripheral.connected loop");
    
    //if (accelerationCharacteristic.valueUpdated()) {

    accCharacteristic.readValue(accData.bytes, sizeof accData.bytes);
    Serial.print("acc ");
    Serial.print(accData.values[0]);
    Serial.print(" ");
    Serial.print(accData.values[1]);
    Serial.print(" ");
    Serial.println(accData.values[2]);
    


    gyroCharacteristic.readValue(gyroData.bytes, sizeof gyroData.bytes);
    Serial.print("gyro ");
    Serial.print(gyroData.values[0]);
    Serial.print(" ");
    Serial.print(gyroData.values[1]);
    Serial.print(" ");
    Serial.println(gyroData.values[2]);


    magCharacteristic.readValue(magData.bytes, sizeof magData.bytes);
    Serial.print("mag ");
    Serial.print(magData.values[0]);
    Serial.print(" ");
    Serial.print(magData.values[1]);
    Serial.print(" ");
    Serial.println(magData.values[2]);
/*
      if (oldCommand == command)
        continue;

      oldCommand = command;

      //Serial.print("command:");
      //Serial.println(command);
      //doMovement(command);
    }
    */

    //MD TODO: do I need this?
    //wait
    //delay(50);
  }
  //Serial.println("Peripheral disconnected!");
}

// void doMovement(byte command)
// {
//   // MD removed
// }```

What baudrate is used in the 3 ...printTasks? Can it be cranked up?
I missed the Serial.begin statement.

My apologies. The [...]PrintTask() functions in "peripheral" should have been commented out. I was using those to test when the peripheral was still connected to the computer. The peripheral is only connected to the USB power bank now, so there is not serial output. In fact Serial.begin(9600) is disabled in the code because otherwise the code is waiting for a serial connection that never starts.

Should I edit the post and put in the revised code?

Thank you!

  • martin

Please post the code that corresponds to your questions.

In general, radio is inherently unreliable, and the data throughput rate depends very strongly on environment, antenna orientation, distance between RX and TX, etc. Establish a baseline data rate for the radio modules reasonably close together (a couple of meters) with clear line of sight between them, antennas parallel, in an open environment.

Serial.print certainly do not speed up any task. Comment them out and look for any changes.

Alright, I'll get on it.

The two arduinos are about 40cm away from another, on the same desk. I can't imagine that there would be any problem there.

I'll edit the code once I've cleaned it up and retested it.

Thank you!

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