7

TL;DR

How come the accelerometer values I get from Sensor.TYPE_ACCELEROMETER are slightly offset? I don't mean by gravity, but by some small error that varies from axis to axis and phone to phone.

Can I calibrate the accelerometer? Or is there a standard way of compensating for these errors?

I'm developing an app that has a need for as precise acceleration measurements as possible (mainly vertical acceleration, i.e. same direction as gravity).

I've been doing A LOT of testing, and it turns out that the raw values I get from Sensor.TYPE_ACCELEROMETER are off. If I let the phone rest at a perfectly horizontal surface with the screen up, the accelerometer shows a Z-value of 9.0, where it should be about 9.81. Likewise, if I put the phone in portrait or landscape mode, the X- and Y- accelerometer values show about 9.6. instead of 9.81.

This of course affects my vertical acceleration, as I'm using SensorManager.getRotationMatrixFromVector(), to calculate the vertical acceleration, resulting in a vertical acceleration that is off by a different amount depending on the rotation of the device.

Now, before anyone jumps the gun and mentions that I should try using Sensor.TYPE_LINEAR_ACCELERATION instead, I must point out that I'm actually doing that as well, parallel to the TYPE_ACCELERATION. By using the gravity sensor I then calculate the vertical acceleration (as described in this answer). The funny thing is that I get EXACTLY the same result as the method that uses the raw accelerometer, SensorManager.getRotationMatrixFromVector() and matrix multiplication (and finally subtracting gravity).

The only way I'm able to get almost exactly zero vertical acceleration for a stationary phone in any rotation is to get the raw accelerometer values, add an offset (from earlier observations, i.e. X+0.21, Y+0.21 and Z+0.81) and then performing the rotation matrix stuff to get the world coordinate system accelerations. Note that since it's not just the calculated vertical acceleration that is wrong - it's actually the raw values from Sensor.TYPE_ACCELEROMETER, which I would think excludes other error sources like gyroscope sensor, etc?

I have tested this on two different phones (Samsung Galaxy S5 and Sony Xperia Z3 compact), and both have these accelerometer value deviances - but of course not the same values on both phones.

How come the the values of Sensor.TYPE_ACCELEROMETER are off, and is there a better way of "calibrating" the accelerometer than simply observing how much they deviate from gravity and adding the difference to the values before using them?

1
  • 3
    This is known as Zero Offset, Offset or Bias. If the device remains stationary the average accelerometer measurement should read (0, 0, -/+SensorManager.GRAVITY_EARTH m/s²) [depending on the handedness of the coordinate frame], if not however, the accelerometer sensor is biased. Adding/subtracting an offset to "correct" the bias is the way to go :)!
    – optional
    Commented Jul 28, 2017 at 11:17

1 Answer 1

2

You should calibrate gains, offsets, and angle of the 3 accelerometers.

Unfortunately it's not possible to deepen the whole topic here.
I'll write a small introduction, describing the basic concept, and then I'll post a link to the code of a simple Clinometer that implements the calibration.

The calibration routine could be done with 7 misurations (calculate the mean value of a good number of samples) in different ortogonal positions at your choice, in order to have all +-0 and +-g values of your accelerometers. For example:

  • STEP 1 = Lay flat
  • STEP 2 = Rotate 180°
  • STEP 3 = Lay on the left side
  • STEP 4 = Rotate 180°
  • STEP 5 = Lay vertical
  • STEP 6 = Rotate 180° upside-down
  • STEP 7 = Lay face down

Then you can use the 7 measurements mean[][] to calculate offsets and gains:

calibrationOffset[0] = (mean[0][2] + mean[0][3]) / 2;
calibrationOffset[1] = (mean[1][4] + mean[1][5]) / 2;
calibrationOffset[2] = (mean[2][0] + mean[2][6]) / 2;

calibrationGain[0] = (mean[0][2] - mean[0][3]) / (STANDARD_GRAVITY * 2);
calibrationGain[1] = (mean[1][4] - mean[1][5]) / (STANDARD_GRAVITY * 2);
calibrationGain[2] = (mean[2][0] - mean[2][6]) / (STANDARD_GRAVITY * 2);

using the values of mean[axis][step], where STANDARD_GRAVITY = 9.81. Then apply the Gain and Offset Corrections to measurements:

for (int i = 0; i < 7; i++) {
    mean[0][i] = (mean[0][i] - calibrationOffset[0]) / calibrationGain[0];
    mean[1][i] = (mean[1][i] - calibrationOffset[1]) / calibrationGain[1];
    mean[2][i] = (mean[2][i] - calibrationOffset[2]) / calibrationGain[2];
}

and finally calculates the correction angles:

for (int i = 0; i < 7; i++) {
    angle[0][i] = (float) (Math.toDegrees(Math.asin(mean[0][i]
        / Math.sqrt(mean[0][i] * mean[0][i] + mean[1][i] * mean[1][i] + mean[2][i] * mean[2][i]))));
    angle[1][i] = (float) (Math.toDegrees(Math.asin(mean[1][i]
        / Math.sqrt(mean[0][i] * mean[0][i] + mean[1][i] * mean[1][i] + mean[2][i] * mean[2][i]))));
    angle[2][i] = (float) (Math.toDegrees(Math.asin(mean[2][i]
        / Math.sqrt(mean[0][i] * mean[0][i] + mean[1][i] * mean[1][i] + mean[2][i] * mean[2][i]))));
}

calibrationAngle[2] =  (angle[0][0] + angle[0][1])/2;       // angle 0 = X axis
calibrationAngle[1] = -(angle[1][0] + angle[1][1])/2;       // angle 1 = Y axis
calibrationAngle[0] = -(angle[1][3] - angle[1][2])/2;       // angle 2 = Z axis

You can find a simple but complete implementation of a 3-axis calibration in this opensource Clinometer app: https://github.com/BasicAirData/Clinometer.
There is also the APK and the link of the Google Play Store if you want to try it.

  • You can find the calibration routine in CalibrationActivity.java;
  • The calibration parameters are applied in ClinometerActivity.java.

Furthermore, you can find a very good technical article that deepens the 3-axis calibration here: https://www.digikey.it/it/articles/using-an-accelerometer-for-inclination-sensing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.