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