I include below
-
the Java equivalent (for the most part) for the Python LSM6 class mentioned in an earlier post; this class depends on the Pi4J project
-
a Java program that uses the Java LSM6 class to calculate the yaw of the device using the Z axis of the gyroscope; the program measures the time needed to do the calculation of the yaw, but ignores the acquisition time
-
a Python script that uses the Python LSM6 class to do the same thing as the Java program
-
a brief discussion of the performance comparison between the two languages
The Performance Comparison
I plan on using the IMU as part of an ambitious project, with many things happening at once, and I became concerned about the use of Python in terms of performance. I decided to create the simple tests shown in the code below to get a hint about the performance differences. I can in no way be considered a Python expert, nor, at least any longer, a Java expert. I admit that I made some choices for data forms, etc. that might not come close to optimal performance in either language. But I did attempt to implement the approach as identically as possible. So accept this comparison with some skepticism about general performance differences.
The performance tests loop 200 times. In each iteration, the test reads the gyro, gets a timestamp, calculates the current yaw angle, gets a timestamp, and outputs the difference between the two timestamps.
I used a spreadsheet to find the average time to do the calculation. For Java, the average was 6.967 microseconds. For Python 3, the average was 82.983 microseconds. Java appears to be about 12X the performance of Python 3 in this simple test. This reference suggests that one can expect Java to be between 1X and 50X faster than Python.
Now, there is a reality check. The time between samples taken was 20 milliseconds. So even 83 microseconds is “in the noise” in this context. However, in the context of a number of threads/programs running simultaneously, a 12X difference could be important.
The following code is the Java LSM6 class. It should not necessarily be considered “production ready” but certainly works.
/*
* This class represents the LSM6DS33, containing an accelerometer and
* a gyroscope. The access to the module is via I2C.
*
* This implementation is a singleton to insure that there is only one. It
* is highly unlikely that will matter, but better safe than sorry.
*/
package pi4j_i2c;
import com.pi4j.io.i2c.I2CBus;
import com.pi4j.io.i2c.I2CDevice;
import com.pi4j.io.i2c.I2CFactory;
import java.io.IOException;
/**
*
* @author gregflurry
*/
public class LSM6 {
// the one and only instance of the class
private static LSM6 instance = null;
// the device used for communication
private static I2CDevice lsm6 = null;
/**
* Constructor (private)
*
* Gets an I2C bus and then gets the device for the LSM6DS33.
*
* @throws IOException
* @throws com.pi4j.io.i2c.I2CFactory.UnsupportedBusNumberException
*/
private LSM6() throws IOException, I2CFactory.UnsupportedBusNumberException {
// get the I2C bus for communication
I2CBus i2c = I2CFactory.getInstance(I2CBus.BUS_1);
// create an I2C device for an individual device on the bus to communicate with;
// in this case an LSM6DS33
lsm6 = i2c.getDevice(0x6b);
// read the "who am I" register
int who = lsm6.read(LSM6_REGS.WHO_AM_I);
// check the ID
if (who != 105) {
throw new IOException("Can't connect to LSM6DS33");
}
}
/**
* This returns the one and only instance of the class representing the LSM6DS33.
*
* Instance gets created if necessary.
*
* @return instance of the I2C device for the LSM6D33
* @throws IOException
* @throws com.pi4j.io.i2c.I2CFactory.UnsupportedBusNumberException
*/
public static synchronized LSM6 getInstance() throws IOException, I2CFactory.UnsupportedBusNumberException {
// if not already constructed
if (instance == null) {
instance = new LSM6(); // so so
}
return instance;
}
/**
* Enable the LSM6DS33.
*
* The accelerometer and gyroscope can be enabled independently
*
* @param enableAccel true enables the accelerometer
* @param enableGyro true enables the gyroscope
* @throws IOException
*/
public void enable(boolean enableAccel, boolean enableGyro) throws IOException {
if (enableAccel) {
lsm6.write(LSM6_REGS.CTRL1_XL, (byte)0x50); // 208 Hz ODR, 2 g FS
}
if (enableGyro) {
lsm6.write(LSM6_REGS.CTRL2_G, (byte)0x58); // 208 Hz ODR, 1000 dps FS
}
lsm6.write(LSM6_REGS.CTRL3_C, (byte)0x04); // IF_INC = 1 (automatically increment register address)
}
/**
* Read the gyroscope, all three axis.
*
* @return a Vector containing the x,y,z readings
* @throws IOException
*/
public Vector readGyro() throws IOException {
byte[] buffer = new byte[6];
// read the data from the device
lsm6.read(LSM6_REGS.OUTX_L_G, buffer, 0, 6);
// get 16 bit integers
// contrust the response as an int
int gx = (int) (buffer[1] << 8);
gx = gx | Byte.toUnsignedInt(buffer[0]);
int gy = (int) (buffer[3] << 8);
gy = gy | Byte.toUnsignedInt(buffer[2]);
int gz = (int) (buffer[5] << 8);
gz = gz | Byte.toUnsignedInt(buffer[4]);
return new Vector(gx, gy, gz);
}
// private class for LSM6 register numbers
private static class LSM6_REGS {
static byte WHO_AM_I = 0x0f;
static byte CTRL1_XL = 0x10;
static byte CTRL2_G = 0x11;
static byte CTRL3_C = 0x12;
static byte OUTX_L_G = 0x22;
static byte OUTX_L_XL = 0x28;
}
// public class defining a "vector" with x,y,z values
public class Vector {
int x;
int y;
int z;
public Vector(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
}
// main for testing
public static void main(String[] args) throws I2CFactory.UnsupportedBusNumberException, IOException, InterruptedException {
LSM6 gyro = LSM6.getInstance();
gyro.enable(false, true);
// Vector res = new Vector(0, 0, 0);
for(int i = 0; i < 200; i++) {
Vector res = gyro.readGyro();
// System.out.println(Instant.now());
// System.out.println(System.nanoTime());
// System.out.println(res.z + ", " + System.nanoTime()/1000);
System.out.println(res.z);
Thread.sleep(20);
}
}
}
The following code is the Java test program. It has some commented out debug statements left in it that can be ignored.
/*
* This does "more accurate" integration
*
*
*
*/
package pi4j_i2c;
import com.pi4j.io.i2c.I2CFactory;
import java.io.IOException;
/**
*
* @author gregflurry
*/
public class TestLSM6_A {
/**
* @param args the command line arguments
*/
public static void main(String[] args) throws IOException, I2CFactory.UnsupportedBusNumberException, InterruptedException {
// offset is approximately bias of z gyro
final double offset = 98.2;
// sf is full scale factor constant for DPS (see spec sheet p.15)
final double sf = 35.0 / 1000.;
// angle is the current angle of gyro (integration)
double angle = 0.0;
double dt = 0.0; // dt in seconds
LSM6 gyro = LSM6.getInstance();
gyro.enable(false, true);
long lastTime = -1;
long currentTime;
double lastScaled = 0.0; // assumes start when motionless
double scaled = 0.0;
for (int i = 0; i < 200; i++) {
LSM6.Vector res = gyro.readGyro();
currentTime = System.nanoTime(); // gets timestamp in nanoseconds
// get the offset and scaled sample
double unscaled = (double) res.z + offset;
scaled = unscaled * sf;
// System.out.println(res.z + ", " + System.nanoTime());
// make sure don't try to mess with first sample
if (lastTime != -1) {
// integrate to calculate current angle
// integration sums current average dps * delta T
dt = (double)(currentTime - lastTime) /1000000000.0; // dt in seconds
// System.out.println("dt: " + dt);
angle = angle + (0.5 * (scaled + lastScaled)) * dt;
}
lastScaled = scaled;
lastTime = currentTime;
// this prints the time it takes to do all the necessary calculations
// to do integration
System.out.println(System.nanoTime() - currentTime);
// System.out.println(angle);
// System.out.println(res.z + ", " + scaled + ", " + currentTime + ", " +
// dt + ", " + angle);
Thread.sleep(20); // delay 20ms so don't sample too often
}
}
}
This is the Python 3 script that is theoretically identical to the Java test program, except of course, that is uses the Python LSM6 class. It too has some extraneous debug code commented out.
import LSM6
import time
import datetime
#q = LSM6.Vector(1,2,3)
#print (q)
# offset is approximately bias of z gyro
offset = 90.3
# sf is full scale factor constant for DPS (see spec sheet p.15)
sf = 35.0/1000
# angle is the current angle of gyro (integration)
angle = 0
gyro = LSM6.LSM6()
gyro.enable(False, True)
# make sure have enough time for the thing to turn on properly
time.sleep(1)
previous_time = -1
current_time = 0
for k in range(200):
gyro.read_gyro()
# print(gyro.g, " ", datetime.datetime.now().microsecond)
# print(gyro.g.z, ", ", datetime.datetime.now().timestamp())
# get time
current_time = datetime.datetime.now().timestamp()
#print (current_time)
unscaled = gyro.g.z + offset
scaled = unscaled * sf
# make sure don't try to mess with first sample
if previous_time != -1:
# integrate to calculate current angle
# integration sums current angle + dps * delta T
# angle = angle + s * (current_time - previous_time)
angle = angle + (0.5 * (scaled + last_scaled)) * (current_time - previous_time)
previous_time = current_time
last_scaled = scaled
# print(angle)
# print(gyro.g.z, ", ", angle)
print(datetime.datetime.now().timestamp() - current_time)
# make sure don't sample too fast
time.sleep(0.02)