# Accelerometer Calculation Question

I've been playing with a IMU board(ADXL320/ADXRS401 IMU datasheet) from sparkfun.com. This consists of both a gyroscope, ADXRS401 and a Accelerometer, ADXL320.  So far I've been playing with the accelerometer readings, trying to understand how to use it for measuring tilt etc.

The accelerometer will output analog volt values based on the orientation of the x and y axis of the board. For example if we look at the Y-axis it will output, according to the datasheet, 2.5V when laying flat on a table - perpendicular to the earth's surface. The output will change 312mV/g change. Lets say we'd like to figure out the angle of the y-axis at a given voltage. We should be able to calculate g of a tilt based on the voltage change, and angle based on g. It's easiest to explain this by doing an example, but first some constants

• Zero(0) g of the y-axis is equal to 2.41V on my board according to the measurments i did with a multimeter during testing.
• Acceleration is 312mV/g.

So if we say that y-axis returns 2.61V and we'd like to find the degree for that volt. We remember our zero g point and calculations go like this:

`2.61 - 2.41  = 0.2+ volt` proportional to zero g.

Now we can calculate g based on those numbers since we know that acceleration is 312mV/g at 5v:

`0.2 / 0.312 = 0.641g`

Since we have found g we can now figure out the angle using this formula:

`     sin^(-1)(0.641) = 0.6958 radians `

All that is left is to convert radians into an angle.

`(180 / PI) * radian = 39.8 degrees`

These calculations look fine at first, but there are a few flaws. One, I use volts to do the calculation. The y-axis readings is returned as a ADC value of 10-bits(0-1024) to my Arduino board. This means we first have to convert that back to volt. Secondly, and more important, The results are not that good. Whenever the device tilts over 80 degrees it returns just 0.00. for example. Also the results vary abit, but i guess that should be possible fix with adding something to reduce the noise on the +5v input and output. What I'm looking for is a better way to do the calculations/ or some tips & tricks on this kind of device.

Btw, my Arduino C code looks something like this:

`    float volt = (y/1023.0) * 5;  // y is the ADC value from analogRead(). *5 is 5volt     float ydiff = volt - zeroG;   // zeroG is constant of 2.41    float yg = ydiff/0.312;    float radian = asin(yg);    float degree = (180 / PI) * radian;`

o/

## Comment viewing options

No problem Simon, it was an interesting task and I was happy to help. A blog posting is a very good idea; these sensors are extremely useful and it would be nice if others could benefit from what we've figured out so far.

I'm glad the readings have somewhat stabilised, that was one of my concerns regarding long-term accuracy. I haven't really looked into the self-test functions, but the +2.5V output can be used to check the precision of your ADC. If you feed the +2.5V signal into your ADC you can then store the digital result as a constant equal to +2.5V input. Since you've already got a regulated +3.3V AREF input the precision +2.5V reference isn't so important, since the +3.3V output is very reliable compared to, say, a battery input which will slowly drop in voltage.

Probably the most useful thing about the gyro is that it can give you more reliable angular velocity and angular acceleration values than the accelerometers can. To get angular velocity from the accelerometers (if the rotation is around this centre of the sensor package) you have to take two readings for the angle, then calculate the difference between them and divide by the time that elapsed between the readings. The gyro of course will tell you immediately, which means you can find the angular velocity at least twice as fast.
To find angular acceleration you need two samples for angular velocity, or a minimum of three samples for angular displacement. Likewise to find angular jerk you need three samples for angular velocity, or four samples for angular displacement.
Using the additional information from the gyro allows you to implement a higher order control function in your programming, which isn't a big deal when things are happening slowly, but for a balancing robot for example it can be the difference between a fairly stable platform and an extremely stable platform.

I'll be interested to see where you take this to next =D

hehe,

It's strange how complex I sometimes make a problem for myself when it indeed is very simple.  I overlooked the last constant conversion this morning and of course got the wrong results. Now the sensor is very precise and the readings are less jumpy then before. In the datasheet for the IMU board it is stated that decreasing the ADC window will increase performance within the given volt range - and that is indeed very true.

I still dont understand what the internal 2.5v precision output is for, how to use the gyro and the self tests, but so far I feel i've come a long way with this little beast :) I also think the gyro will be easier to use - but I'm abit uncertain when it comes to the time aspect of the calculation. And I guess I have to confess to that I dont know why i need the gyro to measure tilt. Lets say I want to make something balance on one axis. Reading this axis using the accelerometer would give me the angle and I would be able to adjust in the direction it is needed based on the tilt. I've read that we need both the gyro and accelerometer to get a reliable result - so we example can balance something - and that the accelerometer is used to adjust the gyro's drift or vica verca, but it's still not completly clear to me how.

I have to say thanks again to you telefox. You have given me some very good answers that have helped me alot. Without it I dont know how far I'd come at this point. Maybe I should sum it all up in a blog for future reference.

Heya, looks like things are starting to shape up nicely. If you change the AREF to +3.3V you get the resolution increase immediately, but you have to adjust your code wherever there's a constant value based on the voltage levels.
The only changes I can think of are:
• The nominal zeroG value is now (2.5V/3.3V)*1024=776 instead of (2.5V/5V)*1024=512.
• Similarly, the ±1g voltage of 0.312V is now (0.312V/3.3V)*1024=97.
Apart from that, the rest of the code should be fine as it is.

Hi again.

I'm not sure if did understand you correctly, but I think i have. I noticed a new error in my code which did couse null return after a certain degree. asin() was was fed with the g value for the axis, which at some point was over 1 or -1. This will return NULL. I've tried to adjust my code after your comment and i've also tried to correct the error with the asin() parameter. This is how it looks. What i tried to do was just to adjust the numbers to the zero_g for the axis at an earlier point in the calculation. The results are actually pretty good.

`    float y_diff = y - 512;    float y_g = (y_diff / 64.0);    y_g -= y_zero_g; // Adjusting according to zero g for the axis.     if(y_g > 1.0)        y_g = 0.999999;    if(y_g < -1.0)        y_g = -0.999999;    float y_radians = asin(y_g);    float y_degree = (180.0 / PI) * y_radians;`

I did try to adjust my AREF using a 3.3v external source(I only had a HT7133A availible), but I wasnt sure how to adjust my code accordingly to the volt change.

I updated my code here as well for the complete overview

How have you connected your potentiometers? Depending on their values they may be causing strain on the sensor, causing inaccurate readings.

Maybe you should grab a spirit level and check your sensor readings again, +2.41V @ 0° is 16.77° off from the normal +2.5V value, which is quite a lot.

Using 2.5V won't give you the wrong results if you correct the angle afterwards, that's where the sensorDegree-zeroDegree bit comes into it. Let's look at an example where the zeroG voltage is as you have it recorded, which means that the sensor is making an angle of asin=((2.41-2.5)/0.312)=-16.77° to the direction of gravity when the sensor package is flat on a level surface. So our two possible calibration values are zeroG=2.41V or zeroDegree=-16.77°.
Now we rotate the sensor package by 60° in the direction of decreasing Y-axis output voltage, or -60°. The sensor should give us an output of 2.5V+(sin(-60°)*0.312V)=+2.230V, but we know that the sensor was not perfectly aligned in the first place, so what we actually get is 2.5V+(sin(-60°-16.77°)*0.312V)=+2.196V.
Now we'll apply the zeroG voltage correction: 2.196V-2.41V=-0.214V, asin(-0.214/0.312)=-43.31°. This obviously didn't work so well, sometimes the errors will be worse, sometimes not so bad.
This time we'll use the zeroDegree angle correction, using +2.5V as the voltage reference: 2.196V-2.5V=-0.304V, asin(-0.304/0.312)=-77.00°, -77.00°-(-16.77°)=60.23°. Well, it's still not perfect thanks to rounding errors, but it's pretty accurate anyway. More importantly, the errors will always just be tiny rounding errors, nothing significant.
You can prove that the zeroG voltage correction method doesn't work by using trig and calculus, but I won't go that far into it unless you're really curious =D

Unfortunately it looks like you can only adjust the upper ADC reference voltage; the ATmega168 datasheet gives you all the details. You can change the upper limit from +5V to and internal +1.1V reference or to an externally controlled reference connected to one of the pins. If you dropped the accelerometer output by ~2V you could set the upper ADC reference to the +1.1V internal reference, but this may be more hassle than it's worth. The other option to increase your resolution is to apply a voltage of about +3V to the AREF pin, which would give you ~65% better resolution. A small linear regulator or zener diode reference would be nice and stable.

Hi, just a short reply now. The weather is to nice to sit inside - and my head has to much poisen from last night to think straight :)

I'm not sure what the zeroG for y-axis and x-axis is, but with two potentiometers Its easier to just adjust it manually when playing around. The x-Axis zeroG will also change depending on which way the accelerometer is turned (I havent figured out the details yet, but the datasheet said so). So using just 2.5v as ZeroG value will give wrong results in the end.

I've also added a 10uF cap on the output and a 0.1uF cap on +5v inout to try to reduce the noise, but i cant say it did change much. The readings are still very jumpy when they go over a certain level(degree) and I'm starting to think that it's just the way this instrument works.

When it comes to the code suggestion you added I must say it didnt make too much sense to me why you wanted to do it in that way. The last degree calculation I didnt see the point of. So sorry for that. I'm just not sure what you're thougth behind it was.  I'm going to add my code so you can see how it looks. But this is still just the testing for the board, so nothing optimized, trying to make it as easy as possible so I dont get lost in the numbers.

I'm using the latest Arduino board, but I want able to set the REF manually in a way that made any sense to me. I didnt understand how i could set it to a specific volt. Or if i should use the 2.5v out pin on the Accelerometer to set the REF on the arduino. I'll try again later, but am open for suggestions. Thanks for all the help so far.

Hey Simon, glad this stuff is of use to you.

I'm starting to think the zeroG reading of 2.41V that you've got is indicative of some sort of bias error, especially with the uneven problem reading large tilt angles. Hopefully the readings will even out with some noise filtering on the power and signal lines. The accelerometer uses high frequency square waves applied to a special capacitor, so it'll be prone to interference.

Check out http://www.arduino.cc/en/Reference/AnalogReference for some basic details on how to tweak the ADC references. How you actually pull it off depends on your board setup; what are you using exactly?

To simplify the calibration and make the programming more robust for future mounting, you could change the 'zeroG' value to 2.5V or 512, and then have a separate offset angle used for calibration, ie:

float yg = y-512;
float degree = sensorDegree-zeroDegree;

'zeroDegree' can be calculated during the calibration routine just by running the first 3 lines above, and storing zeroDegree=sensorDegree. This actually agrees better with the trig than adjusting zeroG directly.

You're totally right about the ADC offset, I'm not even sure how I got it wrong now. I'm sure I did it as: 5V/1024units=4.88mV/unit, 0.312V/(4.88mV/unit)=63.9, but at some point I must've multiplied by 5V for no apparent reason. At least one of us is paying attention =3

Hi, TeleFox. Just wanted to say, I find you're answers very good - and it was just the kind of information I was looking for. So you dont have to excuse you're self for anything. And by the way, The WallofText  phenomenon is quiet common in my house as well :)

I havent had the time to do any more testing, but have a couple of hours now. So maybe I'll come up with something.

Y-axis measurements there are still a few options available that might help. After looking at the datasheet properly I realise that the X and Y orientations are different to what I thought, so you won't be able to get the Z-axis data by combining them. The 2 accelerometers will just have to act independently.

I was not able to figure out how to combine the different readings myself, but expected to find a solution after a while, so i didnt comment after your first reply. But I guess i can just forget it now.

If the output signal is jittery and unstable you can drop a filtering capacitor (just a few μF at most) between the input line and ground to deadened high-frequency noise and erratic fluctuations. If your sampling frequency is also quite high you can take several successive measurements and then average them to smooth out your data.

I have never tried to filter out noise before so i'll just do some experiencing(I'm rather new to electronics) But i remember something about it in the datasheet so i'll just follow their suggestions. And yes, the sensor readings are jumping. I actually set up the processing graphic representation with a average of 20-needle and a needle which shows all the readings. The average was of course less "jumpy", but it will suffer with a small delay compared to the synchronized representation.

There probably isn't much you can do about that dead spot at 80°+, it's likely due to the lack of sensitivity caused by the sensing element coming close to being totally out of alignment with the gravity vector.

If it only was so easy. Still talking about what I get back from my y-axis; when i turn it more then 35-40* on the positive side i starts getting really unreliable, but this only happens after 70-80* on the minus side. First i thought my math was wrong so, but this doesnt make sense with the difference on which way I turn the y-axis.

What sort of power supply is the sensor running on? There might also be some added noise coming from that which you can filter out.

Atm, i've just been running it of my arduino +5v power. But as noted earlier I think I'll try to add some noise reducing caps.

Also what are the ADC limits references set to? I'm guessing 0V and 5V since you're running an Arduino, but I'm not sure what your options are. If possible, you can narrow your limits to something like +2V and +3V, which will give you 5X the resolution compared to what you currently have. This only applies if you just want to look at tilt though; acceleration a bit stronger than gravity will go over the ADC limits in this case.

I noticed these comments in the datasheet as well, but have never tried to do so - or wouldnt know how to do it. How do I decrease the ADC input window?

Are you using the 2.5V output from the sensor board to calibrate your ADC? Probably something worth looking into.

Nope, not done so yet, but will look at it now.

What I did for testing was add a potentiometer to adjust the zeroG for x and y. For the y-axis zeroG in ADC value(10-bit) is about 493-500 somewhere. Eventually It's probably better to create a calibration routine, but that would relay on the robot standing perfectly straight, wouldnt it?

When it comes to the code example; I have tried what you posted already - and I understand you're logic. .312 * 1024 = ~319.4. But the end result isnt the same - and thats why I just converted the 10-bits values back to volt in the first place. When i use the above calculation; at 90* the angle will show 12*, at 45* it shows 6*, and so on. I've experienced with the numbers, but with no luck. I guess my math isnt good enough to spot the mistake, but i hope i find the error soon enough couse I find it rather stupid :p

update: Think I found the solution to the ADC conversion issue. To calculate volt based on 10-bit figure we use (ADC value / bit) * Volt. For example. If our ADC value is 500 and volt is +5v on a 10-bit system it would be like this; (500 / 1023) * 5 = 2.44v. So to reverse it we of course have to divide by Volt as well, which both of us forgot. The above equation for calulating g should then look like this; (.312 * 1023) / 5 = 63.89 and not 319

Sorry for the WALLOFTEXT™, bit of a habit of mine. As for cleaning up just your Y-axis measurements there are still a few options available that might help. After looking at the datasheet properly I realise that the X and Y orientations are different to what I thought, so you won't be able to get the Z-axis data by combining them. The 2 accelerometers will just have to act independently.

If the output signal is jittery and unstable you can drop a filtering capacitor (just a few μF at most) between the input line and ground to deadened high-frequency noise and erratic fluctuations. If your sampling frequency is also quite high you can take several successive measurements and then average them to smooth out your data. There probably isn't much you can do about that dead spot at 80°+, it's likely due to the lack of sensitivity caused by the sensing element coming close to being totally out of alignment with the gravity vector.

What sort of power supply is the sensor running on? There might also be some added noise coming from that which you can filter out.

Also what are the ADC limits references set to? I'm guessing 0V and 5V since you're running an Arduino, but I'm not sure what your options are. If possible, you can narrow your limits to something like +2V and +3V, which will give you 5X the resolution compared to what you currently have. This only applies if you just want to look at tilt though; acceleration a bit stronger than gravity will go over the ADC limits in this case.

Are you using the 2.5V output from the sensor board to calibrate your ADC? Probably something worth looking into.

Instead of relying on a previously measured value for 'zeroG' you could add a 'calibrate' button, or a little routine at the start of your code that causes the Arduino to read in the current values for X and Y and store them as the neutral position values. This would be useful if you install the sensors in a bot or something where you can't guarantee that when the bot is on flat ground the sensor package will line up perfectly with the ground. It also helps reduce any offset errors caused by low batteries or varied temperatures. With zeroG already stored as a 10bit value, you could change your code to something like this:

float yg = y-zeroG;