ADXL345 Driver Class

Here is my own ADXL345 driver class.

It currently supports…

  • Changing between 2g, 4g, 8g & 16g modes.
  • Fixed and Full resolution.
  • Threaded updating with adjustable update rate
  • Free-Fall interrupt callback
  • Free-Fall detect options
  • I2C Mode with main and alternate addressing
  • Raw data (direct from the sensor) and floating-point (values in Gs) data

Coming eventually…

  • SPI mode in the same class (alternate constructors for I2C and SPI modes)
  • Tap & Double-Tap interrupt callbacks

Here is the code…


/*
Copyright 2010 Robert Heffernan. All rights reserved.
 
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
 
   1. Redistributions of source code must retain the above copyright notice, this list of
      conditions and the following disclaimer.
 
   2. Redistributions in binary form must reproduce the above copyright notice, this list
      of conditions and the following disclaimer in the documentation and/or other materials
      provided with the distribution.
 
THIS SOFTWARE IS PROVIDED BY Robert Heffernan "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Robert Heffernan OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of Robert Heffernan.
*/

using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;

namespace Heffsoft.Sensors.Accelerometer
{
    /// <summary>
    /// A class to interface to the ADXL345 3-Axis ±2g, ±4g, ±8g, ±16g Accelerometer from Analog Devices (In I2C Mode, the class can be modified to use SPI)
    /// Product Page: http://www.analog.com/en/sensors/inertial-sensors/adxl345/products/product.html
    /// Datasheet: http://www.analog.com/static/imported-files/data_sheets/ADXL345.pdf
    /// </summary>
    public class ADXL345
    {
        /// <summary>
        /// A structure holding the current acceleration values returned from the sensor
        /// </summary>
        public struct SensorData
        {
            /// <summary>
            /// The raw X-Axis acceleration data
            /// </summary>
            public Int16 RawX;

            /// <summary>
            /// The raw Y-Axis acceleration data
            /// </summary>
            public Int16 RawY;

            /// <summary>
            /// The raw Z-Axis acceleration data
            /// </summary>
            public Int16 RawZ;

            /// <summary>
            /// The X-Axis acceleration data (in Gs)
            /// </summary>
            public Single X;

            /// <summary>
            /// The Y-Axis acceleration data (in Gs)
            /// </summary>
            public Single Y;

            /// <summary>
            /// The Z-Axis acceleration data (in Gs)
            /// </summary>
            public Single Z;

            public override string ToString()
            {
                return "X=" + X.ToString() + "g, Y=" + Y.ToString() + "g, Z=" + Z.ToString() + "g";
            }
        }

        /// <summary>
        /// A private helper enum providing descriptive names for the sensor registers
        /// </summary>
        private enum RegisterMap : byte
        {
            DEVID = 0x00,
            THRESH_TAP = 0x1d,
            OFSX = 0x1e,
            OFSY = 0x1f,
            OFSZ = 0x20,
            DUR = 0x21,
            LATENT = 0x22,
            WINDOW = 0x23,
            THRESH_ACT = 0x24,
            THRESH_INACT = 0x25,
            TIME_INACT = 0x26,
            ACT_INACT_CTL = 0x27,
            THRESH_FF = 0x28,
            TIME_FF = 0x29,
            TAP_AXES = 0x2a,
            ACT_TAP_STATUS = 0x2b,
            BW_RATE = 0x2c,
            POWER_CTL = 0x2d,
            INT_ENABLE = 0x2e,
            INT_MAP = 0x2f,
            INT_SOURCE = 0x30,
            DATA_FORMAT = 0x31,
            DATAX0 = 0x32,
            DATAX1 = 0x33,
            DATAY0 = 0x34,
            DATAY1 = 0x35,
            DATAZ0 = 0x36,
            DATAZ1 = 0x37,
            FIFO_CTL = 0x38,
            FIFO_STATUS = 0x39
        }

        /// <summary>
        /// A private helper enum providing descriptive names for the interrupt source register's bits
        /// </summary>
        [Flags]
        private enum InterruptSource : byte
        {
            DATA_READY = 0x80,
            SINGLE_TAP = 0x40,
            DOUBLE_TAP = 0x20,
            ACTIVITY = 0x10,

            INACTIVITY = 0x08,
            FREE_FALL = 0x04,
            WATERMARK = 0x02,
            OVERRUN = 0x01
        }

        /// <summary>
        /// An enum specifying the G Range currently used by the sensor
        /// </summary>
        public enum G_Range : byte
        {
            TwoG = 0x00,
            FourG = 0x01,
            EightG = 0x02,
            SixteenG = 0x03
        }

        /// <summary>
        /// An enum specifying the Output Resolution (10bit or Full Range) used by the sensor
        /// </summary>
        public enum OutputResolution : byte
        {
            FixedResoultion = 0x00,
            FullResolution = 0x08
        }

        #region Private Values
        private const Byte mainI2CAddress = 0x53;
        private const Byte altI2CAddress = 0x1d;

        private Boolean usingI2C;

        private InterruptPort int1;

        private I2CDevice i2c_bus;
        private I2CDevice.Configuration sensorConfig;

        private Thread updateThread;
        private Int16 updateDelay = 100;

        private G_Range curRange = G_Range.SixteenG;
        private OutputResolution curRes = OutputResolution.FullResolution;

        private SensorData sensorData;
        #endregion

        /// <summary>
        /// Creates an instance of the driver class for the ADXL345 sensor using the I2C bus
        /// </summary>
        /// <param name="I2C_Bus">The I2C bus device used to interface to the sensor</param>
        /// <param name="Interrupt">The pin connected to the sensor to monitor for interrupts</param>
        /// <param name="ClockSpeedKHz">The I2C bus speed (in KHz) to use to communicate with the sensor</param>
        /// <param name="UseAlternateAddress">Indicates to use the alternate device address if multiple sensors are on the same I2C bus</param>
        public ADXL345(I2CDevice I2C_Bus, InterruptPort Interrupt, Int32 ClockSpeedKHz, Boolean UseAlternateAddress)
        {
            // Set the internal bus and interrupts to those provided
            usingI2C = true;
            i2c_bus = I2C_Bus;
            int1 = Interrupt;

            // create a sensor config used from here on to communicate with the sensor
            sensorConfig = new I2CDevice.Configuration(((UseAlternateAddress == false) ? (UInt16)mainI2CAddress : (UInt16)altI2CAddress), ClockSpeedKHz);

            // Initialize our sensor
            InitSensor();
        }

        /// <summary>
        /// Private function used to write data into the sensor's registers
        /// </summary>
        /// <param name="Register">The register to write to</param>
        /// <param name="value">The value to write into the register</param>
        private void WriteRegister(RegisterMap Register, Byte value)
        {
            // Create a new transaction to write a register value
            Byte[] data = new Byte[] { (Byte)Register, value };
            I2CDevice.I2CWriteTransaction write = I2CDevice.CreateWriteTransaction(data);
            I2CDevice.I2CTransaction[] writeTransaction = new I2CDevice.I2CTransaction[] { write };

            // Lock the i2c bus so multiple threads don't try to access it at the same time
            lock (i2c_bus)
            {
                // Set the bus to use our sensor
                i2c_bus.Config = sensorConfig;

                // Execute the transation
                i2c_bus.Execute(writeTransaction, 10);
            }
        }

        /// <summary>
        /// Private function used to read data from the sensor's registers
        /// </summary>
        /// <param name="Register">The register to read from</param>
        /// <returns>A byte value containing the data read from the sensor's register</returns>
        private Byte ReadRegister(RegisterMap Register)
        {
            // Create a new transaction to read a register value
            Byte[] data = new Byte[1];
            I2CDevice.I2CWriteTransaction write = I2CDevice.CreateWriteTransaction(new Byte[] { (Byte)Register });
            I2CDevice.I2CReadTransaction read = I2CDevice.CreateReadTransaction(data);
            I2CDevice.I2CTransaction[] readTransaction = new I2CDevice.I2CTransaction[] { write, read };

            // Lock the i2c bus so multiple threads don't try to access it at the same time
            lock (i2c_bus)
            {
                // Set the bus to use our sensor
                i2c_bus.Config = sensorConfig;

                // Execute the transation
                i2c_bus.Execute(readTransaction, 10);
            }

            // Return the register value
            return data[0];
        }

        /// <summary>
        /// A private function called by the constructor to initialize the sensor for operation and starts measurements
        /// </summary>
        private void InitSensor()
        {
            // Set event callback for the interrupt pin (if connected)
            if (int1 != null)
            {
                int1.OnInterrupt += new NativeEventHandler(ADXL345_Interrupt_OnInterrupt);
                int1.EnableInterrupt();
            }

            // Bypass the FIFO
            WriteRegister(RegisterMap.FIFO_CTL, 0x0F); // Bypass FIFO, Trigger on Int1 and 32 FIFO samples

            // Enable freefall interrupt
            WriteRegister(RegisterMap.INT_MAP, 0x00);
            WriteRegister(RegisterMap.INT_ENABLE, (Byte)InterruptSource.FREE_FALL);

            // Setup freefall thresholds
            WriteRegister(RegisterMap.THRESH_FF, 0x05); // 315mg
            WriteRegister(RegisterMap.TIME_FF, 0x14); // 100ms

            // Setup data format
            WriteRegister(RegisterMap.DATA_FORMAT, (Byte)OutputResolution.FullResolution | (Byte)G_Range.SixteenG); // 16g Full Res Mode

            // Enable measurement
            WriteRegister(RegisterMap.POWER_CTL, 0x08);

            // Start background update thread
            updateThread = new Thread(new ThreadStart(ADXL345_ThreadMain));
            updateThread.Start();
        }

        /// <summary>
        /// Specifies the delay (in milliseconds) used by the background thread to wait between updating the sensor readings
        /// </summary>
        public Int16 UpdateDelay
        {
            get { return updateDelay; }
            set { updateDelay = value; }
        }

        /// <summary>
        /// Specifies the output resolution the sensor uses when calculating the acceleration data
        /// </summary>
        public OutputResolution OutputRes
        {
            get
            {
                // Read the data format register
                Byte val = ReadRegister(RegisterMap.DATA_FORMAT);

                // Mask off the output resolution bit and return it as an OutputResolution enum
                return (OutputResolution)(val & 0x08);
            }

            set
            {
                // Read the existing value from the data format register
                Byte val = ReadRegister(RegisterMap.DATA_FORMAT);

                // Mask off the non output resolution values and then 'OR' with the new resolution value
                val = (Byte)((val & 0xF7) | (Byte)value);

                // Write the modified register back to the sensor
                WriteRegister(RegisterMap.DATA_FORMAT, val);

                // Update our internal conversion value
                curRes = value;
            }
        }

        /// <summary>
        /// Specifies the G range used by the sensor when calculating acceleration data
        /// </summary>
        public G_Range Range
        {
            get
            {
                // Read the data format register
                Byte val = ReadRegister(RegisterMap.DATA_FORMAT);

                // Mask off the G range values and return it as a G_Range enum
                return (G_Range)(val & 0x03);
            }

            set
            {
                // Read the existing value from the data format register
                Byte val = ReadRegister(RegisterMap.DATA_FORMAT);

                // Mask off the non g-related values and then 'OR' with the new range value
                val = (Byte)((val & 0xFC) | (Byte)value);

                // Write the modified register back to the sensor
                WriteRegister(RegisterMap.DATA_FORMAT, val);

                // Update our internal conversion value
                curRange = value;
            }
        }

        /// <summary>
        /// Specifies the time (in milliseconds, 5ms resolution) the sensor uses to detect a freefall event
        /// (The sensor needs to be under (FreefallThreshold)mg acceleration for (FreefallDetectTime)ms to register freefall
        /// </summary>
        public UInt16 FreefallDetectTime
        {
            get
            {
                // Read the freefall time register
                Byte time = ReadRegister(RegisterMap.TIME_FF);

                // Convert the value into milliseconds and return it
                return (UInt16)(time * 5);
            }

            set
            {
                // Clamp and convert the time from milliseconds to a register value
                Byte val = (Byte)(((value > 1275) ? 1275 : value) / 5);

                // Write the new register value
                WriteRegister(RegisterMap.TIME_FF, val);
            }
        }

        /// <summary>
        /// Specifies the threshold (in milli-Gs, 62.5mg resolution) the sensor uses to detect a freefall event
        /// (The sensor needs to be under (FreefallThreshold)mg acceleration for (FreefallDetectTime)ms to register freefall
        /// </summary>
        public Single FreefallThreshold
        {
            get
            {
                // Read the freefall threshold register
                Byte val = ReadRegister(RegisterMap.THRESH_FF);

                // Convert the register value into milliGs and return it
                return (Single)val * 62.5f;
            }

            set
            {
                // Clamp then convert the value from milliGs into a register value
                Byte val = (Byte)(((value > 15937.5f) ? 15937.5f : value) / 62.5f);

                // Write the new register value
                WriteRegister(RegisterMap.THRESH_FF, val);
            }
        }

        /// <summary>
        /// gets the current acceleration data from the sensor
        /// </summary>
        public SensorData CurrentData
        {
            get
            {
                return sensorData;
            }
        }

        /// <summary>
        /// A private callback function called when there is an interrupt generated from the sensor
        /// </summary>
        private void ADXL345_Interrupt_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            // Read the interrupt source register to see what caused the event
            InterruptSource source = (InterruptSource)ReadRegister(RegisterMap.INT_SOURCE);

            // Was the interrupt caused by a freefall event
            if ((source & InterruptSource.FREE_FALL) == InterruptSource.FREE_FALL)
            {
                // Process the freefall callback
                OnFreefallDetected();
            }
        }

        /// <summary>
        /// The main thread function used to update the acceleration values from the sensor
        /// </summary>
        public void ADXL345_ThreadMain()
        {
            // Create the transactions and buffers used to read from the sensor. Only do it once here to save constantly reallocating the same stuff over and over
            Byte[] accelData = new Byte[6];
            I2CDevice.I2CWriteTransaction write = I2CDevice.CreateWriteTransaction(new Byte[] { (Byte)RegisterMap.DATAX0 });
            I2CDevice.I2CReadTransaction read = I2CDevice.CreateReadTransaction(accelData);
            I2CDevice.I2CTransaction[] getDataTransaction = new I2CDevice.I2CTransaction[] { write, read };

            // Loop indefinately
            while (true)
            {
                // Lock the i2c bus so multiple threads don't try to access it at the same time
                lock (i2c_bus)
                {
                    // Set the bus to use our sensor
                    i2c_bus.Config = sensorConfig;

                    // Execute the transaction
                    i2c_bus.Execute(getDataTransaction, 50);
                }

                // Convert the raw byte data into the raw acceleration data for each axis
                sensorData.RawX = (Int16)(accelData[1] << 8 | accelData[0]);
                sensorData.RawY = (Int16)(accelData[3] << 8 | accelData[2]);
                sensorData.RawZ = (Int16)(accelData[5] << 8 | accelData[4]);

                // Take the raw acceleration data and convert it into Gs
                if (curRes == OutputResolution.FullResolution)
                {
                    // In "Full Resolution" mode, the sensor maintains a 4mg/LSB resolution
                    sensorData.X = 0.004f * sensorData.RawX;
                    sensorData.Y = 0.004f * sensorData.RawY;
                    sensorData.Z = 0.004f * sensorData.RawZ;
                }
                else
                {
                    // Call the function to convert the fixed resolution to Gs
                    ConvertFixedRange();
                }

//#if(DEBUG)
//                Debug.Print(sensorData.ToString());
//#endif

                // Sleep until the next update
                Thread.Sleep(updateDelay);
            }
        }

        /// <summary>
        /// A private function to convert the raw fixed mode axis data into Gs
        /// </summary>
        private void ConvertFixedRange()
        {
            Single res = 0.0f;

            // Get the resolution for the current G range
            switch (curRange)
            {
                case G_Range.TwoG:
                    res = 2.0f / 512.0f;
                    break;

                case G_Range.FourG:
                    res = 4.0f / 512.0f;
                    break;

                case G_Range.EightG:
                    res = 8.0f / 512.0f;
                    break;

                case G_Range.SixteenG:
                    res = 16.0f / 512.0f;
                    break;
            }

            // Convert the raw data to Gs
            sensorData.X = res * sensorData.RawX;
            sensorData.Y = res * sensorData.RawY;
            sensorData.Z = res * sensorData.RawZ;
        }

        /// <summary>
        /// A callback delegate used to respond to callback events
        /// </summary>
        /// <param name="sender">The driver that raised the event</param>
        public delegate void ADXL345_Callback(ADXL345 sender);

        /// <summary>
        /// The event called when the sensor registers a freefall event
        /// </summary>
        public event ADXL345_Callback FreefallDetected;

        /// <summary>
        /// A private function used to dispatch a freefall event
        /// </summary>
        private void OnFreefallDetected()
        {
            if (FreefallDetected != null)
            {
                FreefallDetected(this);
            }
        }
    }
}

Good drivers. Add to wiki and I will get you some bonus points :wink: