Show:

File: src/io/AccelerometerADXL345.js

/**
 * Copyright (c) 2011-2012 Jeff Hoefs <soundanalogous@gmail.com>
 * Released under the MIT license. See LICENSE file for details.
 */

JSUTILS.namespace('BO.io.AccelerometerADXL345');

/**
 * @namespace BO.io
 */
BO.io.AccelerometerADXL345 = (function() {
  "use strict";

  var AccelerometerADXL345;

  // private static constants
  var RAD_TO_DEG = 180 / Math.PI,
    POWER_CTL = 0x2D,
    DATAX0 = 0x32,
    DATA_FORMAT = 0x31,
    OFSX = 0x1E,
    OFSY = 0x1F,
    OFSZ = 0x20,
    ALL_AXIS = DATAX0 | 0x80,
    NUM_BYTES = 6;

  // dependencies
  var I2CBase = BO.I2CBase,
    AccelerometerEvent = BO.io.AccelerometerEvent;

  /**
   * Creates an interface to an ADXL345 3-axis accelerometer. Use the
   * accelerometer to read the acceleration along the x, y, and z axis of an
   * object it is attached to. You can also obtain the pitch and roll. See the
   * example in [Breakout/examples/sensors/adxl345.html](https://github.com/soundanalogous/Breakout/blob/master/examples/sensors/adxl345.html).
   *
   * @class AccelerometerADXL345
   * @constructor
   * @extends BO.I2CBase
   * @param {IOBoard} board The IOBoard instance
   * @param {Number} range The dynamic range selection in Gs (options `RANGE_2G`, `RANGE_4G`,
   * `RANGE_8G`, `RANGE_16G`). Default is `RANGE_2G`.
   * @param {Number} address The i2c address of the accelerometer (default is 0x53)
   */
  AccelerometerADXL345 = function(board, range, address) {

    address = address || AccelerometerADXL345.DEVICE_ID;
    I2CBase.call(this, board, address);

    this.name = "AccelerometerADXL345";

    this._dynamicRange = range || AccelerometerADXL345.RANGE_2G;

    this._sensitivity = {
      x: AccelerometerADXL345.DEFAULT_SENSITIVITY,
      y: AccelerometerADXL345.DEFAULT_SENSITIVITY,
      z: AccelerometerADXL345.DEFAULT_SENSITIVITY,
    };

    this._offset = {
      x: 0,
      y: 0,
      z: 0
    };

    this._isReading = false;
    this._debugMode = BO.enableDebugging;

    this._x = 0;
    this._y = 0;
    this._z = 0;
    this._rawX = 0;
    this._rawY = 0;
    this._rawZ = 0;

    // initiate the device
    this.powerOn();

    // sets the dynamic range and sets teh full_res bit
    this.setRangeAndFullRes(this._dynamicRange);

  };

  AccelerometerADXL345.prototype = JSUTILS.inherit(I2CBase.prototype);
  AccelerometerADXL345.prototype.constructor = AccelerometerADXL345;


  // Implement Acceleromter interface:

  Object.defineProperties(AccelerometerADXL345.prototype, {
    // Properties that apply to any accelereomter

    /**
     * [read-only] the accelerometer dynamic range in Gs (either 2G, 4G, 8G, or 16G for this sensor)..
     * @property dynamicRange
     * @type Number
     */
    dynamicRange: {
      get: function() {
        return this._dynamicRange;
      }
    },

    /**
     * [read-only] The acceleration value in Gs (9.8m/sec^2) along the x-axis.
     * @property x
     * @type Number
     */
    x: {
      get: function() {
        return this._x;
      }
    },

    /**
     * [read-only] The acceleration value in Gs (9.8m/sec^2) along the y-axis.
     * @property y
     * @type Number
     */
    y: {
      get: function() {
        return this._y;
      }
    },

    /**
     * [read-only] The acceleration value in Gs (9.8m/sec^2) along the z-axis.
     * @property z
     * @type Number
     */
    z: {
      get: function() {
        return this._z;
      }
    },

    /**
     * [read-only] The pitch value in degrees
     * @property pitch
     * @type Number
     */
    pitch: {
      get: function() {
        // -180 to 180
        //return Math.atan2(this._x, this._z) * RAD_TO_DEG;
        // -90 to 90
        return Math.atan2(this._x, Math.sqrt(this._y * this._y + this._z * this._z)) * RAD_TO_DEG;
      }
    },

    /**
     * [read-only] The roll value in degrees
     * @property roll
     * @type Number
     */
    roll: {
      get: function() {
        // -180 to 180
        //return Math.atan2(this._y, this._z) * RAD_TO_DEG;
        // -90 to 90
        return Math.atan2(this._y, Math.sqrt(this._x * this._x + this._z * this._z)) * RAD_TO_DEG;
      }
    },

    // Properties specific to this I2C Accelerometers:

    /**
     * [read-only] The raw value of the x axis
     * @property rawX
     * @type Number
     */
    rawX: {
      get: function() {
        return this._rawX;
      }
    },

    /**
     * [read-only] The raw value of the y axis
     * @property rawY
     * @type Number
     */
    rawY: {
      get: function() {
        return this._rawY;
      }
    },

    /**
     * [read-only] The raw value of the z axis
     * @property rawZ
     * @type Number
     */
    rawZ: {
      get: function() {
        return this._rawZ;
      }
    },

    /**
     * [read-only] The state of continuous read mode. True if continuous read mode
     * is enabled, false if it is disabled.
     * @property isRunning
     * @type Boolean
     */
    isRunning: {
      get: function() {
        return this._isReading;
      }
    },

    // Properties specific to this Accelerometer type:

    /**
     * The sensitivity value for the x axis (default value = 0.0390625).
     * @property sensitivityX
     * @type Number
     */
    sensitivityX: {
      get: function() {
        return this._sensitivity.x;
      },
      set: function(val) {
        this._sensitivity.x = val;
      }
    },

    /**
     * The sensitivity value for the y axis (default value = 0.0390625).
     * @property sensitivityY
     * @type Number
     */
    sensitivityY: {
      get: function() {

      },
      set: function(val) {
        this._sensitivity.y = val;
      }
    },

    /**
     * The sensitivity value for the z axis (default value = 0.0390625).
     * @property sensitivityZ
     * @type Number
     */
    sensitivityZ: {
      get: function() {

      },
      set: function(val) {
        this._sensitivity.z = val;
      }
    }
  });

  /**
   * @private
   * @method setRangeAndFullRes
   */
  AccelerometerADXL345.prototype.setRangeAndFullRes = function(range) {

    var setting;

    switch (range) {
      case 2:
        setting = 0x00;
        break;
      case 4:
        setting = 0x01;
        break;
      case 8:
        setting = 0x02;
        break;
      case 16:
        setting = 0x03;
        break;
      default:
        setting = 0x00;
        break;
    }

    // set full scale bit (3) and range bits (0 - 1)
    setting |= (0x08 & 0xEC);
    this.sendI2CRequest([I2CBase.WRITE, this._address, DATA_FORMAT, setting]);
  };

  /**
   * @private
   * @method handleI2C
   */
  AccelerometerADXL345.prototype.handleI2C = function(data) {
    switch (data[0]) {
      case ALL_AXIS:
        this.readAccel(data);
        break;
      case OFSX:
        this.debug("offset x = " + data[2]);
        break;
      case OFSY:
        this.debug("offset y = " + data[2]);
        break;
      case OFSZ:
        this.debug("offset z = " + data[2]);
        break;
    }
  };

  /**
   * Start continuous reading of the sensor.
   * @method startReading
   */
  AccelerometerADXL345.prototype.startReading = function() {
    if (!this._isReading) {
      this._isReading = true;
      this.sendI2CRequest([I2CBase.READ_CONTINUOUS, this.address, ALL_AXIS, NUM_BYTES]);
    }
  };

  /**
   * Stop continuous reading of the sensor.
   * @method stopReading
   */
  AccelerometerADXL345.prototype.stopReading = function() {
    this._isReading = false;
    this.sendI2CRequest([I2CBase.STOP_READING, this.address]);
  };

  /**
   * Offset the x, y, or z axis output by the respective input value.
   * @method setAxisOffset
   */
  AccelerometerADXL345.prototype.setAxisOffset = function(xVal, yVal, zVal) {
    // store values so we can retrieve via getAxisOffset
    this._offset.x = xVal;
    this._offset.y = yVal;
    this._offset.z = zVal;

    this.sendI2CRequest([I2CBase.WRITE, this.address, OFSX, xVal]);
    this.sendI2CRequest([I2CBase.WRITE, this.address, OFSY, yVal]);
    this.sendI2CRequest([I2CBase.WRITE, this.address, OFSZ, zVal]);
  };

  /**
   * Get the value of the x, y, and z axis offset.
   * @method getAxisOffset
   */
  AccelerometerADXL345.prototype.getAxisOffset = function() {
    // will trace values if debug mode is enabled
    this.sendI2CRequest([I2CBase.READ, this.address, OFSX, 1]);
    this.sendI2CRequest([I2CBase.READ, this.address, OFSY, 1]);
    this.sendI2CRequest([I2CBase.READ, this.address, OFSZ, 1]);

    // return the locally stored values because it is not possible
    // without a more elaborate design to get i2c read values
    // in a single call
    return this._offset;
  };

  /**
   * Sends read request to accelerometer and updates accelerometer values.
   * @method update
   */
  AccelerometerADXL345.prototype.update = function() {
    if (this._isReading) {
      this.stopReading();
    }
    // read data: contents of X, Y, and Z registers
    this.sendI2CRequest([I2CBase.READ, this.address, ALL_AXIS, NUM_BYTES]);
  };

  /**
   * @private
   * @method powerOn
   */
  AccelerometerADXL345.prototype.powerOn = function() {

    // standby mode
    this.sendI2CRequest([I2CBase.WRITE, this.address, POWER_CTL, 0]);

    // set measure bit
    this.setRegisterBit(POWER_CTL, 3, true);
  };

  /**
   * @private
   * @method setRegisterBit
   */
  AccelerometerADXL345.prototype.setRegisterBit = function(regAddress, bitPos, state) {
    var value;

    if (state) {
      value |= (1 << bitPos);
    } else {
      value &= ~(1 << bitPos);
    }

    this.sendI2CRequest([I2CBase.WRITE, this.address, regAddress, value]);
  };

  /**
   * @private
   * @method readAccel
   */
  AccelerometerADXL345.prototype.readAccel = function(data) {

    var x_val,
      y_val,
      z_val;

    if (data.length != NUM_BYTES + 1) {
      throw new Error("Incorrect number of bytes returned");
    }

    x_val = (data[2] << 8) | (data[1]);
    y_val = (data[4] << 8) | (data[3]);
    z_val = (data[6] << 8) | (data[5]);

    if (x_val >> 15) {
      this._rawX = ((x_val ^ 0xFFFF) + 1) * -1;
    } else {
      this._rawX = x_val;
    }
    if (y_val >> 15) {
      this._rawY = ((y_val ^ 0xFFFF) + 1) * -1;
    } else {
      this._rawY = y_val;
    }
    if (z_val >> 15) {
      this._rawZ = ((z_val ^ 0xFFFF) + 1) * -1;
    } else {
      this._rawZ = z_val;
    }

    this._x = this._rawX * this._sensitivity.x;
    this._y = this._rawY * this._sensitivity.y;
    this._z = this._rawZ * this._sensitivity.z;

    this.dispatchEvent(new AccelerometerEvent(AccelerometerEvent.UPDATE));
  };

  /**
   * for debugging
   * @private
   */
  AccelerometerADXL345.prototype.debug = function(str) {
    if (this._debugMode) {
      console.log(str);
    }
  };

  // public static constants

  /**
   * @property AccelerometerADXL345.RANGE_2G
   * @static
   */
  AccelerometerADXL345.RANGE_2G = 2;
  /**
   * @property AccelerometerADXL345.RANGE_4G
   * @static
   */
  AccelerometerADXL345.RANGE_4G = 4;
  /**
   * @property AccelerometerADXL345.RANGE_8G
   * @static
   */
  AccelerometerADXL345.RANGE_8G = 8;
  /**
   * @property AccelerometerADXL345.RANGE_16G
   * @static
   */
  AccelerometerADXL345.RANGE_16G = 16;
  /**
   * @property AccelerometerADXL345.DEVICE_ID
   * @static
   */
  AccelerometerADXL345.DEVICE_ID = 0x53;
  /**
   * @property AccelerometerADXL345.DEFAULT_SENSITIVITY
   * @static
   */
  AccelerometerADXL345.DEFAULT_SENSITIVITY = 0.00390625;

  // document events

  /**
   * The update event is dispatched when the accelerometer values are updated.
   * @type BO.io.AccelerometerEvent.UPDATE
   * @event update
   * @param {BO.io.AccelerometerADXL345} target A reference to the AccelerometerADXL345 object.
   */

  return AccelerometerADXL345;

}());