package com.team254.lib.util.gyro; import com.team254.lib.util.Util; import edu.wpi.first.wpilibj.Notifier; import edu.wpi.first.wpilibj.TimerEventHandler; import java.util.List; import java.util.Timer; import java.util.TimerTask; /** * Thread which is responsible for reading the gyro */ public class GyroThread { private static final int K_READING_RATE = 200; private static final int K_ZEROING_SAMPLES = 6 * K_READING_RATE; private static final int K_STARTUP_SAMPLES = 2 * K_READING_RATE; // synchronized access object private final Timer mTimer = new Timer("Gyro"); // owned by the background thread private final GyroInterface mGyroInterface = new GyroInterface(); private UpdateHandler mUpdateHandler = new UpdateHandler(); private Notifier mNotifier = new Notifier(mUpdateHandler, this); // thread communication variables private volatile boolean mVolatileHasData = false; private volatile double mVolatileAngle = 0; private volatile double mVolatileRate = 0; private volatile boolean mVolatileShouldReZero = true; // owned by the accesser of this object private double mZeroHeading = 0; public void start() { synchronized (mTimer) { mTimer.schedule(new InitTask(), 0); } } public boolean hasData() { return mVolatileHasData; } public double getAngle() { return mVolatileAngle - mZeroHeading; } public double getRate() { return mVolatileRate; } public void rezero() { mVolatileShouldReZero = true; } public void reset() { mZeroHeading = mVolatileAngle; } /** * Initializes the gyro, verifying the its self-test results */ private class InitTask extends TimerTask { @Override public void run() { boolean initialized = false; while (!initialized) { try { mGyroInterface.initializeGyro(); initialized = true; } catch (GyroInterface.GyroException e) { System.out.println("Gyro failed to initialize: " + e.getMessage()); } if (!initialized) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println("gyo initialized, part ID: 0x" + Integer.toHexString(mGyroInterface.readPartId())); synchronized (mNotifier) { mNotifier.startPeriodic(1.0 / K_READING_RATE); } } } /** * Updates the actual gyro data (zeroing and accumulation) */ private class UpdateHandler implements TimerEventHandler { private int mRemainingStartupCycles = K_STARTUP_SAMPLES; private boolean mIsZerod = false; private double[] mZeroRateSamples = new double[K_ZEROING_SAMPLES]; private int mZeroRateSampleIndex = 0; private boolean mHasEnoughZeroingSamples; private double mZeroBias; private double mAngle = 0; private double mLastTime = 0; @Override public void update(Object param) { int reading; try { reading = mGyroInterface.getReading(); } catch (GyroInterface.GyroException e) { System.out.println("Gyro read failed: " + e.getMessage()); return; } GyroInterface.StatusFlag status = GyroInterface.extractStatus(reading); List<GyroInterface.ErrorFlag> errors = GyroInterface.extractErrors(reading); if (GyroInterface.StatusFlag.VALID_DATA != status || !errors.isEmpty()) { System.out.println("Gyro read failed. Status: " + status + ". Errors: " + Util.joinStrings(", ", errors)); return; } if (mRemainingStartupCycles > 0) { mRemainingStartupCycles--; return; } if (mVolatileShouldReZero) { mVolatileShouldReZero = false; mVolatileHasData = false; mIsZerod = false; } double unbiasedAngleRate = GyroInterface.extractAngleRate(reading); mZeroRateSamples[mZeroRateSampleIndex] = unbiasedAngleRate; mZeroRateSampleIndex++; if (mZeroRateSampleIndex >= K_ZEROING_SAMPLES) { mZeroRateSampleIndex = 0; mHasEnoughZeroingSamples = true; } if (!mIsZerod) { if (!mHasEnoughZeroingSamples) { return; } mZeroBias = 0; for (Double sample : mZeroRateSamples) { mZeroBias += sample / K_ZEROING_SAMPLES; } mAngle = 0; mVolatileAngle = 0; GyroThread.this.reset(); mIsZerod = true; return; } double now = edu.wpi.first.wpilibj.Timer.getFPGATimestamp(); double timeElapsed = mLastTime == 0 ? 1.0 / K_READING_RATE : now - mLastTime; mLastTime = now; mVolatileRate = unbiasedAngleRate - mZeroBias; mAngle += mVolatileRate * timeElapsed; mVolatileAngle = mAngle; mVolatileHasData = true; } } }