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.tInstances;
015import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType;
016import edu.wpi.first.wpilibj.hal.HAL;
017import edu.wpi.first.wpilibj.interfaces.Accelerometer;
018import edu.wpi.first.wpilibj.smartdashboard.SendableBuilder;
019
020/**
021 * ADXL345 SPI Accelerometer.
022 */
023@SuppressWarnings({"TypeName", "PMD.UnusedPrivateField"})
024public class ADXL345_SPI extends SensorBase implements Accelerometer, Sendable {
025  private static final int kPowerCtlRegister = 0x2D;
026  private static final int kDataFormatRegister = 0x31;
027  private static final int kDataRegister = 0x32;
028  private static final double kGsPerLSB = 0.00390625;
029
030  private static final int kAddress_Read = 0x80;
031  private static final int kAddress_MultiByte = 0x40;
032
033  private static final int kPowerCtl_Link = 0x20;
034  private static final int kPowerCtl_AutoSleep = 0x10;
035  private static final int kPowerCtl_Measure = 0x08;
036  private static final int kPowerCtl_Sleep = 0x04;
037
038  private static final int kDataFormat_SelfTest = 0x80;
039  private static final int kDataFormat_SPI = 0x40;
040  private static final int kDataFormat_IntInvert = 0x20;
041  private static final int kDataFormat_FullRes = 0x08;
042  private static final int kDataFormat_Justify = 0x04;
043
044  public enum Axes {
045    kX((byte) 0x00),
046    kY((byte) 0x02),
047    kZ((byte) 0x04);
048
049    /**
050     * The integer value representing this enumeration.
051     */
052    @SuppressWarnings("MemberName")
053    public final byte value;
054
055    Axes(byte value) {
056      this.value = value;
057    }
058  }
059
060  @SuppressWarnings("MemberName")
061  public static class AllAxes {
062    public double XAxis;
063    public double YAxis;
064    public double ZAxis;
065  }
066
067  protected SPI m_spi;
068
069  /**
070   * Constructor.
071   *
072   * @param port  The SPI port that the accelerometer is connected to
073   * @param range The range (+ or -) that the accelerometer will measure.
074   */
075  public ADXL345_SPI(SPI.Port port, Range range) {
076    m_spi = new SPI(port);
077    init(range);
078    setName("ADXL345_SPI", port.value);
079  }
080
081  @Override
082  public void free() {
083    super.free();
084    m_spi.free();
085  }
086
087  /**
088   * Set SPI bus parameters, bring device out of sleep and set format.
089   *
090   * @param range The range (+ or -) that the accelerometer will measure.
091   */
092  private void init(Range range) {
093    m_spi.setClockRate(500000);
094    m_spi.setMSBFirst();
095    m_spi.setSampleDataOnFalling();
096    m_spi.setClockActiveLow();
097    m_spi.setChipSelectActiveHigh();
098
099    // Turn on the measurements
100    byte[] commands = new byte[2];
101    commands[0] = kPowerCtlRegister;
102    commands[1] = kPowerCtl_Measure;
103    m_spi.write(commands, 2);
104
105    setRange(range);
106
107    HAL.report(tResourceType.kResourceType_ADXL345, tInstances.kADXL345_SPI);
108  }
109
110  @Override
111  public void setRange(Range range) {
112    final byte value;
113
114    switch (range) {
115      case k2G:
116        value = 0;
117        break;
118      case k4G:
119        value = 1;
120        break;
121      case k8G:
122        value = 2;
123        break;
124      case k16G:
125        value = 3;
126        break;
127      default:
128        throw new IllegalArgumentException(range + " unsupported");
129    }
130
131    // Specify the data format to read
132    byte[] commands = new byte[]{kDataFormatRegister, (byte) (kDataFormat_FullRes | value)};
133    m_spi.write(commands, commands.length);
134  }
135
136  @Override
137  public double getX() {
138    return getAcceleration(Axes.kX);
139  }
140
141  @Override
142  public double getY() {
143    return getAcceleration(Axes.kY);
144  }
145
146  @Override
147  public double getZ() {
148    return getAcceleration(Axes.kZ);
149  }
150
151  /**
152   * Get the acceleration of one axis in Gs.
153   *
154   * @param axis The axis to read from.
155   * @return Acceleration of the ADXL345 in Gs.
156   */
157  public double getAcceleration(ADXL345_SPI.Axes axis) {
158    ByteBuffer transferBuffer = ByteBuffer.allocate(3);
159    transferBuffer.put(0,
160        (byte) ((kAddress_Read | kAddress_MultiByte | kDataRegister) + axis.value));
161    m_spi.transaction(transferBuffer, transferBuffer, 3);
162    // Sensor is little endian
163    transferBuffer.order(ByteOrder.LITTLE_ENDIAN);
164
165    return transferBuffer.getShort(1) * kGsPerLSB;
166  }
167
168  /**
169   * Get the acceleration of all axes in Gs.
170   *
171   * @return An object containing the acceleration measured on each axis of the ADXL345 in Gs.
172   */
173  public ADXL345_SPI.AllAxes getAccelerations() {
174    ADXL345_SPI.AllAxes data = new ADXL345_SPI.AllAxes();
175    if (m_spi != null) {
176      ByteBuffer dataBuffer = ByteBuffer.allocate(7);
177      // Select the data address.
178      dataBuffer.put(0, (byte) (kAddress_Read | kAddress_MultiByte | kDataRegister));
179      m_spi.transaction(dataBuffer, dataBuffer, 7);
180      // Sensor is little endian... swap bytes
181      dataBuffer.order(ByteOrder.LITTLE_ENDIAN);
182
183      data.XAxis = dataBuffer.getShort(1) * kGsPerLSB;
184      data.YAxis = dataBuffer.getShort(3) * kGsPerLSB;
185      data.ZAxis = dataBuffer.getShort(5) * kGsPerLSB;
186    }
187    return data;
188  }
189
190  @Override
191  public void initSendable(SendableBuilder builder) {
192    builder.setSmartDashboardType("3AxisAccelerometer");
193    NetworkTableEntry entryX = builder.getEntry("X");
194    NetworkTableEntry entryY = builder.getEntry("Y");
195    NetworkTableEntry entryZ = builder.getEntry("Z");
196    builder.setUpdateTable(() -> {
197      AllAxes data = getAccelerations();
198      entryX.setDouble(data.XAxis);
199      entryY.setDouble(data.YAxis);
200      entryZ.setDouble(data.ZAxis);
201    });
202  }
203}