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);
}
}
}
}