001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package org.wpilib.hardware.accelerometer;
006
007import java.nio.ByteBuffer;
008import java.nio.ByteOrder;
009import org.wpilib.hardware.bus.I2C;
010import org.wpilib.hardware.hal.HAL;
011import org.wpilib.hardware.hal.SimDevice;
012import org.wpilib.hardware.hal.SimDouble;
013import org.wpilib.hardware.hal.SimEnum;
014import org.wpilib.networktables.DoublePublisher;
015import org.wpilib.networktables.DoubleTopic;
016import org.wpilib.networktables.NTSendable;
017import org.wpilib.networktables.NTSendableBuilder;
018import org.wpilib.util.sendable.SendableRegistry;
019
020/** ADXL345 I2C Accelerometer. */
021@SuppressWarnings("TypeName")
022public class ADXL345_I2C implements NTSendable, AutoCloseable {
023  /** Default I2C device address. */
024  public static final byte DEFAULT_ADDRESS = 0x1D;
025
026  private static final byte POWER_CTL_REGISTER = 0x2D;
027  private static final byte DATA_FORMAT_REGISTER = 0x31;
028  private static final byte DATA_REGISTER = 0x32;
029  private static final double GS_PER_LSB = 0.00390625;
030  // private static final byte POWER_CTL_LINK = 0x20;
031  // private static final byte POWER_CTL_AUTO_SLEEP = 0x10;
032  private static final byte POWER_CTL_MEASURE = 0x08;
033  // private static final byte POWER_CTL_SLEEP = 0x04;
034
035  // private static final byte DATA_FORMAT_SELF_TEST = (byte) 0x80;
036  // private static final byte DATA_FORMAT_SPI = 0x40;
037  // private static final byte DATA_FORMAT_INT_INVERT = 0x20;
038  private static final byte DATA_FORMAT_FULL_RES = 0x08;
039
040  // private static final byte DATA_FORMAT_JUSTIFY = 0x04;
041
042  /** Accelerometer axes. */
043  public enum Axis {
044    /** X axis. */
045    X((byte) 0x00),
046    /** Y axis. */
047    Y((byte) 0x02),
048    /** Z axis. */
049    Z((byte) 0x04);
050
051    /** The integer value representing this enumeration. */
052    public final byte value;
053
054    Axis(byte value) {
055      this.value = value;
056    }
057  }
058
059  /** Container type for accelerations from all axes. */
060  public static class AllAxes {
061    /** Acceleration along the X axis in g-forces. */
062    public double x;
063
064    /** Acceleration along the Y axis in g-forces. */
065    public double y;
066
067    /** Acceleration along the Z axis in g-forces. */
068    public double z;
069
070    /** Default constructor. */
071    public AllAxes() {}
072  }
073
074  private I2C m_i2c;
075
076  private SimDevice m_simDevice;
077  private SimEnum m_simRange;
078  private SimDouble m_simX;
079  private SimDouble m_simY;
080  private SimDouble m_simZ;
081
082  /**
083   * Constructs the ADXL345 Accelerometer with I2C address 0x1D.
084   *
085   * @param port The I2C port the accelerometer is attached to
086   * @param range The range (+ or -) that the accelerometer will measure. Valid values are 2, 4, 8,
087   *     or 16 Gs.
088   */
089  public ADXL345_I2C(I2C.Port port, int range) {
090    this(port, range, DEFAULT_ADDRESS);
091  }
092
093  /**
094   * Constructs the ADXL345 Accelerometer over I2C.
095   *
096   * @param port The I2C port the accelerometer is attached to
097   * @param range The range (+ or -) that the accelerometer will measure. Valid values are 2, 4, 8,
098   *     or 16 Gs.
099   * @param deviceAddress I2C address of the accelerometer (0x1D or 0x53)
100   */
101  @SuppressWarnings("this-escape")
102  public ADXL345_I2C(I2C.Port port, int range, int deviceAddress) {
103    m_i2c = new I2C(port, deviceAddress);
104
105    // simulation
106    m_simDevice = SimDevice.create("Accel:ADXL345_I2C", port.value, deviceAddress);
107    if (m_simDevice != null) {
108      m_simRange =
109          m_simDevice.createEnumDouble(
110              "range",
111              SimDevice.Direction.OUTPUT,
112              new String[] {"2G", "4G", "8G", "16G"},
113              new double[] {2.0, 4.0, 8.0, 16.0},
114              0);
115      m_simX = m_simDevice.createDouble("x", SimDevice.Direction.INPUT, 0.0);
116      m_simY = m_simDevice.createDouble("y", SimDevice.Direction.INPUT, 0.0);
117      m_simZ = m_simDevice.createDouble("z", SimDevice.Direction.INPUT, 0.0);
118    }
119
120    // Turn on the measurements
121    m_i2c.write(POWER_CTL_REGISTER, POWER_CTL_MEASURE);
122
123    setRange(range);
124
125    HAL.reportUsage("I2C[" + port.value + "][" + deviceAddress + "]", "ADXL345");
126    SendableRegistry.add(this, "ADXL345_I2C", port.value);
127  }
128
129  /**
130   * Returns the I2C port.
131   *
132   * @return The I2C port.
133   */
134  public I2C.Port getPort() {
135    return m_i2c.getPort();
136  }
137
138  /**
139   * Returns the I2C device address.
140   *
141   * @return The I2C device address.
142   */
143  public int getDeviceAddress() {
144    return m_i2c.getDeviceAddress();
145  }
146
147  @Override
148  public void close() {
149    SendableRegistry.remove(this);
150    if (m_i2c != null) {
151      m_i2c.close();
152      m_i2c = null;
153    }
154    if (m_simDevice != null) {
155      m_simDevice.close();
156      m_simDevice = null;
157    }
158  }
159
160  /**
161   * Set the measuring range of the accelerometer.
162   *
163   * @param range The maximum acceleration, positive or negative, that the accelerometer will
164   *     measure. Valid values are 2, 4, 8, or 16 Gs.
165   */
166  public final void setRange(int range) {
167    final byte value =
168        switch (range) {
169          case 2 -> 0;
170          case 4 -> 1;
171          case 8 -> 2;
172          case 16 -> 3;
173          default -> 0; // default to 2G if invalid value is passed in
174        };
175
176    // Specify the data format to read
177    m_i2c.write(DATA_FORMAT_REGISTER, DATA_FORMAT_FULL_RES | value);
178
179    if (m_simRange != null) {
180      m_simRange.set(value);
181    }
182  }
183
184  /**
185   * Returns the acceleration along the X axis in g-forces.
186   *
187   * @return The acceleration along the X axis in g-forces.
188   */
189  public double getX() {
190    return getAcceleration(Axis.X);
191  }
192
193  /**
194   * Returns the acceleration along the Y axis in g-forces.
195   *
196   * @return The acceleration along the Y axis in g-forces.
197   */
198  public double getY() {
199    return getAcceleration(Axis.Y);
200  }
201
202  /**
203   * Returns the acceleration along the Z axis in g-forces.
204   *
205   * @return The acceleration along the Z axis in g-forces.
206   */
207  public double getZ() {
208    return getAcceleration(Axis.Z);
209  }
210
211  /**
212   * Get the acceleration of one axis in Gs.
213   *
214   * @param axis The axis to read from.
215   * @return Acceleration of the ADXL345 in Gs.
216   */
217  public double getAcceleration(Axis axis) {
218    if (axis == Axis.X && m_simX != null) {
219      return m_simX.get();
220    }
221    if (axis == Axis.Y && m_simY != null) {
222      return m_simY.get();
223    }
224    if (axis == Axis.Z && m_simZ != null) {
225      return m_simZ.get();
226    }
227    ByteBuffer rawAccel = ByteBuffer.allocate(2);
228    m_i2c.read(DATA_REGISTER + axis.value, 2, rawAccel);
229
230    // Sensor is little endian... swap bytes
231    rawAccel.order(ByteOrder.LITTLE_ENDIAN);
232    return rawAccel.getShort(0) * GS_PER_LSB;
233  }
234
235  /**
236   * Get the acceleration of all axes in Gs.
237   *
238   * @return An object containing the acceleration measured on each axis of the ADXL345 in Gs.
239   */
240  public AllAxes getAccelerations() {
241    AllAxes data = new AllAxes();
242    if (m_simX != null && m_simY != null && m_simZ != null) {
243      data.x = m_simX.get();
244      data.y = m_simY.get();
245      data.z = m_simZ.get();
246      return data;
247    }
248    ByteBuffer rawData = ByteBuffer.allocate(6);
249    m_i2c.read(DATA_REGISTER, 6, rawData);
250
251    // Sensor is little endian... swap bytes
252    rawData.order(ByteOrder.LITTLE_ENDIAN);
253    data.x = rawData.getShort(0) * GS_PER_LSB;
254    data.y = rawData.getShort(2) * GS_PER_LSB;
255    data.z = rawData.getShort(4) * GS_PER_LSB;
256    return data;
257  }
258
259  @Override
260  public void initSendable(NTSendableBuilder builder) {
261    builder.setSmartDashboardType("3AxisAccelerometer");
262    DoublePublisher pubX = new DoubleTopic(builder.getTopic("X")).publish();
263    DoublePublisher pubY = new DoubleTopic(builder.getTopic("Y")).publish();
264    DoublePublisher pubZ = new DoubleTopic(builder.getTopic("Z")).publish();
265    builder.addCloseable(pubX);
266    builder.addCloseable(pubY);
267    builder.addCloseable(pubZ);
268    builder.setUpdateTable(
269        () -> {
270          AllAxes data = getAccelerations();
271          pubX.set(data.x);
272          pubY.set(data.y);
273          pubZ.set(data.z);
274        });
275  }
276}