Arduino Nano 33 BLE and HeadTracking over BlueTooth

I've done a little code for headtracking over BT for this board, still awaiting it's arrival so I can't test it right now, but would like to hear some suggestions, criticism etc from you guys if I need to fix something for this project to work.

I am using Arduino Nano 33 BLE and it's LSM9DS1 sensors for Head Tracking of x, y, z, yaw, pitch and roll, sending data over BT to opentrack program on PC via BT for sims (flying, racing etc).

Thanks

// Include libraries
#include <ArduinoBLE.h>
#include <Arduino_LSM9DS1.h>
#include <MadgwickAHRS.h>

// Declare the BLE object
BLEService movementService("19B10000-E8F2-537E-4F6C-D104768A1214");
BLEFloatCharacteristic xChar("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);
BLEFloatCharacteristic yChar("19B10002-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);
BLEFloatCharacteristic zChar("19B10003-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);
BLEFloatCharacteristic yawChar("19B10004-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);
BLEFloatCharacteristic pitchChar("19B10005-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);
BLEFloatCharacteristic rollChar("19B10006-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);

// Filter setup
Madgwick filter; // Madgwick filter

// Sensor measurements
float ax, ay, az; // Accelerometer data in m/s^2
float gx, gy, gz; // Gyroscope data in rad/s
float mx, my, mz; // Magnetometer data in uT

// additional variables
float distance = 0; // distance travelled
float vx = 0, vy = 0, vz = 0; // velocity readings
float prev_ax = 0, prev_ay = 0, prev_az = 0; // previous acceleration readings
float prev_vx = 0, prev_vy = 0, prev_vz = 0; // previous velocity readings
float x_accel = 0, y_accel = 0, z_accel = 0; // accel temporary coordinates
float x_yawpitch = 0, y_yawpitch = 0, z_yawpitch = 0; // yaw and pitch temporary coordinates

// Output variables
float x = 0, y = 0, z = 0;
float roll = 0, pitch = 0, yaw = 0;

// Time variables
unsigned long prevTime = 0;
unsigned long currentTime = 0;
float dt = 0;

// Define complementary filter constants
const float alpha = 0.9; // Weight for accelerometer data
const float beta = 0.1; // Weight for yaw and pitch data

// Setup
void setup() {
	// Initialize the BLE module
	if (!BLE.begin()) {
		while (1);
	}	
	
	// Set BLE local name
	BLE.setLocalName("Head Tracker");
	
	// Add BLE service and characteristic	
	BLE.addService(movementService);
	BLE.setAdvertisedService(movementService);
	movementService.addCharacteristic(xChar);
	movementService.addCharacteristic(yChar);
	movementService.addCharacteristic(zChar);
	movementService.addCharacteristic(yawChar);
	movementService.addCharacteristic(pitchChar);
	movementService.addCharacteristic(rollChar);
	
	// Device name over BlueTooth
	BLE.setDeviceName("Head Tracker");
	BLE.setAppearance(0x00);
	
    // Start advertising the BLE service
	BLE.advertise();  
	
	// Initialize the LSM9DS1 sensor
	if (!IMU.begin()) {
		while (1);
	}
	   	
	// Zeroing
	xChar.setValue(0);
	yChar.setValue(0);
	zChar.setValue(0);
	yawChar.setValue(0);
	pitchChar.setValue(0);
	rollChar.setValue(0);
	
	// Initialize the Madgwick filter with a sampling frequency of 60 Hz
	filter.begin(60);

}

// Main
void loop() {
	
	// Compute time interval since last loop iteration
	currentTime = micros();
	dt = (currentTime - prevTime) / 1000000.0;
	
	if (dt >= 1/60) {
	
		// Read sensor data
		IMU.readAcceleration(ax, ay, az);
		IMU.readGyroscope(gx, gy, gz);
		IMU.readMagneticField(mx, my, mz);
  
		// Perform sensor fusion with the Madgwick filter
		filter.update(gx, gy, gz, ax, ay, az, mx, my, mz);
  
		// Get orientation estimates in terms of pitch, yaw, and roll
		roll = filter.getRoll();
		pitch = filter.getPitch();
		yaw = filter.getYaw();
		
		// calculate position over acceleration
		vx = vx + (ax + prev_ax) * dt / 2;
		vy = vy + (ay + prev_ay) * dt / 2;
		vz = vz + (az + prev_az) * dt / 2;

		x_accel = x + (vx + prev_vx) * dt / 2;
		y_accel = y + (vy + prev_vy) * dt / 2;
		z_accel = z + (vz + prev_vz) * dt / 2;

		// calculate distance
		distance = sqrt(x*x + y*y + z*z);

		// calculate position over yaw and pitch
		x_yawpitch = cos(yaw) * cos(pitch) * distance;
		y_yawpitch = sin(yaw) * cos(pitch) * distance;
		z_yawpitch = sin(pitch) * distance;
		
		// Combine data using complementary filter
		x = alpha * x_accel + beta * x_yawpitch;
		y = alpha * y_accel + beta * y_yawpitch;
		z = alpha * z_accel + beta * z_yawpitch;
	
		// Send the sensor data over BLE
		rollChar.writeValue(roll);
		pitchChar.writeValue(pitch);
		yawChar.writeValue(yaw);
		xChar.writeValue(x);
		yChar.writeValue(y);
		zChar.writeValue(z);
		
		// Save current values
		prevTime = currentTime;
		prev_vx = vx;
		prev_ax = ax;
		prev_vy = vy;
		prev_ay = ay;
		prev_vz = vz;
		prev_az = az;
	}
	
	BLE.poll();
	
}

Hello rex1825,
what a marvellous idea!
I was searching for the functionality to head track for racing sims as a wireless solution in connection with opentrack.
Some years ago I got hold on a TrackIR 5, but these cables are really annoying and when moving out of camera beam bad thing happen on screen :joy:
With a device that's measuring it's position in room itself and sending the data to opentrack the precision must be great.
Have you receive the BLE in the meantime and been able to test it on a device?
Best regards
dusfor63

Hi. Were you able to test it out with Arduino Nano BLE? I am unfamiliar with BLE, so I wondered whether it is possible to do it over BLE. I saw that you are polling at 60 Hz. Did you test with BLE before deciding on the frequency?

Hey Rex, how did you get on with this looking to do something along the same lines ?

Hi rex1825,
I am working on a project involving head tracking also. I wish to mount a GoPro camera on my bike's handlebars which will be panned (yaw rotation) by a servo motor controlled by a Nano 33 BLE, which will be the central device. I've got that part built and operational. I would like to use a second Nano 33 BLE (as a peripheral device) mounted on top of my bike helmet. My goal is to have the camera pan to follow my head rotation.
Just wanted to let you know, I uploaded your code to my peripheral Nano and it compiled and uploaded without error. I was able to see the Head Tracker device and the "advertisement" info on Light Blue. I didn't see any of the Movement characteristics - but I am very new to BLE and have no idea if I should expect to see them there.
I tried adding some Serial.print statements to your code to see if I could observe the pitch, roll and yaw values in IDE as the BLE was moved, but once I moved the BLE around the program locked up.
I imagine I mucked up the timing, so I undid all that.
My next step is to try to devise a sketch for the central device BLE to see if I can read the values from the peripheral.
I would be very interested if you have had success with your project! Thanks for sharing your code!

If you change the order slightly in the original post, you can see the service and characteristics in Light Blue.

// Add BLE service and characteristic 
  //BLE.addService(movementService);
  BLE.setAdvertisedService(movementService);
  movementService.addCharacteristic(xChar);
  movementService.addCharacteristic(yChar);
  movementService.addCharacteristic(zChar);
  movementService.addCharacteristic(yawChar);
  movementService.addCharacteristic(pitchChar);
  movementService.addCharacteristic(rollChar);
  BLE.addService(movementService);  

LightBlue (or nrfConnect) do not have a data format to view a float, but I can see the 4 bytes of the data changing rapidly.

Cattledog, thank you kindly for that advice! I made the change you suggested and confirm I now see the data streaming by for each characteristic in LightBlue. Greatly appreciated!

Can I ask you one more noobie question - is it correct for me to just copy the UUID strings for the characteristics exactly as shown in rex1825's post, or do I need to alter them to "personalize" them to my boards?
I am still confused by how UUIDs are supposed to be applied. If you know of a good detailed step by step tutorial for new users on using UUIDs for BLE on Arduino I would be grateful.

Again, many thanks!

I doubt you will be near any other BLE devices using those UUIDs so it is OK to use them.

I am still confused by how UUIDs are supposed to be applied. If you know of a good detailed step by step tutorial for new users on using UUIDs for BLE on Arduino I would be grateful.

The search engine of your choice will give the same advice I can. BLE is complex, and the learning curve for new users is steep. There is no getting around that. Follow library examples and read as many tutorials as you can.

Ok, will do. Thanks!

Did you get this working? I use Windows and am yet to figure out how to use BLE to interface with Nano33BLE. Are you using a second to connect to the tracker and sending via serial or something?

Also, using the filter library you have here showed accurate rotation but with the pitch always drifting to an inverted position after the initial movement. Did anyone else have a solution for this?

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