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 edu.wpi.first.wpilibj;
006
007import edu.wpi.first.hal.FRCNetComm.tResourceType;
008import edu.wpi.first.hal.HAL;
009import edu.wpi.first.hal.SimDevice;
010import edu.wpi.first.hal.SimDouble;
011import edu.wpi.first.hal.SimEnum;
012import edu.wpi.first.networktables.NTSendable;
013import edu.wpi.first.networktables.NTSendableBuilder;
014import edu.wpi.first.networktables.NetworkTableEntry;
015import edu.wpi.first.util.sendable.SendableRegistry;
016import edu.wpi.first.wpilibj.interfaces.Accelerometer;
017import java.nio.ByteBuffer;
018import java.nio.ByteOrder;
019
020/**
021 * ADXL362 SPI Accelerometer.
022 *
023 * <p>This class allows access to an Analog Devices ADXL362 3-axis accelerometer.
024 */
025public class ADXL362 implements Accelerometer, NTSendable, AutoCloseable {
026  private static final byte kRegWrite = 0x0A;
027  private static final byte kRegRead = 0x0B;
028
029  private static final byte kPartIdRegister = 0x02;
030  private static final byte kDataRegister = 0x0E;
031  private static final byte kFilterCtlRegister = 0x2C;
032  private static final byte kPowerCtlRegister = 0x2D;
033
034  private static final byte kFilterCtl_Range2G = 0x00;
035  private static final byte kFilterCtl_Range4G = 0x40;
036  private static final byte kFilterCtl_Range8G = (byte) 0x80;
037  private static final byte kFilterCtl_ODR_100Hz = 0x03;
038
039  private static final byte kPowerCtl_UltraLowNoise = 0x20;
040
041  @SuppressWarnings("PMD.UnusedPrivateField")
042  private static final byte kPowerCtl_AutoSleep = 0x04;
043
044  private static final byte kPowerCtl_Measure = 0x02;
045
046  public enum Axes {
047    kX((byte) 0x00),
048    kY((byte) 0x02),
049    kZ((byte) 0x04);
050
051    public final byte value;
052
053    Axes(byte value) {
054      this.value = value;
055    }
056  }
057
058  @SuppressWarnings("MemberName")
059  public static class AllAxes {
060    public double XAxis;
061    public double YAxis;
062    public double ZAxis;
063  }
064
065  private SPI m_spi;
066
067  private SimDevice m_simDevice;
068  private SimEnum m_simRange;
069  private SimDouble m_simX;
070  private SimDouble m_simY;
071  private SimDouble m_simZ;
072
073  private double m_gsPerLSB;
074
075  /**
076   * Constructor. Uses the onboard CS1.
077   *
078   * @param range The range (+ or -) that the accelerometer will measure.
079   */
080  public ADXL362(Range range) {
081    this(SPI.Port.kOnboardCS1, range);
082  }
083
084  /**
085   * Constructor.
086   *
087   * @param port The SPI port that the accelerometer is connected to
088   * @param range The range (+ or -) that the accelerometer will measure.
089   */
090  public ADXL362(SPI.Port port, Range range) {
091    m_spi = new SPI(port);
092
093    // simulation
094    m_simDevice = SimDevice.create("Accel:ADXL362", port.value);
095    if (m_simDevice != null) {
096      m_simRange =
097          m_simDevice.createEnumDouble(
098              "range",
099              SimDevice.Direction.kOutput,
100              new String[] {"2G", "4G", "8G", "16G"},
101              new double[] {2.0, 4.0, 8.0, 16.0},
102              0);
103      m_simX = m_simDevice.createDouble("x", SimDevice.Direction.kInput, 0.0);
104      m_simY = m_simDevice.createDouble("y", SimDevice.Direction.kInput, 0.0);
105      m_simZ = m_simDevice.createDouble("z", SimDevice.Direction.kInput, 0.0);
106    }
107
108    m_spi.setClockRate(3000000);
109    m_spi.setMSBFirst();
110    m_spi.setSampleDataOnTrailingEdge();
111    m_spi.setClockActiveLow();
112    m_spi.setChipSelectActiveLow();
113
114    ByteBuffer transferBuffer = ByteBuffer.allocate(3);
115    if (m_simDevice == null) {
116      // Validate the part ID
117      transferBuffer.put(0, kRegRead);
118      transferBuffer.put(1, kPartIdRegister);
119      m_spi.transaction(transferBuffer, transferBuffer, 3);
120      if (transferBuffer.get(2) != (byte) 0xF2) {
121        m_spi.close();
122        m_spi = null;
123        DriverStation.reportError("could not find ADXL362 on SPI port " + port.value, false);
124        return;
125      }
126    }
127
128    setRange(range);
129
130    // Turn on the measurements
131    transferBuffer.put(0, kRegWrite);
132    transferBuffer.put(1, kPowerCtlRegister);
133    transferBuffer.put(2, (byte) (kPowerCtl_Measure | kPowerCtl_UltraLowNoise));
134    m_spi.write(transferBuffer, 3);
135
136    HAL.report(tResourceType.kResourceType_ADXL362, port.value + 1);
137    SendableRegistry.addLW(this, "ADXL362", port.value);
138  }
139
140  public int getPort() {
141    return m_spi.getPort();
142  }
143
144  @Override
145  public void close() {
146    SendableRegistry.remove(this);
147    if (m_spi != null) {
148      m_spi.close();
149      m_spi = null;
150    }
151    if (m_simDevice != null) {
152      m_simDevice.close();
153      m_simDevice = null;
154    }
155  }
156
157  @Override
158  public void setRange(Range range) {
159    if (m_spi == null) {
160      return;
161    }
162
163    final byte value;
164    switch (range) {
165      case k2G:
166        value = kFilterCtl_Range2G;
167        m_gsPerLSB = 0.001;
168        break;
169      case k4G:
170        value = kFilterCtl_Range4G;
171        m_gsPerLSB = 0.002;
172        break;
173      case k8G:
174      case k16G: // 16G not supported; treat as 8G
175        value = kFilterCtl_Range8G;
176        m_gsPerLSB = 0.004;
177        break;
178      default:
179        throw new IllegalArgumentException(range + " unsupported");
180    }
181
182    // Specify the data format to read
183    byte[] commands =
184        new byte[] {kRegWrite, kFilterCtlRegister, (byte) (kFilterCtl_ODR_100Hz | value)};
185    m_spi.write(commands, commands.length);
186
187    if (m_simRange != null) {
188      m_simRange.set(value);
189    }
190  }
191
192  @Override
193  public double getX() {
194    return getAcceleration(Axes.kX);
195  }
196
197  @Override
198  public double getY() {
199    return getAcceleration(Axes.kY);
200  }
201
202  @Override
203  public double getZ() {
204    return getAcceleration(Axes.kZ);
205  }
206
207  /**
208   * Get the acceleration of one axis in Gs.
209   *
210   * @param axis The axis to read from.
211   * @return Acceleration of the ADXL362 in Gs.
212   */
213  public double getAcceleration(ADXL362.Axes axis) {
214    if (axis == Axes.kX && m_simX != null) {
215      return m_simX.get();
216    }
217    if (axis == Axes.kY && m_simY != null) {
218      return m_simY.get();
219    }
220    if (axis == Axes.kZ && m_simZ != null) {
221      return m_simZ.get();
222    }
223    if (m_spi == null) {
224      return 0.0;
225    }
226    ByteBuffer transferBuffer = ByteBuffer.allocate(4);
227    transferBuffer.put(0, kRegRead);
228    transferBuffer.put(1, (byte) (kDataRegister + axis.value));
229    m_spi.transaction(transferBuffer, transferBuffer, 4);
230    // Sensor is little endian
231    transferBuffer.order(ByteOrder.LITTLE_ENDIAN);
232
233    return transferBuffer.getShort(2) * m_gsPerLSB;
234  }
235
236  /**
237   * Get the acceleration of all axes in Gs.
238   *
239   * @return An object containing the acceleration measured on each axis of the ADXL362 in Gs.
240   */
241  public ADXL362.AllAxes getAccelerations() {
242    ADXL362.AllAxes data = new ADXL362.AllAxes();
243    if (m_simX != null && m_simY != null && m_simZ != null) {
244      data.XAxis = m_simX.get();
245      data.YAxis = m_simY.get();
246      data.ZAxis = m_simZ.get();
247      return data;
248    }
249    if (m_spi != null) {
250      ByteBuffer dataBuffer = ByteBuffer.allocate(8);
251      // Select the data address.
252      dataBuffer.put(0, kRegRead);
253      dataBuffer.put(1, kDataRegister);
254      m_spi.transaction(dataBuffer, dataBuffer, 8);
255      // Sensor is little endian... swap bytes
256      dataBuffer.order(ByteOrder.LITTLE_ENDIAN);
257
258      data.XAxis = dataBuffer.getShort(2) * m_gsPerLSB;
259      data.YAxis = dataBuffer.getShort(4) * m_gsPerLSB;
260      data.ZAxis = dataBuffer.getShort(6) * m_gsPerLSB;
261    }
262    return data;
263  }
264
265  @Override
266  public void initSendable(NTSendableBuilder builder) {
267    builder.setSmartDashboardType("3AxisAccelerometer");
268    NetworkTableEntry entryX = builder.getEntry("X");
269    NetworkTableEntry entryY = builder.getEntry("Y");
270    NetworkTableEntry entryZ = builder.getEntry("Z");
271    builder.setUpdateTable(
272        () -> {
273          AllAxes data = getAccelerations();
274          entryX.setDouble(data.XAxis);
275          entryY.setDouble(data.YAxis);
276          entryZ.setDouble(data.ZAxis);
277        });
278  }
279}