This is the third post in a series.
- Simple Methods
- Least-Squares and Gauss Newton
- Streaming Gauss-Newton on an ATMEGA
- Error Analysis
In the last post we looked at some simple calibration methods and they didn’t seem to get us results that were better than the ones we got by just trusting the datasheet. There were two obvious problems:
- Our calibration methods assumed we had placed our accelerometer in perfect axial alignment each time. We were, um, less than perfect.
- We ignored the fact that there is noise in the sensor readings. Eyeballing it, this alone would give us about ~1% error.
We could address problem (1) by finding ways to place our device very carefully before taking measurements. We could address problem (2) by averaging our samples (and assuming that the average was a good estimate of the real values).
Instead, we will use some Mathematics to develop a calibration method that is robust to noise and is not dependent on careful sensor placement. This will give more accurate results even though it requires less care by the user.
Here is the idea. We’ve been assuming that there are just six numbers we need to find to get a good calibration: . (See the last post for details and definitions. Also note that if there is any skew we’ll need a more complicated model.) Now matter how we place our sensor before we take readings, we assume it is still and is detecting exactly 1G of acceleration in some direction.
So if we read value on the X-pin, on the Y-pin, and on the Z-pin we can look at the acceleration vector given by
If our measurements have no noise and our parameters are correct, the length of this vector should be exactly 1 no matter which way it is pointing. That is
To calibrate our model we just need to find parameters so that this equation is always true. Unfortunately
- With only one observation we get one equation in 6 unknowns. There will be infinitely many solutions in general. How do we pick one?
- With exactly six distinct observations we have 6 equations in 6 unknowns and should get a unique solution. The observations should be as different as possible so the solution isn’t error prone. But we know there is noise in our readings to this won’t be exact.
- With more than six observations we capture information about the noise in the data but we get more than 6 equations in 6 unknowns and cannot expect to find a solution.
It seems that the right thing to do is work with many observations. We won’t be able to solve our equations, but we can choose parameters that make the errors as small as possible.
Let’s presume we used the circuit we built last time, collected data for the 6-point calibration, and ended up with readings. Write and for the reading on the X-pin, Y-pin, and Z-pin in the -th sample. Then the error we get for sample is
Now we want to find parameters so that all of the are small. We expect some errors so we don’t want to over-penalize these, but large errors suggest significant problems and should be penalized heavily. It turns out that if we look at square errors, , they have just this sort of property and make the algorithms that follow simpler. So we will try to pick parameters so that the sum of square errors is small:
Why square errors? In statistics we always look at square errors first, even if it isn’t always right. In this case I believe using square errors are both reasonable and convenient even if there isn’t a clear reason we shouldn’t use some other convex function of the error. And you’ll see we get pretty good results.
But I digress.
Now we can formally state the problem we are trying to solve.
Least Squares Problem. Given samples for , find parameters such that the penalty function
is as small as possible.
This is a classic nonlinear least-squares problem and it can be solved numerically using the Gauss-Newton method. One day I may write more about these things — it is a beautiful algorithm and we’ll need to adjust it to work with the limited memory of an ATMEGA chip — but for today I will just offer you my code.
Performing Gauss-Newton to Solve the Least-squares Problem
I use the following workflow to calibrate an ADXL335 using the Gauss-Newton method. I do things on windows 7, but it is all implemented with Python and Octave – free tools that are available many platforms. Small adjustments may be needed. (You can download Python here and download octave here.)
- Pick a directory to store data and code. I’ll call this C:\your\calibration\directory\.
- Collect data using the pushbutton circuit described in the last post. Put the sensor in a variety of positions (the 6 positions you used for 6-point calibration would be a good start). Store the serial output in a text file. I’ll assume it is stored in C:\your\calibration\directory\calData.txt.
- download the Python script octaveprep.py and place it in C:\your\calibration\directory\, then run it, passing the data file name as an argument. At the windows command line (already in the right directory) I typeC:\Python27\python.exe octaveprep.py calData.txt
This will write a new file, an octave script called calTest_octave.m. This script will be used to perform the real data analysis.
- Download the file gaussnewton.mand place it in C:\your\calibration\directory\. Open octave and load the script by typingsource “C:\\your\\calibration\\directory\\calData_octave.m”
This will compute a 6-number array beta which holds all of our calibration parameters. The correspondence is as follows:
beta = 5.1448e+002 5.0231e+002 5.1665e+002 9.5485e-003 9.4631e-003 9.6936e-003
which means that and . Sticking these parameters in the unit conversion loop I used to evaluate the 6-point calibration method in the last post, I get readings
X-up 0.9980144 0.0254738 0.0615826 X-down -0.9976190 0.0254738 0.0131148 Y-up 0.0145204 1.0001717 -0.0062723 Y-down -0.0332220 -0.9965395 0.0131148 Z-up -0.0618675 0.0538631 0.9921641 Z-down 0.0336174 -0.0123785 -1.0047088
We did not get a bunch of beautiful readings like “0 0 1”, “0 0 -1”, etc. because we still had sloppy placement going in. But if you compute the magnitudes of each of these vectors you will see that they range from 0.99555 to 1.00535. They are almost all within one half of one percent of the perfect 1G reading!
If you have trouble getting these scripts to work for you please leave a comment and let me know.
Now we have a calibration that uses exactly the same measurements that our 6-point calibration system did but that has very accurate results. If we automated the workflow described above, we’d have an easy accurate calibration method.
Better Accuracy Through Better Samples
This is starting to look pretty good, but we can still do better. For the last example I just used data collected from six basic positions: Z-up, Z-down, Y-up, Y-down, X-up, and X-down. We can get better results if we sample different directions, but wich new directions should we add?
You can think of these positions we already sampled as the ones we would get if we stuck our accelerometer on each of the faces of a cube. We want to get new directions that are “as different as possible”. To do this, just chop off each of the corners of the cube to get 8 new faces and set the accelerometer on each of these new faces too.
I don’t really use a truncated cube to take measurements. Actually I tape my accelerometer to a ruler and clamp it in a third hand from my soldering bench to position it securely.
After 30 minutes of fiddling, I got a stream of slightly noisy data from each of these 14 positions. I calibrate and get the following parameters:
beta = 5.1409e+002 5.0231e+002 5.1662e+002 9.5632e-003 9.4572e-003 9.6777e-003
These are very close to the parameters we got from the 6-position data. In fact, on those 6 positions these parameters do not give better results (they can’t because we had chosen the best parameters for those 6 positions). But for the new positions and for positions scattered around the sphere we see significantly better accuracy. I’ll show you the details when we talk about error analysis later.
Six-point Calibration Overall Grades: Accuracy – Great Easiness – OK
We could go through this process every time we get a new sensor, store the parameters in EEPROM, and carry on. But I want to be able to calibrate a device on the slopes or on the lift and I won’t be toting around my laptop and Octave installation. If you looked through some of that code you would see that we were dealing with some very large matrices, ones that would quickly overrun the memory on an Arduino. In the next post we’ll see how to overcome these problems and implement the Gauss-Newton method directly on the Arduino.