Improve requesting accelerometer data rate through BLE

Hello,

I’ve got a Nano 33 BLE Sense board, and I recently want to raise a project which is to get IMU data from Arduino and analyze them. Since I am ready to run program on a Raspberry Pi 4 to collect data from Arduino, I decided to use bluepy to do this job.
There the problem comes…
After I finish the sample program on both side, Arduino and Pi4, I find out that the data rate in my python program is too slow. On Arduino serial monitor, it print imu data about every 25ms. But in python, it read data about every 295 ms.
There is something that mention about getting accelerometer data through BLE here in the “Notify” section. So I am wondering that If I should use this approach? or is there anything I can improve on my sample code?

Here is my simplest code on arduino…

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

const int ledPin = LED_BUILTIN; // set ledPin to on-board LED
//Service for publish Accelerometer value...
BLEService ACC("1001");
BLEFloatCharacteristic accX("2001", BLERead | BLENotify);
BLEFloatCharacteristic accY("2002", BLERead | BLENotify);
BLEFloatCharacteristic accZ("2003", BLERead | BLENotify);


float acc_x, acc_y, acc_z;

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

  pinMode(ledPin, OUTPUT); // use the LED as an output
 
  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");

    while (1);
  }

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

  IMU.setAccelFS(3);          
  IMU.setAccelODR(5);           //
  IMU.setAccelOffset(0, 0, 0);  //   uncalibrated
  IMU.setAccelSlope (1, 1, 1);  //   uncalibrated
  IMU.accelUnit = GRAVITY;    // or  METERPERSECOND2

  BLE.setDeviceName("IMU");
  // set the local name peripheral advertises
  BLE.setLocalName("IMU");
 
  BLE.setAdvertisedService(ACC);
 
  ACC.addCharacteristic(accX);
  ACC.addCharacteristic(accY);
  ACC.addCharacteristic(accZ);
 
  BLE.addService(ACC);
 
  BLE.setConnectable(true);
  // BLE.setAdvertisingInterval(100);// configure the interval time for BLE
  BLE.advertise();
 
  Serial.println("Bluetooth Device Active, Waiting for Connections...");

  Serial.println(" AX \t AY \t AZ");
}

void loop() {
  BLEDevice central = BLE.central();

  if(central) {
    Serial.print("Connected to Central: ");
    Serial.println(central.address());
    while(central.connected()) {
      IMU.readAccel(acc_x, acc_y, acc_z);
      accX.writeValue(acc_x);
      accY.writeValue(acc_y);
      accZ.writeValue(acc_z);
     
      Serial.print(millis());
      Serial.print('\t');
      Serial.print(acc_x);
      Serial.print('\t');
      Serial.print(acc_y);
      Serial.print('\t');
      Serial.print(acc_z);
      Serial.println('\t');
      sleep(5);
    }
  }
  Serial.print("Disconnected from Central: ");
  Serial.println(BLE.address());
}

And here is my python code…

import sys
import time
import binascii
import struct
from bluepy import btle
from bluepy.btle import UUID, Peripheral

def main():
	if len(sys.argv) != 2:
		print("Fatal, must pass device address:", sys.argv[0], "<device address="">")
		quit()

	accelServiceUuid = "00001001-0000-1000-8000-00805f9b34fb"
	accXCharUuid = "00002001-0000-1000-8000-00805f9b34fb"
	accYCharUuid = "00002002-0000-1000-8000-00805f9b34fb"
	accZCharUuid = "00002003-0000-1000-8000-00805f9b34fb"

	print("Connecting......", accelServiceUuid)
	peripheralObject = btle.Peripheral(sys.argv[1]);

	print("Services...")
	for svc in peripheralObject.services:
	    print(str(svc))

	mySensor = btle.UUID(accelServiceUuid);
	sensorService = peripheralObject.getServiceByUUID(mySensor);

	print("get Characteristics...")
	acc_x = sensorService.getCharacteristics(accXCharUuid)[0]
	acc_y = sensorService.getCharacteristics(accYCharUuid)[0]
	acc_z = sensorService.getCharacteristics(accZCharUuid)[0]

	while 1:
		print(round(time.time() * 1000), acc_x.read(), acc_y.read(), acc_z.read())


if __name__ == "__main__":
	main()

Welcome to the forum!

Please have a look at reply#1 in the following post. Your post is almost identical and has the same issues. e.g, use of delay and choice of characteristics.

You can ignore the first few lines in the reply. You used code tags correctly. :slight_smile:

Let me know when anything is unclear or you have some further questions.

Hello,

First of all, thanks to Klaus_K, I appreciate so much…

According to the suggestion of Klaus_K, I modify my sample program on both side. It improve a lot, but I still hope it better. On Arduino serial monitor, it print imu data about every 20ms. But in python, it read data about every 98 ms. My goal is to make the frequency as much close as arduino advertised. Is it possible??

Here is the code I modified.

Code on arduino…

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

#define BLE_UUID_SENSOR_DATA_SERVICE              "2BEEF31A-B10D-271C-C9EA-35D865C1F48A"
#define BLE_UUID_MULTI_SENSOR_DATA                "4664E7A1-5A13-BFFF-4636-7D0A4B16496C"

#define NUMBER_OF_SENSORS 3

const int ledPin = LED_BUILTIN; // set ledPin to on-board LED

union multi_sensor_data
{
  struct __attribute__( ( packed ) )
  {
    float values[NUMBER_OF_SENSORS];
  };
  uint8_t bytes[ NUMBER_OF_SENSORS * sizeof( float ) ];
};

union multi_sensor_data multiSensorData;

//Service for publish Accelerometer value...
BLEService sensorDataService( BLE_UUID_SENSOR_DATA_SERVICE );
BLECharacteristic
 multiSensorDataCharacteristic( BLE_UUID_MULTI_SENSOR_DATA, BLERead | 
BLENotify, sizeof multiSensorData.bytes );

float acc_x, acc_y, acc_z;

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

  pinMode(ledPin, OUTPUT); // use the LED as an output
 
  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");

    while (1);
  }

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

  IMU.setAccelFS(3);           
  IMU.setAccelODR(5);           // 
  IMU.setAccelOffset(0, 0, 0);  //   uncalibrated
  IMU.setAccelSlope (1, 1, 1);  //   uncalibrated
  IMU.accelUnit = GRAVITY;    // or  METERPERSECOND2 

  BLE.setDeviceName("IMU");
  // set the local name peripheral advertises
  BLE.setLocalName("IMU");
  
  BLE.setAdvertisedService(sensorDataService);
  
  // BLE add characteristics
  sensorDataService.addCharacteristic( multiSensorDataCharacteristic );
  
  // add service
  BLE.addService( sensorDataService );
  
  // set the initial value for the characeristic:
  multiSensorDataCharacteristic.writeValue( multiSensorData.bytes, sizeof multiSensorData.bytes );
  
  BLE.setConnectable(true);
  // BLE.setAdvertisingInterval(100);// configure the interval time for BLE
  BLE.advertise();
  
  Serial.println("Bluetooth Device Active, Waiting for Connections...");

  Serial.println(" AX \t AY \t AZ");
}

void loop() {
  #define UPDATE_INTERVALL 5
  static long previousMillis = 0;
  
  BLEDevice central = BLE.central();

  if(central) {
    Serial.print("Connected to Central: ");
    Serial.println(central.address());
	while(central.connected()) {
		unsigned long currentMillis = millis();
		if (currentMillis - previousMillis >= UPDATE_INTERVALL) {
			previousMillis = currentMillis;
			
			IMU.readAccel(acc_x, acc_y, acc_z);
			multiSensorData.values[0] = acc_x;
			multiSensorData.values[1] = acc_y;
			multiSensorData.values[2] = acc_z;
			multiSensorDataCharacteristic.writeValue( multiSensorData.bytes, sizeof multiSensorData.bytes );
      
			Serial.print(millis());
			Serial.print('\t');
			Serial.print(acc_x);
			Serial.print('\t');
			Serial.print(acc_y);
			Serial.print('\t');
			Serial.print(acc_z);
			Serial.println('\t');
		}
	}
  }
  Serial.print("Disconnected from Central: ");
  Serial.println(BLE.address());

}

And here is my python code…

import sys
import time
import binascii
import struct
from bluepy import btle
from bluepy.btle import UUID, Peripheral

def main():
	if len(sys.argv) != 2:
		print("Fatal, must pass device address:", sys.argv[0], "<device address="">")
		quit()
		
	accelServiceUuid = "2BEEF31A-B10D-271C-C9EA-35D865C1F48A"
	accCharUuid = "4664E7A1-5A13-BFFF-4636-7D0A4B16496C"
	
	print("Connecting......", accelServiceUuid)
	peripheralObject = btle.Peripheral(sys.argv[1]);

	print("Services...")
	for svc in peripheralObject.services:
		print(str(svc))

	mySensor = btle.UUID(accelServiceUuid);
	sensorService = peripheralObject.getServiceByUUID(mySensor);
	
	print("get Characteristics...")
	accVal = sensorService.getCharacteristics(accCharUuid)[0]
	
	while 1:
		print(round(time.time() * 1000), accVal.read())
		
def parseFloatData(data):
	data = binascii.b2a_hex(data)
	data = binascii.unhexlify(data)
	data = struct.unpack('f', data)[0]
	return data


if __name__ == "__main__":
    main()

You got the basics now, please go back to the two threads, there are more answers to your question. e.g., change data type and add more values into each packet. BLE does not just send data whenever you update it, so reducing the data rate is the most effective way forward.

Is there a way to subscribe to the characteristics in your python program? That is why on the Arduino the characteristic is set to BLENotify. If the client does not subscribe, it needs to poll the data, reducing the data rate.

Another thing you might need to think about is the application layer. Is it necessary to transfer all the sensor data from the Arduino to the Raspberry Pi? The Arduino Nano 33 BLE has a powerful processor that would allow all kinds of algorithm to run on it. Then you can send processed data that may allow an even lower data rate.

Do you use the build in Bluetooth on the Raspberry Pi 4? Can you point me to a web page that helps setting this up? I tried using BLE in Node-RED on Raspberry Pi and that did not work.

Hi Klaus_K,

Thanks to your reply, I am still working on it, thanks....

About my Raspberry Pi, mine is below....
https://wiki.radxa.com/Rockpi4

I use the OS publish by this web, and the BLE works fine.

Hello…

After lots of test, I found some bottleneck. So I can’t wait to share with you guys…

At the beginning, I am thinking about improve the very beginning source throughput which is “readAccel” first.
No matter what I do, even if I try to begin with SimpleAccelerometer.ino, Arduino still prints data about every 20ms.
Then I try to print the mills before and after “readAccel”, it only cost 2ms!!
So I guess maybe it is because of the “Serial.print” which consumes too much time.
The article below proves my guess…

After not “Serial.print” accelerometer data, I try to add a member of mills in the BLE struct in order to verify the sample rate in the python program. I found it really save some more time.

More after, I try to modify my python program with notify approach. It improves more…
Now I can get about 60Hz sample in my python program.

Thanks to Klaus_K’s help again…

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