001/*----------------------------------------------------------------------------*/
002/* Copyright (c) 2008-2018 FIRST. All Rights Reserved.                        */
003/* Open Source Software - may be modified and shared by FRC teams. The code   */
004/* must be accompanied by the FIRST BSD license file in the root directory of */
005/* the project.                                                               */
006/*----------------------------------------------------------------------------*/
007
008package edu.wpi.first.wpilibj;
009
010import java.nio.ByteBuffer;
011import java.nio.ByteOrder;
012
013import edu.wpi.first.networktables.NetworkTableEntry;
014import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType;
015import edu.wpi.first.wpilibj.hal.HAL;
016import edu.wpi.first.wpilibj.interfaces.Accelerometer;
017import edu.wpi.first.wpilibj.smartdashboard.SendableBuilder;
018
019/**
020 * ADXL362 SPI Accelerometer.
021 *
022 * <p>This class allows access to an Analog Devices ADXL362 3-axis accelerometer.
023 */
024@SuppressWarnings("PMD.UnusedPrivateField")
025public class ADXL362 extends SensorBase implements Accelerometer, Sendable {
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  private static final byte kPowerCtl_AutoSleep = 0x04;
041  private static final byte kPowerCtl_Measure = 0x02;
042
043  public enum Axes {
044    kX((byte) 0x00),
045    kY((byte) 0x02),
046    kZ((byte) 0x04);
047
048    @SuppressWarnings("MemberName")
049    public final byte value;
050
051    Axes(byte value) {
052      this.value = value;
053    }
054  }
055
056  @SuppressWarnings("MemberName")
057  public static class AllAxes {
058    public double XAxis;
059    public double YAxis;
060    public double ZAxis;
061  }
062
063  private SPI m_spi;
064  private double m_gsPerLSB;
065
066  /**
067   * Constructor.  Uses the onboard CS1.
068   *
069   * @param range The range (+ or -) that the accelerometer will measure.
070   */
071  public ADXL362(Range range) {
072    this(SPI.Port.kOnboardCS1, range);
073  }
074
075  /**
076   * Constructor.
077   *
078   * @param port  The SPI port that the accelerometer is connected to
079   * @param range The range (+ or -) that the accelerometer will measure.
080   */
081  public ADXL362(SPI.Port port, Range range) {
082    m_spi = new SPI(port);
083
084    m_spi.setClockRate(3000000);
085    m_spi.setMSBFirst();
086    m_spi.setSampleDataOnFalling();
087    m_spi.setClockActiveLow();
088    m_spi.setChipSelectActiveLow();
089
090    // Validate the part ID
091    ByteBuffer transferBuffer = ByteBuffer.allocate(3);
092    transferBuffer.put(0, kRegRead);
093    transferBuffer.put(1, kPartIdRegister);
094    m_spi.transaction(transferBuffer, transferBuffer, 3);
095    if (transferBuffer.get(2) != (byte) 0xF2) {
096      m_spi.free();
097      m_spi = null;
098      DriverStation.reportError("could not find ADXL362 on SPI port " + port.value, false);
099      return;
100    }
101
102    setRange(range);
103
104    // Turn on the measurements
105    transferBuffer.put(0, kRegWrite);
106    transferBuffer.put(1, kPowerCtlRegister);
107    transferBuffer.put(2, (byte) (kPowerCtl_Measure | kPowerCtl_UltraLowNoise));
108    m_spi.write(transferBuffer, 3);
109
110    HAL.report(tResourceType.kResourceType_ADXL362, port.value);
111    setName("ADXL362", port.value);
112  }
113
114  @Override
115  public void free() {
116    super.free();
117    if (m_spi != null) {
118      m_spi.free();
119      m_spi = null;
120    }
121  }
122
123  @Override
124  public void setRange(Range range) {
125    if (m_spi == null) {
126      return;
127    }
128
129    final byte value;
130    switch (range) {
131      case k2G:
132        value = kFilterCtl_Range2G;
133        m_gsPerLSB = 0.001;
134        break;
135      case k4G:
136        value = kFilterCtl_Range4G;
137        m_gsPerLSB = 0.002;
138        break;
139      case k8G:
140      case k16G:  // 16G not supported; treat as 8G
141        value = kFilterCtl_Range8G;
142        m_gsPerLSB = 0.004;
143        break;
144      default:
145        throw new IllegalArgumentException(range + " unsupported");
146
147    }
148
149    // Specify the data format to read
150    byte[] commands = new byte[]{kRegWrite, kFilterCtlRegister, (byte) (kFilterCtl_ODR_100Hz
151        | value)};
152    m_spi.write(commands, commands.length);
153  }
154
155
156  @Override
157  public double getX() {
158    return getAcceleration(Axes.kX);
159  }
160
161  @Override
162  public double getY() {
163    return getAcceleration(Axes.kY);
164  }
165
166  @Override
167  public double getZ() {
168    return getAcceleration(Axes.kZ);
169  }
170
171  /**
172   * Get the acceleration of one axis in Gs.
173   *
174   * @param axis The axis to read from.
175   * @return Acceleration of the ADXL362 in Gs.
176   */
177  public double getAcceleration(ADXL362.Axes axis) {
178    if (m_spi == null) {
179      return 0.0;
180    }
181    ByteBuffer transferBuffer = ByteBuffer.allocate(4);
182    transferBuffer.put(0, kRegRead);
183    transferBuffer.put(1, (byte) (kDataRegister + axis.value));
184    m_spi.transaction(transferBuffer, transferBuffer, 4);
185    // Sensor is little endian
186    transferBuffer.order(ByteOrder.LITTLE_ENDIAN);
187
188    return transferBuffer.getShort(2) * m_gsPerLSB;
189  }
190
191  /**
192   * Get the acceleration of all axes in Gs.
193   *
194   * @return An object containing the acceleration measured on each axis of the ADXL362 in Gs.
195   */
196  public ADXL362.AllAxes getAccelerations() {
197    ADXL362.AllAxes data = new ADXL362.AllAxes();
198    if (m_spi != null) {
199      ByteBuffer dataBuffer = ByteBuffer.allocate(8);
200      // Select the data address.
201      dataBuffer.put(0, kRegRead);
202      dataBuffer.put(1, kDataRegister);
203      m_spi.transaction(dataBuffer, dataBuffer, 8);
204      // Sensor is little endian... swap bytes
205      dataBuffer.order(ByteOrder.LITTLE_ENDIAN);
206
207      data.XAxis = dataBuffer.getShort(2) * m_gsPerLSB;
208      data.YAxis = dataBuffer.getShort(4) * m_gsPerLSB;
209      data.ZAxis = dataBuffer.getShort(6) * m_gsPerLSB;
210    }
211    return data;
212  }
213
214  @Override
215  public void initSendable(SendableBuilder builder) {
216    builder.setSmartDashboardType("3AxisAccelerometer");
217    NetworkTableEntry entryX = builder.getEntry("X");
218    NetworkTableEntry entryY = builder.getEntry("Y");
219    NetworkTableEntry entryZ = builder.getEntry("Z");
220    builder.setUpdateTable(() -> {
221      AllAxes data = getAccelerations();
222      entryX.setDouble(data.XAxis);
223      entryY.setDouble(data.YAxis);
224      entryZ.setDouble(data.ZAxis);
225    });
226  }
227}