package com.team254.lib.util.gyro; import com.team254.lib.util.Util; import edu.wpi.first.wpilibj.SPI; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Deals with the SPI interface to the gyro. Datasheet for the command used here can be found at: * "http://www.analog.com/static/imported-files/data_sheets/ADXRS453.pdf */ public class GyroInterface { // states according to ST1/ST0 bits public static enum StatusFlag { INVALID_DATA, VALID_DATA, SELF_TEST_DATA, RW_RESPONSE, } public static enum ErrorFlag { PLL_FAILURE(7), QUADRATURE_ERROR(6), NONVOLATILE_MEMORY_FAULT(5), RESET_INITIALIZE_FAILURE(4), POWER_FAILURE(3), CONTINUOUS_SELF_SELF_FAILURE(2), GENERATED_FAULT(1); private final int mBit; ErrorFlag(int bit) { mBit = bit; } } private final List<ErrorFlag> ALL_ERRORS = Arrays.asList(ErrorFlag.values()); private final SPI mSPI; private static final int SENSOR_DATA_CMD = 0x20000000; private static final int CHK_GENERATE_FAULTS_BIT = 0x02; public GyroInterface() { mSPI = new SPI(SPI.Port.kOnboardCS0); mSPI.setClockRate(4_000_000); mSPI.setChipSelectActiveLow(); mSPI.setClockActiveHigh(); mSPI.setSampleDataOnRising(); mSPI.setMSBFirst(); } /** * Initializes the gyro * * @throws GyroException If the initialization routine fails for any reason */ public void initializeGyro() throws GyroException { // start a self-check int result = doTransaction(SENSOR_DATA_CMD | CHK_GENERATE_FAULTS_BIT); if (result != 1) { System.out.println( "Unexpected self-check response: 0x" + Integer.toHexString(result) + " errors: " + Util.joinStrings(", ", extractErrors(result))); } // wait for the fault conditions to occur try { Thread.sleep(50); } catch (InterruptedException ignored) { } // clear latched non-fault data doTransaction(SENSOR_DATA_CMD); // actually read the self-test data int selfCheckResult = doTransaction(SENSOR_DATA_CMD); if (extractStatus(selfCheckResult) != StatusFlag.SELF_TEST_DATA) { throw new GyroException("Gyro not in self test: 0x" + Integer.toHexString(selfCheckResult)); } if (!extractErrors(selfCheckResult).containsAll(ALL_ERRORS)) { throw new GyroException( "Gyro self-test didn't include all errors: 0x" + Integer.toHexString(selfCheckResult)); } // clear the latched self-test data selfCheckResult = doTransaction(SENSOR_DATA_CMD); if (extractStatus(selfCheckResult) != StatusFlag.SELF_TEST_DATA) { throw new GyroException("Gyro second self test read failed: 0x" + Integer.toHexString(selfCheckResult)); } } public short doRead(byte address) { int command = (0x8 << 28) | (address << 17); while (true) { int result; try { result = doTransaction(command); } catch (GyroException e) { e.printStackTrace(System.out); continue; } if ((result & 0xEFE00000) != 0x4E000000) { System.out.println("Unexpected gyro read response: 0x" + Integer.toHexString(result) + " ... retrying"); continue; } return (short) ((result >> 5) & 0xFFFF); } } public static double extractAngleRate(int result) { short reading = (short) ((result >> 10) & 0xFFFF); return reading * 2.0 * Math.PI / 360.0 / 80.0; } public short readPartId() { return doRead((byte) 0x0C); } public int readSerialNumber() { return (((int) doRead((byte) 0x0E)) << 16) | (int) doRead((byte) 0x10); } public int getReading() throws GyroException { return doTransaction(SENSOR_DATA_CMD); } /** * @param command The word to write * @return The result of the transaction * @throws GyroInterface.GyroException if the transaction fails */ private int doTransaction(int command) throws GyroException { // ensure the parity bit if (!isOddParity(command & ~0x01)) { command |= 0x01; } ByteBuffer resultBuffer = ByteBuffer.allocate(4); int transactionSize = mSPI.transaction(ByteBuffer.allocate(4).putInt(command).array(), resultBuffer.array(), 4); if (transactionSize != 4) { throw new GyroException("Transaction failed with size: " + transactionSize); } int result = resultBuffer.getInt(0); // check the high-byte parity of the response if (!isOddParity(result & 0xffff0000)) { throw new GyroException("High bytes parity failure"); } if (!isOddParity(result)) { throw new GyroException("Whole word parity failure"); } return result; } private static boolean isOddParity(int word) { boolean isOdd = false; for (int i = 0; i < 32; ++i) { if ((word & (1 << i)) != 0) { isOdd = !isOdd; } } return isOdd; } public static StatusFlag extractStatus(int result) { int stBits = (result >> 26) & 0b11; switch (stBits) { case 0b00: return StatusFlag.INVALID_DATA; case 0b01: return StatusFlag.VALID_DATA; case 0b10: return StatusFlag.SELF_TEST_DATA; case 0b11: return StatusFlag.RW_RESPONSE; default: throw new RuntimeException("wtf"); } } public static List<ErrorFlag> extractErrors(int result) { ArrayList<ErrorFlag> errors = new ArrayList<ErrorFlag>(ErrorFlag.values().length); for (ErrorFlag errorFlag : ErrorFlag.values()) { if ((result & (1 << errorFlag.mBit)) != 0) { errors.add(errorFlag); } } return errors; } public class GyroException extends Exception { public GyroException(String s) { super(s); } } }