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 kAddress = 0x1D;
025
026  private static final byte kPowerCtlRegister = 0x2D;
027  private static final byte kDataFormatRegister = 0x31;
028  private static final byte kDataRegister = 0x32;
029  private static final double kGsPerLSB = 0.00390625;
030  // private static final byte kPowerCtl_Link = 0x20;
031  // private static final byte kPowerCtl_AutoSleep = 0x10;
032  private static final byte kPowerCtl_Measure = 0x08;
033  // private static final byte kPowerCtl_Sleep = 0x04;
034
035  // private static final byte kDataFormat_SelfTest = (byte) 0x80;
036  // private static final byte kDataFormat_SPI = 0x40;
037  // private static final byte kDataFormat_IntInvert = 0x20;
038  private static final byte kDataFormat_FullRes = 0x08;
039
040  // private static final byte kDataFormat_Justify = 0x04;
041
042  /** Accelerometer range. */
043  public enum Range {
044    /** 2 Gs max. */
045    k2G,
046    /** 4 Gs max. */
047    k4G,
048    /** 8 Gs max. */
049    k8G,
050    /** 16 Gs max. */
051    k16G
052  }
053
054  /** Accelerometer axes. */
055  public enum Axes {
056    /** X axis. */
057    kX((byte) 0x00),
058    /** Y axis. */
059    kY((byte) 0x02),
060    /** Z axis. */
061    kZ((byte) 0x04);
062
063    /** The integer value representing this enumeration. */
064    public final byte value;
065
066    Axes(byte value) {
067      this.value = value;
068    }
069  }
070
071  /** Container type for accelerations from all axes. */
072  @SuppressWarnings("MemberName")
073  public static class AllAxes {
074    /** Acceleration along the X axis in g-forces. */
075    public double XAxis;
076
077    /** Acceleration along the Y axis in g-forces. */
078    public double YAxis;
079
080    /** Acceleration along the Z axis in g-forces. */
081    public double ZAxis;
082
083    /** Default constructor. */
084    public AllAxes() {}
085  }
086
087  private I2C m_i2c;
088
089  private SimDevice m_simDevice;
090  private SimEnum m_simRange;
091  private SimDouble m_simX;
092  private SimDouble m_simY;
093  private SimDouble m_simZ;
094
095  /**
096   * Constructs the ADXL345 Accelerometer with I2C address 0x1D.
097   *
098   * @param port The I2C port the accelerometer is attached to
099   * @param range The range (+ or -) that the accelerometer will measure.
100   */
101  public ADXL345_I2C(I2C.Port port, Range range) {
102    this(port, range, kAddress);
103  }
104
105  /**
106   * Constructs the ADXL345 Accelerometer over I2C.
107   *
108   * @param port The I2C port the accelerometer is attached to
109   * @param range The range (+ or -) that the accelerometer will measure.
110   * @param deviceAddress I2C address of the accelerometer (0x1D or 0x53)
111   */
112  @SuppressWarnings("this-escape")
113  public ADXL345_I2C(I2C.Port port, Range range, int deviceAddress) {
114    m_i2c = new I2C(port, deviceAddress);
115
116    // simulation
117    m_simDevice = SimDevice.create("Accel:ADXL345_I2C", port.value, deviceAddress);
118    if (m_simDevice != null) {
119      m_simRange =
120          m_simDevice.createEnumDouble(
121              "range",
122              SimDevice.Direction.kOutput,
123              new String[] {"2G", "4G", "8G", "16G"},
124              new double[] {2.0, 4.0, 8.0, 16.0},
125              0);
126      m_simX = m_simDevice.createDouble("x", SimDevice.Direction.kInput, 0.0);
127      m_simY = m_simDevice.createDouble("y", SimDevice.Direction.kInput, 0.0);
128      m_simZ = m_simDevice.createDouble("z", SimDevice.Direction.kInput, 0.0);
129    }
130
131    // Turn on the measurements
132    m_i2c.write(kPowerCtlRegister, kPowerCtl_Measure);
133
134    setRange(range);
135
136    HAL.reportUsage("I2C[" + port.value + "][" + deviceAddress + "]", "ADXL345");
137    SendableRegistry.add(this, "ADXL345_I2C", port.value);
138  }
139
140  /**
141   * Returns the I2C port.
142   *
143   * @return The I2C port.
144   */
145  public int getPort() {
146    return m_i2c.getPort();
147  }
148
149  /**
150   * Returns the I2C device address.
151   *
152   * @return The I2C device address.
153   */
154  public int getDeviceAddress() {
155    return m_i2c.getDeviceAddress();
156  }
157
158  @Override
159  public void close() {
160    SendableRegistry.remove(this);
161    if (m_i2c != null) {
162      m_i2c.close();
163      m_i2c = null;
164    }
165    if (m_simDevice != null) {
166      m_simDevice.close();
167      m_simDevice = null;
168    }
169  }
170
171  /**
172   * Set the measuring range of the accelerometer.
173   *
174   * @param range The maximum acceleration, positive or negative, that the accelerometer will
175   *     measure.
176   */
177  public final void setRange(Range range) {
178    final byte value =
179        switch (range) {
180          case k2G -> 0;
181          case k4G -> 1;
182          case k8G -> 2;
183          case k16G -> 3;
184        };
185
186    // Specify the data format to read
187    m_i2c.write(kDataFormatRegister, kDataFormat_FullRes | value);
188
189    if (m_simRange != null) {
190      m_simRange.set(value);
191    }
192  }
193
194  /**
195   * Returns the acceleration along the X axis in g-forces.
196   *
197   * @return The acceleration along the X axis in g-forces.
198   */
199  public double getX() {
200    return getAcceleration(Axes.kX);
201  }
202
203  /**
204   * Returns the acceleration along the Y axis in g-forces.
205   *
206   * @return The acceleration along the Y axis in g-forces.
207   */
208  public double getY() {
209    return getAcceleration(Axes.kY);
210  }
211
212  /**
213   * Returns the acceleration along the Z axis in g-forces.
214   *
215   * @return The acceleration along the Z axis in g-forces.
216   */
217  public double getZ() {
218    return getAcceleration(Axes.kZ);
219  }
220
221  /**
222   * Get the acceleration of one axis in Gs.
223   *
224   * @param axis The axis to read from.
225   * @return Acceleration of the ADXL345 in Gs.
226   */
227  public double getAcceleration(Axes axis) {
228    if (axis == Axes.kX && m_simX != null) {
229      return m_simX.get();
230    }
231    if (axis == Axes.kY && m_simY != null) {
232      return m_simY.get();
233    }
234    if (axis == Axes.kZ && m_simZ != null) {
235      return m_simZ.get();
236    }
237    ByteBuffer rawAccel = ByteBuffer.allocate(2);
238    m_i2c.read(kDataRegister + axis.value, 2, rawAccel);
239
240    // Sensor is little endian... swap bytes
241    rawAccel.order(ByteOrder.LITTLE_ENDIAN);
242    return rawAccel.getShort(0) * kGsPerLSB;
243  }
244
245  /**
246   * Get the acceleration of all axes in Gs.
247   *
248   * @return An object containing the acceleration measured on each axis of the ADXL345 in Gs.
249   */
250  public AllAxes getAccelerations() {
251    AllAxes data = new AllAxes();
252    if (m_simX != null && m_simY != null && m_simZ != null) {
253      data.XAxis = m_simX.get();
254      data.YAxis = m_simY.get();
255      data.ZAxis = m_simZ.get();
256      return data;
257    }
258    ByteBuffer rawData = ByteBuffer.allocate(6);
259    m_i2c.read(kDataRegister, 6, rawData);
260
261    // Sensor is little endian... swap bytes
262    rawData.order(ByteOrder.LITTLE_ENDIAN);
263    data.XAxis = rawData.getShort(0) * kGsPerLSB;
264    data.YAxis = rawData.getShort(2) * kGsPerLSB;
265    data.ZAxis = rawData.getShort(4) * kGsPerLSB;
266    return data;
267  }
268
269  @Override
270  public void initSendable(NTSendableBuilder builder) {
271    builder.setSmartDashboardType("3AxisAccelerometer");
272    DoublePublisher pubX = new DoubleTopic(builder.getTopic("X")).publish();
273    DoublePublisher pubY = new DoubleTopic(builder.getTopic("Y")).publish();
274    DoublePublisher pubZ = new DoubleTopic(builder.getTopic("Z")).publish();
275    builder.addCloseable(pubX);
276    builder.addCloseable(pubY);
277    builder.addCloseable(pubZ);
278    builder.setUpdateTable(
279        () -> {
280          AllAxes data = getAccelerations();
281          pubX.set(data.XAxis);
282          pubY.set(data.YAxis);
283          pubZ.set(data.ZAxis);
284        });
285  }
286}