Accelerometers are invariably surface mount devices, making prototyping difficult, however, there are many, so called, breakout boards that contain the accelerometer chip and any surrounding circuitry required. These are normally brought to a more useable format, such as 0.1" (

*2.54mm*) headers.

This is the type of board I used, and it was purchased from ebay for less that I could purchase the actual accelerometer chip itself. The board is made by Sure electronics and is shown below, along with it's axis orientation:

The datasheet for the MMA7260 accelerometer chip can be downloaded from here:

**MMA7260QT datasheet.pdf**.

The PCB is a little misleading, in that it has two rows of 0.1" headers, one row contains the texts that pertain to a serial SPI device (

*J4*), and the other row contains the texts that pertain to the accelerometer outputs (

*J1*). The serial text row (

*left hand side from the above illustration, named J4*) pins are not connected to any serial conversion device, and the MMA7260 only produces analogue outputs. The only connected pin is the 5 volt one, which connects to a regulator chip.

As the Amicus18 operates at 3.3 Volts, and so does the MMA7260 device, this row can be totally ignored, as most pins serve no purpose in any case, and all interfacing is performed with header J1.

As usual, I used the trusty companion shield to implement the interface, and it's layout is shown below:

**Click here for a larger image**

A top down layout is shown below for more clarity:

**Click here for a larger image**

The MMA7260 has analogue outputs that vary in voltage according to the gravity force measured. Unusually, it also has four selections of force sensitivity through the use of it's SEL1 and SEL2 inputs:

SEL2 Input |
SEL1 Input |
g-Range |
Sensitivity |

0 | 0 | 1.5g | 800mV per g |

0 | 1 | 2g | 600mV per g |

1 | 0 | 4g | 300mV per g |

1 | 1 | 6g | 200mV per g |

The analogue outputs for X, Y and Z can easily be interfaced to three of the microcontroller's ADC peripheral inputs, each producing a value from 0 to 1023 depending on the movement of a particular axis. This can then be converted to a voltage value.

That is exactly what the program below does. However, it also incorporates a median filter so it can smooth out the ADC values somewhat. The program can also be downloaded from here:

**MMA7260_Accelerometer_Simple.bas**.

Code:

' ' MMA7260 Accelerometer interface ' ' Display the X, Y and Z axis values on the serial terminal ' According to the position of the accelerometer ' ' Accelerometer connections ' ' X output connects to AN0 (PORTA.0) ' Y output connects to AN1 (PORTA.1) ' Z output connects to AN2 (PORTA.2) ' Sel1 input connects to PORTB.5 ' Sel2 input connects to PORTB.0 'Include"Amicus18.inc"' Configure the compiler for a PIC18F25K20 at 64MHz. i.e. An Amicus18 boardInclude"Amicus18_ADC.inc"' Load the PIC18F25K20 ADC macro's into the programSymboltSel1 = PORTB.5' Accelerometer Sel1 pin connectionSymboltSel2 = PORTB.0' Accelerometer Sel2 pin connectionSymbolcQuanta = 3.3 / 1023' ADC to voltage conversion (VDD / ADC resolution)DimwXoutput AsWord' Holds the filtered 10-Bit result from the ADCDimwYoutputAs Word' Holds the filtered 10-Bit result from the ADCDimwZoutputAs Word' Holds the filtered 10-Bit result from the ADCDimfXVoltageAs Float' Holds the voltage output from the channelDimfYVoltageAs Float' Holds the voltage output from the channelDimfZVoltageAs Float' Holds the voltage output from the channel' ' Variables used by the bubble sort routine 'DimbSortIndexAs ByteDimtSwapOccuredAs BitDimwSwapTempAs WordDimwSortResultAswSwapTempSymbolcSamplesToTake = 9' The amount of samples to take for the bubble sortDimwSampleArray[cSamplesToTake]As Word'--------------------------------------------------------------------GoToMain' Jump over the subroutines '--------------------------------------------------------------------' ' Select the sensitivity of the MMA7620 '$defineSet_1_5g()LowtSel1 :LowtSel2$defineSet_2g()HightSel1 :LowtSel2$defineSet_4g()LowtSel1 :HightSel2$defineSet_6g()HightSel1 :HightSel2'-------------------------------------------------------------------- ' Implement a bubble sorting median filter ' ' Input : wSampleArray holds the values to sort ' Output : wSortResult holds the median of the values ' Notes : None 'MedianFilter:RepeattSwapOccured = 0' Clear flag that indicates swap.bSortIndex = 0Repeat' For each cell of the array...IfwSampleArray[bSortIndex] > wSampleArray[bSortIndex + 1]Then' Move larger values up.wSwapTemp = wSampleArray[bSortIndex]' ..by swapping them.wSampleArray[bSortIndex] = wSampleArray[bSortIndex + 1] wSampleArray[bSortIndex + 1] = wSwapTemp tSwapOccured = 1' Set bit if swap occurred.EndIfIncbSortIndexUntilbSortIndex > cSamplesToTake' Check next cell of the array.UntiltSwapOccured = 0' Keep sorting until no more swaps.wSortResult = wSampleArray[cSamplesToTake / 2]' Extract the middle (median) valueReturn'-------------------------------------------------------------------- ' Read the X output from the accelerometer (Forward-Backward) ' ' Input : None ' Output : wXoutput holds the filtered 10-bit result from the ADC ' fXVoltage holds the voltage output from the channel ' Notes : NoneReadChannel_X:ForbSortIndex = (cSamplesToTake - 1)To0Step-1' Create a loop for the amount of samples to takewSampleArray[bSortIndex] =ReadADC(ADC_CH0)' Read the channel into the filter arrayDelayUS2' A small delay to allow the ADC's capacitor to re-chargeNext' Close the loopGoSubMedianFilter' Perform a median filter on the sampleswXoutput = wSortResult' Transfer the result of the filterfXVoltage = wXoutput * cQuanta' Convert to a voltageReturn'-------------------------------------------------------------------- ' Read the Y output from the accelerometer (Side to Side) ' ' Input : None ' Output : wYoutput holds the filtered 10-bit result from the ADC ' fYVoltage holds the voltage output from the channel ' Notes : NoneReadChannel_Y:ForbSortIndex = (cSamplesToTake - 1)To0Step-1' Create a loop for the amount of samples to takewSampleArray[bSortIndex] =ReadADC(ADC_CH1)' Read the channel into the filter arrayDelayUS2' A small delay to allow the ADC's capacitor to re-chargeNext' Close the loopGoSubMedianFilter' Perform a median filter on the sampleswYoutput = wSortResult' Transfer the result of the filterfYVoltage = wYoutput * cQuanta' Convert to a voltageReturn'-------------------------------------------------------------------- ' Read the Z output from the accelerometer (Up and Down) ' ' Input : None ' Output : wZoutput holds the filtered 10-bit result from the ADC ' fZVoltage holds the voltage output from the channel ' Notes : NoneReadChannel_Z:ForbSortIndex = (cSamplesToTake - 1)To0Step-1' Create a loop for the amount of samples to takewSampleArray[bSortIndex] =ReadADC(ADC_CH2)' Read the channel into the filter arrayDelayUS2' A small delay to allow the ADC's capacitor to re-chargeNext' Close the loopGoSubMedianFilter' Perform a median filter on the sampleswZoutput = wSortResult' Transfer the result of the filterfZVoltage = wZoutput * cQuanta' Convert to a voltageReturn'-------------------------------------------------------------------- ' The main program loop starts hereMain:Set_1_5g()' Set accelerometer sensitivity' ' Open the ADC and make AN0, AN1, and AN2 analogue inputs 'OpenADC(ADC_FOSC_64 & ADC_RIGHT_JUST & ADC_2_TAD, ADC_REF_VDD_VSS, ADC_1ANA & ADC_2ANA & ADC_3ANA)' ' Display the accelerometer values on the serial terminal 'While' Create an infinite loopGoSubReadChannel_X' Read the X channelGoSubReadChannel_Y' Read the Y channelGoSubReadChannel_Z' Read the Z channelHRSOut1' Clear the serial terminal's windowHRSOut"X axis (Forward-Backward): ",DecwXoutput, " : ",Dec2fXVoltage, " Volts\r"' Display the X channel's valueHRSOut"Y axis (Side to Side) : ",DecwYoutput, " : ",Dec2fYVoltage, " Volts\r"' Display the Y channel's valueHRSOut"Z axis (Up and Down) : ",DecwZoutput, " : ",Dec2fZVoltage, " Volts\r"' Display the Z channel's valueDelayMS300Wend' Do it forever

The above code will transmit the raw ADC values as well as the voltage values to the serial terminal, as shown below:

We could leave it at that, as we have enough information for a lot of applications, however, we still haven't retrieved the actual g force measured by the device, so we'll continue.

**Measuring g forces and inclinations.**

When thinking about accelerometers it is often useful to image a cube with a ball inside it:

If we take this box to a location with no gravitation fields, or for that matter with no other fields that might affect the ball's position, the ball will simply float in the middle of the box. You can imagine the box as a space craft orbiting around the planet where everything is in a weightless state . From the illustration above you can see that we assign to each axis a pair of walls (

*the Y+ wall has been removed so we can see inside the box*).

Imagine that each wall is pressure sensitive. If we move the box to the left (we accelerate it with acceleration 1g = 9.8m/s^2), the ball will hit the wall X-. We then measure the pressure force that the ball applies to the wall and output a value of -1g on the X axis:

Note that the accelerometer will actually detect a force that is directed in the opposite direction from the acceleration vector. This force is often named Inertial Force or Fictitious Force . One thing that should be learn from this is that an accelerometer measures acceleration indirectly through a force that is applied to one of it's walls (

*it may be a spring in our box analogy, or semiconductor tie in a real accelerometer*). This force can be caused by the acceleration , but as we'll see in the next example it is not always caused by acceleration.

If we take our model and put it on Earth, the ball will fall on the Z- wall and will apply a force of 1g on the bottom wall, as shown in the illustration below:

In this case the box isn't moving but we still get a reading of -1g on the Z axis. The pressure that the ball has applied on the wall was caused by a gravitation force. In theory, it could be a different type of force; for example, if you imagine that our ball is metallic, placing a magnet next to the box could move the ball so it hits another wall. This illustrates that in essence, an accelerometer measures force not acceleration. It just happens that acceleration causes an inertial force that is captured by the force detection mechanism of the accelerometer.

So far we've looked at the accelerometer output on a single axis and this is all that's available with a single axis accelerometer. The real value of triaxial accelerometers comes from the fact that they can detect inertial forces on all three axes. Let's go back to our box model, and rotate it 45 degrees to the right. The ball will touch two walls now: Z- and X- as shown in the illustration below:

The values of 0.71 are not arbitrary, they are actually an approximation for sqr(1/2). This will become more clear as we introduce our next model for the accelerometer.

In the previous model we've fixed the gravitation force and rotated our imaginary box. In the last two examples we've looked at the output in two different box positions, while the force vector remained constant. While this was useful in understanding how the accelerometer interacts with outside forces, it's more practical to perform calculations if we fix the coordinate system to the axes of the accelerometer and imagine that the force vector rotates around us.

Looking at the illustration above, imagine that each axis in the model is perpendicular to the respective faces of the box in the previous illustrations. The vector R is the force vector that the accelerometer is measuring (

*it could be either the gravitation force or the inertial force from the examples above or a combination of both*). Rx, Ry, Rz are a projection of the R vector on the X, Y, and Z axes. Note the following relation:

*Equation 1.*

*R^2 = Rx^2 + Ry^2 + Rz^2*

This is essentially the equivalent of the Pythagorean theorem, but in 3D.

Remember that the values of sqr(1/2) ~ 0.71 are not random. If they are inserted into the formula above, after recalling that our gravitation force was 1 g we can verify that:

*1^2 = (-sqr(1/2) )^2 + 0 ^2 + (-sqr(1/2))^2*

simply by substituting R=1, Rx = -sqr(1/2), Ry = 0 , Rz = -sqr(1/2) in equation 1

The values Rx, Ry, and Rz are actually linearly related to the values that a real accelerometer will output and are in a form that can be used for performing various calculations.

We’ll move on with a simple example. On my desk, the 10-bit ADC of the Amicus18 board gave the following values for the three accelerometer channels (

*axes*), when the board was tilted to the right and forward:

*AdcRx = 513 485*

AdcRy = 541 480

AdcRz = 687 502

AdcRy = 541 480

AdcRz = 687 502

In order to convert a 10-bit ADC value to a voltage we use the following formula:

VoltsRx = AdcRx * 3.3 / 1023

VoltsRx = AdcRx * 3.3 / 1023

Applying this formula to all three channels we get:

*VoltsRx = 513 * (3.3V / 1023) = ~1.65V (we round all results to 2 decimal points)*

VoltsRy = 540 * (3.3V / 1023) = ~1.74V

VoltsRz = 687 * (3.3V / 1023) = ~2.21V

VoltsRy = 540 * (3.3V / 1023) = ~1.74V

VoltsRz = 687 * (3.3V / 1023) = ~2.21V

The accelerometer has a zero-g voltage level, which can be found in the datasheet, or better still use the “MMA7260_Accelerometer_Simple.bas” program and note the values from the serial terminal when the companion shield is sitting horizontal on a level desk. these are the voltages that correspond to 0g. The companion shield will need to be moved on to it’s right side in order to take the 0g reading of the Z axis correctly.

In experiments, the 0g values for a sensitivity setting of 1.5 were:

*VoltsRx = 1.57V*

VoltsRy = 1.55V

VoltsRz = 1.65V

VoltsRy = 1.55V

VoltsRz = 1.65V

To get a signed voltage value we need to calculate the shift from these levels. For example, if our 0g voltage level is 1.57V. We calculate the voltage shifts from the zero-g voltage as follows:

*DeltaVoltsRx = 1.65V - 1.57V = 0.08V*

DeltaVoltsRy = 1.74V - 1.55V = 0.19V

DeltaVoltsRz = 2.21V – 1.65V = 0.56V

DeltaVoltsRy = 1.74V - 1.55V = 0.19V

DeltaVoltsRz = 2.21V – 1.65V = 0.56V

We now have our accelerometer readings in Volts, but it's still not in g (

*9.8 m/s^2*), to do the final conversion we apply the accelerometer’s sensitivity, usually expressed in mV/g. Sensitivity values can be found in the accelerometer's datasheet, and are selectable with the MMA7260, i.e. 800mV/g, 600mV/g, 300mV/g, and 200mV/g.

To get the final force values expressed in g we use the following formula:

*Rx = DeltaVoltsRx / Sensitivity*

*Rx = 0.08V / 0.6V/g = ~0.13g*

Ry = 0.19V / 0.6V/g = ~0.31g

Rz = 0.56V / 0.6V/g = ~0.93g

Ry = 0.19V / 0.6V/g = ~0.31g

Rz = 0.56V / 0.6V/g = ~0.93g

We now have all three values that define our inertial force vector. If we need to calculate inclination of the device relative to the ground we can calculate the angle between these vector and the Z axis.

If we were interested in a per-axis direction of inclination we could split this result into two components. i.e. inclination on the X and Y axis that can be calculated as the angle between gravitation vector and X / Y axes.

Calculating these angles is simpler than you may imagine, now that we’ve calculated the values for Rx, Ry and Rz.

The angles that we’re interested in are the angles between the X, Y, and Z axes and the force vector R. We'll define these angles as Axr, Ayr, Azr. The illustration above shows a right angle triangle formed by R and Rx:

*cos(Axr) = Rx / R*

and similarly :

*cos(Ayr) = Ry / R*

cos(Azr) = Rz / R

cos(Azr) = Rz / R

We can deduce from the earlier

*equation 1*that R = sqr( Rx^2 + Ry^2 + Rz^2). So we can now calculate the angles by using the compiler's acos() function (

*the inverse cos() function*):

*Axr = acos(Rx/R)*

Ayr = acos(Ry/R)

Azr = acos(Rz/R)

Ayr = acos(Ry/R)

Azr = acos(Rz/R)

The angles are still in radians, so a conversion to degrees is called for. This is simply a multiplication by the constant 180 / 3.14:

*Axd = Axr * (180 / 3.14)*

*Ayd = Ayr * (180 / 3.14)*

Azd = Azr * (180 / 3.14)

Azd = Azr * (180 / 3.14)

The above theory is made practical in the program listed below. It will display the g forces and the angles on the serial terminal. The program can also be downloaded from here:

**MMA7260_Accelerometer_Gravity.bas**.

Code:

' ' MMA7260 Accelerometer interface ' ' Display the X, Y and Z axis g-force values and angles ' ' Accelerometer connections ' ' X output connects to AN0 (PORTA.0) ' Y output connects to AN1 (PORTA.1) ' Z output connects to AN2 (PORTA.2) ' Sel1 input connects to PORTB.5 ' Sel2 input connects to PORTB.0 ' Sleep connects to VDD 'Include"Amicus18.inc"' Configure the compiler for a PIC18F25K20 at 64MHz. i.e. An Amicus18 boardInclude"Amicus18_ADC.inc"' Load the PIC18F25K20 ADC macro's into the programSymboltSel1 = PORTB.5' Accelerometer's Sel1 pin connectionSymboltSel2 = PORTB.0' Accelerometer's Sel2 pin connectionSymbolcQuanta = 3.3 / 1023' ADC to voltage conversion (VDD / ADC resolution)SymbolcDegreeConversion = 180.0 / 3.14159265' Conversion from radians to degreesDimwXoutput AsWord' Holds the filtered 10-Bit result from the ADCDimwYoutputAs Word' Holds the filtered 10-Bit result from the ADCDimwZoutputAs Word' Holds the filtered 10-Bit result from the ADCDimfXVoltageAs Float' Holds the voltage output from the channelDimfYVoltageAs Float' Holds the voltage output from the channelDimfZVoltageAs Float' Holds the voltage output from the channelDimRAs Float' Holds the result of the multiple sqrDimRxAs Float' Holds the result of fGravity_X squaredDimRyAs Float' Holds the result of fGravity_Y squaredDimRzAs Float' Holds the result of fGravity_Z squaredDimbAngleXAs Byte' Holds the inclination angle of the X axisDimbAngleYAs Byte' Holds the inclination angle of the Y axisDimfGravity_XAs Float' Holds the result of the X axis g-force calculationDimfGravity_YAs Float' Holds the result of the Y axis g-force calculationDimfGravity_ZAs Float' Holds the result of the Z axis g-force calculationSymbolZero_G_X = 1.57' Zero g voltage level for X axis, as measuredSymbolZero_G_Y = 1.55' Zero g voltage level for Y axis, as measuredSymbolZero_G_Z = 1.65' Zero g voltage level for Z axis, as measured' ' Variables used by the bubble sort routine 'DimbSortIndexAs ByteDimtSwapOccuredAs BitDimwSwapTempAs WordDimwSortResultAswSwapTempSymbolcSamplesToTake = 19' The amount of samples to take for the bubble sortDimwSampleArray[cSamplesToTake]As Word'--------------------------------------------------------------------GoToMain' Jump over the subroutines '--------------------------------------------------------------------' Select the sensitivity of the MMA7620 ' Also creates a sensitivity constant for each g selection'$defineSet_1_5g()'SymbolcSensitivity = 0.8'LowtSel1'LowtSel2$defineSet_2g()'SymbolcSensitivity = 0.6'HightSel1'LowtSel2$defineSet_4g()'SymbolcSensitivity = 0.3'LowtSel1'HightSel2$defineSet_6g()'SymbolcSensitivity = 0.2'HightSel1'HightSel2'-------------------------------------------------------------------- ' Implement a bubble sorting median filter ' Input : wSampleArray holds the values to sort ' Output : wSortResult holds the median of the values ' Notes : None 'MedianFilter:RepeattSwapOccured = 0' Clear flag that indicates swap.bSortIndex = 0Repeat' For each cell of the array...IfwSampleArray[bSortIndex] > wSampleArray[bSortIndex + 1]Then' Move larger values up.wSwapTemp = wSampleArray[bSortIndex]' ..by swapping them.wSampleArray[bSortIndex] = wSampleArray[bSortIndex + 1] wSampleArray[bSortIndex + 1] = wSwapTemp tSwapOccured = 1' Set bit if swap occurred.EndIfIncbSortIndexUntilbSortIndex > cSamplesToTake' Check next cell of the array.UntiltSwapOccured = 0' Keep sorting until no more swaps.wSortResult = wSampleArray[cSamplesToTake / 2]' Extract the middle (median) valueReturn'-------------------------------------------------------------------- ' Read the X output from the accelerometer (Forward-Backward) ' Input : None ' Output : wXoutput holds the filtered 10-bit result from the ADC ' fXVoltage holds the voltage output from the channel ' Notes : NoneReadChannel_X:ForbSortIndex = (cSamplesToTake - 1)To0Step-1' Create a loop for the amount of samples to takewSampleArray[bSortIndex] =ReadADC(ADC_CH0)' Read the channel into the filter arrayDelayUS2' A small delay to allow the ADC's capacitor to re-chargeNext' Close the loopGoSubMedianFilter' Perform a median filter on the sampleswXoutput = wSortResult' Transfer the result of the filterfXVoltage = wXoutput * cQuanta' Convert to a voltageReturn'-------------------------------------------------------------------- ' Read the Y output from the accelerometer (Side to Side) ' Input : None ' Output : wYoutput holds the filtered 10-bit result from the ADC ' fYVoltage holds the voltage output from the channel ' Notes : NoneReadChannel_Y:ForbSortIndex = (cSamplesToTake - 1)To0Step-1' Create a loop for the amount of samples to takewSampleArray[bSortIndex] =ReadADC(ADC_CH1)' Read the channel into the filter arrayDelayUS2' A small delay to allow the ADC's capacitor to re-chargeNext' Close the loopGoSubMedianFilter' Perform a median filter on the sampleswYoutput = wSortResult' Transfer the result of the filterfYVoltage = wYoutput * cQuanta' Convert to a voltageReturn'-------------------------------------------------------------------- ' Read the Z output from the accelerometer (Up and Down) ' Input : None ' Output : wZoutput holds the filtered 10-bit result from the ADC ' fZVoltage holds the voltage output from the channel ' Notes : NoneReadChannel_Z:ForbSortIndex = (cSamplesToTake - 1)To0Step-1' Create a loop for the amount of samples to takewSampleArray[bSortIndex] =ReadADC(ADC_CH2)' Read the channel into the filter arrayDelayUS2' A small delay to allow the ADC's capacitor to re-chargeNext' Close the loopGoSubMedianFilter' Perform a median filter on the sampleswZoutput = wSortResult' Transfer the result of the filterfZVoltage = wZoutput * cQuanta' Convert to a voltageReturn'-------------------------------------------------------------------- ' The main program loop starts hereMain:Set_2g()' Set accelerometer sensitivity' ' Open the ADC and make AN0, AN1, and AN2 analogue inputs 'OpenADC(ADC_FOSC_32 & ADC_RIGHT_JUST & ADC_0_TAD, ADC_REF_VDD_VSS, ADC_1ANA & ADC_2ANA & ADC_3ANA)' ' Display the accelerometer forces and angles on the serial terminal 'While' Create an infinite loopGoSubReadChannel_X' Read the X channelGoSubReadChannel_Y' Read the Y channelGoSubReadChannel_Z' Read the Z channel' ' Calculate the gravity measurement of the axis 'fGravity_X = (fXVoltage - Zero_G_X) / cSensitivity fGravity_Y = (fYVoltage - Zero_G_Y) / cSensitivity fGravity_Z = (fZVoltage - Zero_G_Z) / cSensitivity' ' Convert the gravity measurements to angles and convert to degrees 'Rx = fGravity_X * fGravity_X Ry = fGravity_Y * fGravity_Y Rz = fGravity_Z * fGravity_Z R =Sqr(Rx + Ry + Rz) bAngleX = (ACos(fGravity_X / R)) * cDegreeConversion bAngleY = (ACos(fGravity_Y / R)) * cDegreeConversionHRSOut1' Clear the serial terminal's windowHRSOut"X axis (Forward-Backward): ",Dec2fGravity_X, " g\r"' Display the X channel's gravity valueHRSOut"Y axis (Side to Side) : ",Dec2fGravity_Y, " g\r"' Display the Y channel's gravity valueHRSOut"\r"HRSOut"X axis (Forward-Backward): ",DecbAngleX, " degrees\r"' Display the X channel's angleHRSOut"Y axis (Side to Side) : ",DecbAngleY, " degrees\r"' Display the Y channel's angleDelayMS300Wend' Do it forever

Once the program is loaded into the Amicus18 board, the serial terminal will display the values, as shown below:

Les Johnson.

Les Johnson.

Les