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}