Tuto. 14 - Accelerometer
PIC tutorial 14 - 2 axis accelerometer
Fig. 1 Board 4 with accelerometer and graphic lcd display
Description:
With this tutorial, we'll start looking at analog to digital conversion, using a 2 axis accelerometer. The project will build a tilt sensor with roll and pitch reading. It will also display the acceleration from both axis.
You will need a processor board with 4K of program memory at least (we'll use the math library), a power supply, the graphical or alphanumeric display on port B and the analog accelerometer board on port A (An0 and An1 used). The power supply is also the voltage reference for the analog to digital converter, so it needs to be properly filtered.
The boards can be powered by one of the PSU boards (powered from USB port), or between 2.5 to 5V.
Requirements:
Processor boardAt least 4Kword program (board 4 used)
Extension(s)analog accelerometer
alphanumeric or graphic LCD display
Power supplyPSU 1, 2 or 3. Depends on board used.
LanguageCCS C
ProgrammerPicKIT 2
Program:
#define board_id 4
#include "lib\mainboard.c" // Load board module
#include "lib\nok3310_lib.c"
#include "lib\ADXL320_lib.c"
signed int16 accX, accY;
float pitch, roll;
void main() {
boot_up();
init_display(); // Init display
Init_ADXL320(); // Init accelerometer
while (true) {
accX = ADXL320_read_axis(0); // Read acceleration on axis X
roll = ADXL320_get_angle(accX); // convert it to angle
accY = ADXL320_read_axis(1); // Do the same with axis Y
pitch = ADXL320_get_angle(accY);
printf(display_putc, "\f ADXL320"); // Display results in 2 columns (Roll and pitch)
display_gotoxy(0,1);
printf(display_putc, " Roll Pitch ");
display_gotoxy(0,2);
printf(display_putc, "%4.2wg ", accX); // The acceleration comes in 100th of g. Format it properly
display_gotoxy(8,2);
printf(display_putc, "%4.2wg ", accY);
display_gotoxy(0,3);
printf(display_putc, "%3.1f", roll);
display_print_degree();
printf(display_putc, " ");
display_gotoxy(8,3);
printf(display_putc, "%3.1f", pitch);
display_print_degree();
printf(display_putc, " ");
delay_ms(500); // wait a bit before looping
}
}
First, as usual, the processor board used is defined with board_id., then it loads the various libraries. The new one here is ADXL320_lib. It contains a few functions to easily use the sensor board.
accX and accY are signed long integer that will hold 100th of g. Pitch and Roll will hold the angle in degree of the inclination of the sensor.
The main function initialise the processor board, the display and the accelerometer.
For the main loop, we read each axis, and convert the acceleration into angle. We can then display roll and pitch in 2 column, with the acceleration in g and the corresponding angle.
The angle is only relevant when g is between +/- 1g. It uses static acceleration (gravity) to calculate the angle of the sensor.
The sensitivity is at its best when the sensor is flat. It decrease as the angle increase.
Most of this is done in the ADXL320 library:
#ifndef _ADXL320_LIB
#define _ADXL320_LIB
#ifndef board_id
#error "variable board_id not defined"
#else
#ifndef ACCEL_NO_TILT // define to 1 if angle function not required. (Doesn't load math library to save space)
#define ACCEL_NO_TILT 0
#endif
#if !(ACCEL_NO_TILT)
#ifndef MATH_H
#include <math.h>
#endif
#endif
#define sensitivity_ADXL 190 // sensitivity (mV/g) (From ADXL320 datasheet). Use this value to calibrate the sensor (+/-1g)
//* sensitivity changes with power supply. It is typically 174mV/g at 3V, 312 at 5V, or 135 at 2.4V. My board power supply is 3.3V. 190mV/g works well for me.
#define ADXL_formula (3000000000/(sensitivity_ADXL*(1<<(getenv("ADC_RESOLUTION"))))) // formula to get g out of digital value (Vref/(sensitivity*ADC resolution))
const float ADXL_factor=(float)(ADXL_formula)/10000;
const signed int16 off_ADXL_X=-45; // offset to calibrate X axis. The numbers are for 10 bits ADC. Correction is done automatically to adjust for other resolution
const signed int16 off_ADXL_Y=-32; // offset to calibrate Y axis. Use this value to calibrate the sensor center point for each axis
void Init_ADXL320() { // Initialise ADC for ADXL320
setup_adc_ports( AN0_TO_AN1 ); // Enable An0 and An1 as analog inputs
setup_adc(ADC_CLOCK_DIV_32);
}
signed int16 ADXL320_read_axis(int8 axis){ // Function to read axis. Return signed int of value in 100th of g. Axis: 0=X, 1=Y.
unsigned int16 readADXL;
set_adc_channel(axis); // set ADC to channel 0 or 1
delay_us(10); // delay to allow ADC to settle on channel
readADXL = read_adc(); // read value. 4 consecutive reads are averaged
delay_ms(10); // little delay
readADXL += read_adc(); // takes 4 reading, adding the value to previous read.
delay_ms(10); // readADXL is 16 bits integer, so it will not overflow, even with 4 12bits reads
readADXL += read_adc();
delay_ms(10);
readADXL += read_adc();
if (axis) // if axis = 1, return Y axis
return (((readADXL >> 2) - (1<<(getenv("ADC_RESOLUTION")-1)) + ((off_ADXL_Y*(1<<getenv("ADC_RESOLUTION")))/1024)) * ADXL_factor); // we need the difference between 0g (half of power supply), corrected by offset
else // we use the resolution of the AD converter to find half way. (we shift left 1 the proper number of times to get mid value)
return (((readADXL >> 2) - (1<<(getenv("ADC_RESOLUTION")-1)) + ((off_ADXL_X*(1<<getenv("ADC_RESOLUTION")))/1024)) * ADXL_factor);
}
#if !(ACCEL_NO_TILT)
float ADXL320_get_angle(signed int16 g){ // get angle value in degree from g value (in 100th of g)
float angle;
angle = (float) g/100; // we need to get a value between -1 and +1
if (angle > 1) angle = 1; // make sure it is within range. Clip if not
if (angle < -1) angle = -1;
return ((ASIN(angle))*90/(PI/2)); // ASIN return angle in radian, converts it to degree before returning
}
#endif
#endif
#endif
The library uses a few macros and tricks to configure itself with the analog to digital converter's resolution of the project and use sensitivity out of the datasheet of the ADXL320.
First we make sure that a chip is defined. The variable ACCEL_NO_TILT, if set to 1, will skip loading the math library and the get_angle function to save memory space. The math library is quite big, and that functionnality might not be needed.
Then come some variables to make implementation and calibration easier:
- sensitivity_ADXL: this is the sensitivity of the sensor, as described in the datasheet. It changes with power supply. It is typically 174mV/g at 3V, 312 at 5V, or 135 at 2.4V. My board power supply is 3.3V, 190mV/g works well for it. Use this to calibrate the sensor so it reads +-1g when the sensor is vertical.
- ADXL_formula: this is used to calculate acceleration in g out of digital value read on DAC. (Vref/(sensitivity*ADC resolution))
- ADXL_factor: this is a float from the previous formula
- off_ADXL_X and off_ADXL_Y are offset added to the digital value read from the DAC to compensate and get 0g when the sensor is flat (Find the middle point for each axis). The sensor is soldered byhand on my board, so it might not be exactly parallel to the PCB. the values are used to compensate for this. The number would change depending on the resolution of the DAC, so the values are given for 10 bits resolution. It is adjusted automatically when converting the value to g.
The formula 1<<(getenv("ADC_RESOLUTION") is used to get the resolution of the DAC: it shifts left 1 the number of bits used in the resolution of the DAC: 1 << 10 = 1024, or 1 << 8 = 256. This is used to adjust the function to convert digital value to g.
We then declare the functions:
- Init_ADXL320: it simply sets up the ADC to manage the sensor. Pins An0 and An1 are set as analog inputs using setup_adc_ports function. The value that can be passed to it depends on the processor used. Chech the .h file for your processor to get a list of possible values (18F2320.h in my case) . Then setup_adc to configure the AD converter.
- ADXL320_read_axis(axis): this returns the value in 100th of g for the axis given. If axis = 0, it returns the X axis, Y if 1. The function set the channel to be read with set_adc_channel to An0 or An1. It pauses a bit to give time for the channel to settle on the ADC. as suggested in the processor's datasheet. We then add 4 consecutive reading and average it to calculate the g value. It returns the value in 100th of g.
- ADXL320_get_angle(g): this function calculate the angle of the sensor based on a static acceleration reading (gravity) between +/- 1 g. When the sensor is 90 degrees, it reads 1g. When flat, or 0 degree, it reads 0g. Using ASIN math function, we can return the corresponding value. If the value to convert is greater than 1 or lower than -1, it is clipped to 1. The resolution is best when the sensor is at 0 degree, and gets worse as it gets near the 90 degrees.
Compile the project and program the target with a PicKIT 2 programmer for example. Connect the processor board to the accelerometer on port A, using pins An0 and An1, and the graphical display on port B. Power up the boards.
The screen will display 2 columns, one for each axis, with the acceleration and angle.
ADXL320
Roll Pitch
0.03g 0.05g
1.9° 2.8°
Files and links:
Tutorial 14 source files.Processor board 4.
Graphic LCD display.
Analog accelerometer.

