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.SimBoolean;
010import edu.wpi.first.hal.SimDevice;
011import edu.wpi.first.hal.SimDouble;
012import edu.wpi.first.util.sendable.Sendable;
013import edu.wpi.first.util.sendable.SendableBuilder;
014import edu.wpi.first.util.sendable.SendableRegistry;
015import edu.wpi.first.wpilibj.interfaces.Gyro;
016import java.nio.ByteBuffer;
017import java.nio.ByteOrder;
018
019/**
020 * Use a rate gyro to return the robots heading relative to a starting position. The Gyro class
021 * tracks the robots heading based on the starting position. As the robot rotates the new heading is
022 * computed by integrating the rate of rotation returned by the sensor. When the class is
023 * instantiated, it does a short calibration routine where it samples the gyro while at rest to
024 * determine the default offset. This is subtracted from each sample to determine the heading.
025 *
026 * <p>This class is for the digital ADXRS450 gyro sensor that connects via SPI. Only one instance of
027 * an ADXRS Gyro is supported.
028 */
029@SuppressWarnings({"TypeName", "PMD.UnusedPrivateField"})
030public class ADXRS450_Gyro implements Gyro, Sendable {
031  private static final double kSamplePeriod = 0.0005;
032  private static final double kCalibrationSampleTime = 5.0;
033  private static final double kDegreePerSecondPerLSB = 0.0125;
034
035  private static final int kRateRegister = 0x00;
036  private static final int kTemRegister = 0x02;
037  private static final int kLoCSTRegister = 0x04;
038  private static final int kHiCSTRegister = 0x06;
039  private static final int kQuadRegister = 0x08;
040  private static final int kFaultRegister = 0x0A;
041  private static final int kPIDRegister = 0x0C;
042  private static final int kSNHighRegister = 0x0E;
043  private static final int kSNLowRegister = 0x10;
044
045  private SPI m_spi;
046
047  private SimDevice m_simDevice;
048  private SimBoolean m_simConnected;
049  private SimDouble m_simAngle;
050  private SimDouble m_simRate;
051
052  /** Constructor. Uses the onboard CS0. */
053  public ADXRS450_Gyro() {
054    this(SPI.Port.kOnboardCS0);
055  }
056
057  /**
058   * Constructor.
059   *
060   * @param port The SPI port that the gyro is connected to
061   */
062  public ADXRS450_Gyro(SPI.Port port) {
063    m_spi = new SPI(port);
064
065    // simulation
066    m_simDevice = SimDevice.create("Gyro:ADXRS450", port.value);
067    if (m_simDevice != null) {
068      m_simConnected = m_simDevice.createBoolean("connected", SimDevice.Direction.kInput, true);
069      m_simAngle = m_simDevice.createDouble("angle_x", SimDevice.Direction.kInput, 0.0);
070      m_simRate = m_simDevice.createDouble("rate_x", SimDevice.Direction.kInput, 0.0);
071    }
072
073    m_spi.setClockRate(3000000);
074    m_spi.setMode(SPI.Mode.kMode0);
075    m_spi.setChipSelectActiveLow();
076
077    if (m_simDevice == null) {
078      // Validate the part ID
079      if ((readRegister(kPIDRegister) & 0xff00) != 0x5200) {
080        m_spi.close();
081        m_spi = null;
082        DriverStation.reportError("could not find ADXRS450 gyro on SPI port " + port.value, false);
083        return;
084      }
085
086      m_spi.initAccumulator(
087          kSamplePeriod, 0x20000000, 4, 0x0c00000e, 0x04000000, 10, 16, true, true);
088
089      calibrate();
090    }
091
092    HAL.report(tResourceType.kResourceType_ADXRS450, port.value + 1);
093    SendableRegistry.addLW(this, "ADXRS450_Gyro", port.value);
094  }
095
096  /**
097   * Determine if the gyro is connected.
098   *
099   * @return true if it is connected, false otherwise.
100   */
101  public boolean isConnected() {
102    if (m_simConnected != null) {
103      return m_simConnected.get();
104    }
105    return m_spi != null;
106  }
107
108  @Override
109  public void calibrate() {
110    if (m_spi == null) {
111      return;
112    }
113
114    Timer.delay(0.1);
115
116    m_spi.setAccumulatorIntegratedCenter(0);
117    m_spi.resetAccumulator();
118
119    Timer.delay(kCalibrationSampleTime);
120
121    m_spi.setAccumulatorIntegratedCenter(m_spi.getAccumulatorIntegratedAverage());
122    m_spi.resetAccumulator();
123  }
124
125  /**
126   * Get the SPI port number.
127   *
128   * @return The SPI port number.
129   */
130  public int getPort() {
131    return m_spi.getPort();
132  }
133
134  private boolean calcParity(int value) {
135    boolean parity = false;
136    while (value != 0) {
137      parity = !parity;
138      value = value & (value - 1);
139    }
140    return parity;
141  }
142
143  private int readRegister(int reg) {
144    int cmdhi = 0x8000 | (reg << 1);
145    boolean parity = calcParity(cmdhi);
146
147    ByteBuffer buf = ByteBuffer.allocate(4);
148    buf.order(ByteOrder.BIG_ENDIAN);
149    buf.put(0, (byte) (cmdhi >> 8));
150    buf.put(1, (byte) (cmdhi & 0xff));
151    buf.put(2, (byte) 0);
152    buf.put(3, (byte) (parity ? 0 : 1));
153
154    m_spi.write(buf, 4);
155    m_spi.read(false, buf, 4);
156
157    if ((buf.get(0) & 0xe0) == 0) {
158      return 0; // error, return 0
159    }
160    return (buf.getInt(0) >> 5) & 0xffff;
161  }
162
163  @Override
164  public void reset() {
165    if (m_simAngle != null) {
166      m_simAngle.reset();
167    }
168    if (m_spi != null) {
169      m_spi.resetAccumulator();
170    }
171  }
172
173  /** Delete (free) the spi port used for the gyro and stop accumulating. */
174  @Override
175  public void close() {
176    SendableRegistry.remove(this);
177    if (m_spi != null) {
178      m_spi.close();
179      m_spi = null;
180    }
181    if (m_simDevice != null) {
182      m_simDevice.close();
183      m_simDevice = null;
184    }
185  }
186
187  @Override
188  public double getAngle() {
189    if (m_simAngle != null) {
190      return m_simAngle.get();
191    }
192    if (m_spi == null) {
193      return 0.0;
194    }
195    return m_spi.getAccumulatorIntegratedValue() * kDegreePerSecondPerLSB;
196  }
197
198  @Override
199  public double getRate() {
200    if (m_simRate != null) {
201      return m_simRate.get();
202    }
203    if (m_spi == null) {
204      return 0.0;
205    }
206    return m_spi.getAccumulatorLastValue() * kDegreePerSecondPerLSB;
207  }
208
209  @Override
210  public void initSendable(SendableBuilder builder) {
211    builder.setSmartDashboardType("Gyro");
212    builder.addDoubleProperty("Value", this::getAngle, null);
213  }
214}