Calibrating an Accelerometer

I'm trying to calibrate my 3 axis accelerometer so that when I go through a calibration sequence of putting every axis at 0g and 1g it calculates a bias (to zero the readings at 0g, calculated using the difference between what the 0g reading is and should be) and a scaling factor so the selected ±1.5g range of the accelerometer fills the whole range and is accurate. But, it doesn't seem to work properly as it doesn't fill the scale properly, and the y-axis becomes erratic (though it is more sensitive to begin with than the other axes).

This also uses the Running Average (Arduino Playground - HomePage) library.

#include "RunningAverage.h"

#define xPin A0
#define yPin A1
#define zPin A2

#define gMode 1.5 //accelerometer has selectable ±1.5/±6g maximum

#define smoothCount 5 //how many data points should be used to average

int bias[3];
float scale[3] = {1.000, 1.000, 1.000};

RunningAverage xAxis(smoothCount);
RunningAverage yAxis(smoothCount);
RunningAverage zAxis(smoothCount);

//////////////////////////////  SETUP  /////////////////////////////

void setup() {
  Serial.begin(9600);
}

/////////////////////////////////  LOOP  ///////////////////////

void loop() {
  addCalibAvg();

  Serial.println(xAxis.getAverage(), 3);
  Serial.println(yAxis.getAverage(), 3);
  Serial.println(zAxis.getAverage(), 3);

  if (Serial.read() == 'c') {
    calibrate();
  }
}

///////////////////////////////  FUNCTIONS  /////////////////////////
void calibrate() { // make sure accelerometer is where it should be calib'd
  int zeroG[3], oneG[3]; //each axis' readings at 0 and 1g (x,y,z = 0,1,2)
  digitalWrite(13, LOW);

  clrAvg(); //clear all averages, even if it shouldn't be necessary with the next
  // step as we will be filling the whole thing with new data anyway
  //delay(2000); //give the user some time to get it right
  for(byte i; i < smoothCount; i++) addRawAvg(); //fill with new data where calibration should be
  zeroG[0] = xAxis.getAverage();
  zeroG[1] = yAxis.getAverage();
  oneG[2] = zAxis.getAverage();

  digitalWrite(13, HIGH); // signal next stage y at +1g (pointing up)
  delay(4000); //get ready for next position
  digitalWrite(13, LOW);
  delay(1000); //make the light turning off visible

  clrAvg();
  for(byte i; i < smoothCount; i++) addRawAvg();
  zeroG[0] = (zeroG[0] + xAxis.getAverage()) / 2; // spread readings over more
  oneG[1] = yAxis.getAverage();
  zeroG[2] = zAxis.getAverage();

  digitalWrite(13, HIGH); // now x pointing up
  delay(4000);
  digitalWrite(13, LOW);
  delay(1000);

  clrAvg();
  for(byte i; i < smoothCount; i++) addRawAvg();
  oneG[0] = xAxis.getAverage();
  zeroG[1] = (zeroG[1] + yAxis.getAverage()) / 2;
  zeroG[2] = (zeroG[2] + zAxis.getAverage()) / 2;

  digitalWrite(13, HIGH); // let the user know it's finished
  delay(600);
  digitalWrite(13, LOW);

  // Calculate bias and scale
  //bias based off 0g and scale off difference between 0g - 512 and 1g
  for(byte i = 0; i < 3; i++){ bias[i] = zeroG[i];}

  //calculate scaling to make 1g on accelerometer= what 1g should be on a range of 0 - 1023 (341.3333333)
  for(byte i = 0; i < 3; i++) {
    scale[i] = 341 / (oneG[i] - bias[i]); //scaling factor calculation
  }

  Serial.print("Biases: ");
  for(byte i; i< 3; i++) {
    Serial.print(bias[i]);
    Serial.print(", ");
  }
  Serial.print(" Scales:");
  for(byte i; i < 3; i++) {
    Serial.print(scale[i]);
    Serial.print(" ");
  }
  Serial.println("");
}


void addCalibAvg() { //add to averages and convert to calibrated
  xAxis.addValue((analogRead(xPin) - bias[0] - 512) * scale[0]);// take away the 512 extra as incoming values will be 0 - 1023
  yAxis.addValue((analogRead(yPin) - bias[1] - 512) * scale[1]);
  zAxis.addValue((analogRead(zPin) - bias[2] - 512) * scale[2]);
}

void addRawAvg() { //add to averages with raw -512 to 512 so centre is 0
  xAxis.addValue(analogRead(xPin) - 512);
  yAxis.addValue(analogRead(yPin) - 512);
  zAxis.addValue(analogRead(zPin) - 512);
}


void clrAvg() {
  xAxis.clear();
  yAxis.clear();
  zAxis.clear();
}

And when I look at the output of the biases and scales in the serial monitor from these lines:

  Serial.print("Biases: ");
  for(byte i; i< 3; i++) {
    Serial.print(bias[i]);
    Serial.print(", ");
  }
  Serial.print(" Scales:");
  for(byte i; i < 3; i++) {
    Serial.print(scale[i]);
    Serial.print(" ");
  }
  Serial.println("");

it shows in the serial monitor:

Biases: -10, 21, 63,  Scales:

or

Biases: -11, 22, -57,  Scales:

etc. not showing the scales.

  1. Is this best way to calibrate the accelerometer?

  2. What’s going wrong with my code?

You should calibrate it by taking readings is several known orientations, usually
-Z, +Z, -X, +X, -Y, +Y. Then the relative scaling of the axes is easy to calculate from
the differences (+Z - -Z, etc).

The offsets are in theory calculable from the sums, separately for each axis.

Of course taking a reading means an integrated/averaged reading to reduce noise.

If you want to use the device for sensing level orientation the best calibration for X and
Y offsets are their values when the device is in a known accurate +Z state. This compensates
for any misalingment of the package or even within the package.

euge64:

  Serial.print(" Scales:");

for(byte i; i < 3; i++) {
    Serial.print(scale[i]);
    Serial.print(" ");
  }

i is not being initialized. You want

for(byte i = 0; i < 3; i++) {

You've done this in several places.

Ha! Thanks gardner - I (obviously mistakenly) believed that variables were automatically initialised to zero. That's a bad mistake to make!

And as for MarkT:

Then the relative scaling of the axes is easy to calculate from
the differences (+Z - -Z, etc).

The offsets are in theory calculable from the sums, separately for each axis.

Could you explain further? How do you suggest I do these?

Thanks to both of you.

Assuming gravity doesn't change, the zero point for each axis should be half-way between
the two maximal values obtained when that axis was pointing up and pointing down, so just
average the two values (for instance if +105 and -98, the average is 3.5). Of course that
assumes the sensor is linear...

And what about the scaling?

You also might have an issue with Serial.print( ) not working properly for floats.

You also need to be clear, whether you are trying to use the sensor to measure orientation, or actual acceleration. Because the objectives of the "calibration" process outcome would be different.

It's all very well to point the sensor axis up and down, and see what reading you get in 'g', and then take the midpoint, But if this midpoint corresponds neither to a horizontal position of the axis, nor to a zero acceleration, then it probably doesn't actually serve a useful purpose.

You probably want the numerical relationship between what the sensor reads, and what you assume the acceleration to be, to be piecewise-linear rather than just plain linear.

Thanks for the replies. Sorry, why would Serial.print() not work properly for floats? It's printing it out properly now.
In this case I am trying to measure orientation/tilt. How would I do a piecewise-linear calibration? For the negative and positive sides of each axis?

Once again, thanks for your help.

Here is a detailed tutorial on calibrating accelerometers Programming and Calibration | ADXL345 Digital Accelerometer | Adafruit Learning System

However, it is not as accurate as this approach: Sailboat Instruments: Improved magnetometer calibration (Part 1) (for magnetometers, but works just as well for accelerometers, provided that the accelerometer is held still while each data point is recorded).

Thanks - I don't need it to be as accurate as the second approach. I'm not sure what to do with the readings from the first, though, as it simply says "These values can be used to re-scale readings for better accuracy", without actually telling you how to implement them.

michinyon:
You also might have an issue with Serial.print( ) not working properly for floats.

Serial.print() works just fine with floats. It's sprintf() that doesn't.

"These values can be used to re-scale readings for better accuracy", without actually telling you how to implement them.

Yes, sloppy of them. No quality control!
Another example on calibration: Bot Thoughts: Quick and Dirty Compass Calibration in 3d!

Following the notation of the last link, the relatively scaled correction for each axis could be as follows:

Xoff = (Xmax+Xmin)/2.;
Xsca = (Xmax-Xmin)/minimum_value_of_the_three_axial_extreme_differences;  //one axis is taken to be standard
Xcorrected = (Xinput - Xoff)*Xsca;