Accelerometer Calibration II: Simple Methods

This is the second post in a series.  It starts here.

Now let’s look at some of the simplest methods we can use to calibrate an accelerometer.   By “simple” here I mean simple all the way through: easy to wire the circuit, easy to code, easy to understand, easy to use.  Later we’ll see that these don’t always come as a package: fairly sophisticated software can make the users job even simpler than anything we see here.

The simple methods I will describe here all follow the same flow: the user carefully places the accelerometer in position, flips a switch to tell it to start recording data, flips the switch back to tell it to stop, then moves the accelerometer into a different position to start again.  The differences all come down to deciding how many measurements to take and what to do with the numbers we get.  Because of that, all of these methods can be performed using the same simple circuit.

Let’s build that before we go on.

The Data Collection Circuit

The data collection circuit is pretty simple:  the ADXL335 is set up just like it was in the basic circuit example.  The only difference is that I add a switch.  One tells the circuit to start recording data, the other tells it to stop.  This lets us make sure we only record data when the accelerometer is properly positioned and still.

This is what the circuit looks like.

Adding a pushbutton to the ADXL335 circuit to control data recording. I really placed the button on the table, not the breadboard.  It's hard to keep it still while holding the button.

Of course we need some code to control the circuit.  The sketch below will do the job:

// these constants describe the pins. They won't change:
const int xpin = A1;                  // x-axis of the accelerometer
const int ypin = A2;                  // y-axis
const int zpin = A3;                  // z-axis (only on 3-axis models)

int switchPin = 2;

int reportData = 0;
int sampleDelay = 20;   //number of milliseconds between readings
int collectionPhase = 0;  //Button presses will break up data collection into phases.  This variable counts and identifies the phase.

void setup()
{
  // initialize the serial communications:
  Serial.begin(9600);
  analogReference(EXTERNAL);

  pinMode(xpin, INPUT);
  pinMode(ypin, INPUT);
  pinMode(zpin, INPUT);

  pinMode(switchPin, INPUT);

}

void loop()
{
  //Check if the button was pressed.  Toggle data reporting if it was.
  int val;
  val = digitalRead(switchPin);

  //When the button is not pressed there is no connection to GND
  //so the value we read is HIGH
  if (val==HIGH) {
    if(reportData==1) {
      collectionPhase += 1;
      Serial.print("#Collection phase ");
      Serial.println(collectionPhase);
    }

    reportData = 0;
    delay(500);
  } else {
    //The value is LOW.  This means the button is being pressed,
    // opening a connection to GND that pulls down the voltage on
    // the input pin.
    reportData = 1;
  }

  if(reportData>0) {
    //take sample and write values to the serial monitor
    Serial.print( analogRead(xpin));
    Serial.print("\t");
      delay(1);
    Serial.print( analogRead(ypin));
    Serial.print("\t");
      delay(1);
    Serial.print( analogRead(zpin));
    Serial.print("\n");

  }
  // delay before next reading:
  delay(sampleDelay);
}

The code here is pretty simple.  When the button is pressed, a connection is opened to GND that pulls the voltage on pin 2 down to LOW.  If we see this happen, go into a “reporting data” state (reportData == 1).  When we’re in the reporting state, we write the readings to the serial monitor.  Keep doing this as long as the button is still pressed.

When the button isn’t pressed, pin 2 reads HIGH.  This puts is in a “quiet state” (reportData==0).  If this happens when we had been in a reporting state then we print a one-line comment telling us what data collection phase we just finished.  These comments can be very helpful for understanding the data later.

Now you can take measurements.  Build the circuit, upload the sketch, and open the serial monitor.  At first you’ll see nothing, but push the button and hold it down a bit and watch the data flow.  Move the accelerometer and try again.

Now we are ready to calibrate.

One-Point Calibration

The simplest possible empirical calibration we can do will only use one measurement.  In fact we’re only going to use one-third of a measurement.  Here is what you do.

  1. Build the data collection circuit from above, upload the sketch, and open the serial monitor.
  2. lay the accelerometer flat on a table.
  3. push the button for a bit.
You should see a stream of consistent readings show up on the monitor.  If they are bouncing around a lot, something is wrong and you won’t be able to calibrate.  My readings look like this:
511	521	619
510	521	618
511	521	618
511	521	618
511	521	618
511	521	618
511	521	619

I could average them, but I’ll just say x=511, y= 521, and z=618.  Now I want to use this to calibrate.  Recall that when we did the faith-based calibration based on datasheet specs, we needed two numbers:

  1. zero-G.  This number tells us which voltage reading corresponds to zero G’s on an axis.
  2. sensitivity. This tells us how much the voltage changes per G an an axis.

Now if we only take one measurement, we can’t estimate two parameters.  We’ll need to take one on faith and estimate the other.

Let’s assume that a reading of 512 corresponds to zero G and use this reading to estimate the sensitivity, or how much the voltage changes per G.  The way we placed the device, all of the acceleration is on the z-axis, so we should be seeing 1 G of acceleration on the z pin. Thus

1 G = 618 - 512 = 106

Recall that per the datasheet, we expected this sensitivity factor to be 102.3.  This is significantly different.

Now when we get new readings we can convert them to G’s as follows. Let a be the acceleration on an axis measured in Gs and let m be the actual measurement we got off the pin.  Then

a = (m-512)/106

In code, we can now change our loop to output converted units:

void loop()
{
  int x = analogRead(xpin);

  //add a small delay between pin readings.  I read that you should
  //do this but haven't tested the importance
    delay(1); 

  int y = analogRead(ypin);

  //add a small delay between pin readings.  I read that you should
  //do this but haven't tested the importance
    delay(1); 

  int z = analogRead(zpin);

  //zero_G is the reading we expect from the sensor when it detects
  //no acceleration.  Subtract this value from the sensor reading to
  //get a shifted sensor reading.
  float zero_G = 512.0; 

  //scale is the number of units we expect the sensor reading to
  //change when the acceleration along an axis changes by 1G.
  //Divide the shifted sensor reading by scale to get acceleration in Gs.
  float scale = 106; 

  Serial.print(((float)x - zero_G)/scale);
  Serial.print("\t");

  Serial.print(((float)y - zero_G)/scale);
  Serial.print("\t");

  Serial.print(((float)z - zero_G)/scale);
  Serial.print("\n");  // delay before next reading:
  delay(sampleDelay);
}

When I run this loop, I get readings like this:

-0.04	0.08	1.00
-0.03	0.09	1.00
-0.04	0.08	1.00

It is better than what we had before on the z-axis, but not too great on the other axes.  I do a more thorough error analysis later, but for now it is clear that we should do something better.

One-point Calibration Overall Grades:  Accuracy – Bad       Easiness – OK

Six-point Calibration

It’s pretty clear why the one-point calibration didn’t work very well:  We only took measurements in one direction on one axis. Eyeballing the data makes it clear that 0G doesn’t really give us a reading of 512 on our input pin, and it looks like different pins might have different centers.

So we are not trying to find just two numbers. We need to find the 0G mark and the sensitivity on each axis.  Call them m_{x}, \delta_x, m_{y}, \delta_y, m_z, and \delta_z (e.g. m_z is the zero-G mark or the “middle” on the z-axis, \delta_z is the sensitivity on the z-axis).  That makes 6 numbers,  so we should expect to need at least 6 measurements.

Let’s do the obvious thing.  Take right-side-up and up-side-down measurements on each axis.  Here I will start with the z-axis.  Laying my circuit flat on the table I got the reading

511	521	618

Flipping it upside down I get

518	501	413

The X and Y axis readings tell me I’m not laying this down perfectly flat, but I tried!  We could futz back and forth until we get it just right, but in the next post we’ll see a better way to deal with positioning trouble.  For now let’s accept it as “good enough” and see how good that is.

Our 1G reading for the Z axis was 618, the -1G reading (upside down) was 413.  Linear interpolation (aka common sense)  tells us that 0G on the z-axis should be exactly in the middle:

m_{z} = (618 + 413)/2 = 515.5

We can also estimate the sensitivity.  The difference between our two measurements should be 2G, so just find the difference and divide by 2:

\delta_z = (618-413)/2 = 102.5

Continuing on the Y axis I get measurements

516	608	516  #Y-up
511     397     518  #Y-down

Giving us a zero-G mark

m_{y} = (608+397)/2 = 502.5

and sensitivity

\delta_{y} = (608-397)/2 = 105.5

Finally on the X axis we get  measurements

619	505	523  #X-up
410     505     518  #X-down

Giving us a zero-G mark

m_{x} = (619+410)/2 = 509.5

and sensitivity

\delta_{x} = (619-410)/2 = 104.5

So the parameters are close, but still significantly different on each axis.  To convert the measurements to Gs using these parameters, use a loop like this:

void loop()
{
  int x = analogRead(xpin);
  delay(1); 

  int y = analogRead(ypin);
  delay(1); 

  int z = analogRead(zpin);

  float zero_G_x = 509.5;
  float zero_G_y = 502.5;
  float zero_G_z = 515.5;

  float scale_x = 104.5;
  float scale_y = 105.5;
  float scale_z = 102.5;

  Serial.print(((float)x - zero_G_x)/scale_x);
  Serial.print("\t");

  Serial.print(((float)y - zero_G_y)/scale_y);
  Serial.print("\t");

  Serial.print(((float)z - zero_G_z)/scale_z);
  Serial.print("\n");  // delay before next reading:
  delay(sampleDelay);
}

Here are some sample readings I get from this calibration with the accelerometer in each of the six test positions:

X up:     1.02    0.02    0.00
X down:  -0.99    0.05   -0.02
Y up:     0.08    1.01   -0.09
Y down:  -0.02   -0.97    0.02
Z up:    -0.02    0.16    1.00
Z down:   0.08   -0.08   -0.99

This is OK, but not great.  We get clear readings close to +/- 1G on the axis of interest and we get something “smaller” on the other axes. Obviously I was not laying the thing down straight, and this caused calibration errors.  I also made no effort to account for noise in the input signals.

Both of these problems will be solved through software in the next post when we look at statistics, least-squares, and the Gauss-Newton method.

I’ll do a thorough error analysis of all of these methods later, but for now I’ll give six-point calibration these grades.

Six-point Calibration Overall Grades:  Accuracy – Bad       Easiness – OK

Advertisements
This entry was posted in Electronics and tagged , , , , . Bookmark the permalink.

3 Responses to Accelerometer Calibration II: Simple Methods

  1. iamkaylee14 says:

    I find this very helpful 🙂 but i have a question.. Im kinda confused with the computation of the sensitivity.. how did it came up with that equation.. although it is precise when I tried it.. im just wondering how it came up with that 🙂 Thank you…

  2. hassam says:

    may be late but i can give you reply for others who really want to know about the formula 🙂

    infact it is sensitivity = acc_axis_value – mz —- eq 1
    where mz = (acc_axis_value + opp_acc_axis_value)/2 — eq 2
    put the eq2 into eq 1 and you will get
    sensitivity = (acc_axis_value – opp_acc_axis_value)/2

  3. Pingback: Using a BMA180 accelerometer as a high resolution tilt sensor | An Arduino based underwater sensor project

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s