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.math.MathUtil;
013import edu.wpi.first.util.sendable.Sendable;
014import edu.wpi.first.util.sendable.SendableBuilder;
015
016// CHECKSTYLE.OFF: TypeName
017// CHECKSTYLE.OFF: MemberName
018// CHECKSTYLE.OFF: LocalVariableName
019// CHECKSTYLE.OFF: ParameterName
020// CHECKSTYLE.OFF: EmptyCatchBlock
021
022/** This class is for the ADIS16470 IMU that connects to the RoboRIO SPI port. */
023@SuppressWarnings("PMD.RedundantFieldInitializer")
024public class ADIS16470_IMU implements AutoCloseable, Sendable {
025  /* ADIS16470 Register Map Declaration */
026  private static final int FLASH_CNT = 0x00; // Flash memory write count
027  private static final int DIAG_STAT = 0x02; // Diagnostic and operational status
028  private static final int X_GYRO_LOW = 0x04; // X-axis gyroscope output, lower word
029  private static final int X_GYRO_OUT = 0x06; // X-axis gyroscope output, upper word
030  private static final int Y_GYRO_LOW = 0x08; // Y-axis gyroscope output, lower word
031  private static final int Y_GYRO_OUT = 0x0A; // Y-axis gyroscope output, upper word
032  private static final int Z_GYRO_LOW = 0x0C; // Z-axis gyroscope output, lower word
033  private static final int Z_GYRO_OUT = 0x0E; // Z-axis gyroscope output, upper word
034  private static final int X_ACCL_LOW = 0x10; // X-axis accelerometer output, lower word
035  private static final int X_ACCL_OUT = 0x12; // X-axis accelerometer output, upper word
036  private static final int Y_ACCL_LOW = 0x14; // Y-axis accelerometer output, lower word
037  private static final int Y_ACCL_OUT = 0x16; // Y-axis accelerometer output, upper word
038  private static final int Z_ACCL_LOW = 0x18; // Z-axis accelerometer output, lower word
039  private static final int Z_ACCL_OUT = 0x1A; // Z-axis accelerometer output, upper word
040  private static final int TEMP_OUT = 0x1C; // Temperature output (internal, not calibrated)
041  private static final int TIME_STAMP = 0x1E; // PPS mode time stamp
042  private static final int X_DELTANG_LOW = 0x24; // X-axis delta angle output, lower word
043  private static final int X_DELTANG_OUT = 0x26; // X-axis delta angle output, upper word
044  private static final int Y_DELTANG_LOW = 0x28; // Y-axis delta angle output, lower word
045  private static final int Y_DELTANG_OUT = 0x2A; // Y-axis delta angle output, upper word
046  private static final int Z_DELTANG_LOW = 0x2C; // Z-axis delta angle output, lower word
047  private static final int Z_DELTANG_OUT = 0x2E; // Z-axis delta angle output, upper word
048  private static final int X_DELTVEL_LOW = 0x30; // X-axis delta velocity output, lower word
049  private static final int X_DELTVEL_OUT = 0x32; // X-axis delta velocity output, upper word
050  private static final int Y_DELTVEL_LOW = 0x34; // Y-axis delta velocity output, lower word
051  private static final int Y_DELTVEL_OUT = 0x36; // Y-axis delta velocity output, upper word
052  private static final int Z_DELTVEL_LOW = 0x38; // Z-axis delta velocity output, lower word
053  private static final int Z_DELTVEL_OUT = 0x3A; // Z-axis delta velocity output, upper word
054  private static final int XG_BIAS_LOW =
055      0x40; // X-axis gyroscope bias offset correction, lower word
056  private static final int XG_BIAS_HIGH =
057      0x42; // X-axis gyroscope bias offset correction, upper word
058  private static final int YG_BIAS_LOW =
059      0x44; // Y-axis gyroscope bias offset correction, lower word
060  private static final int YG_BIAS_HIGH =
061      0x46; // Y-axis gyroscope bias offset correction, upper word
062  private static final int ZG_BIAS_LOW =
063      0x48; // Z-axis gyroscope bias offset correction, lower word
064  private static final int ZG_BIAS_HIGH =
065      0x4A; // Z-axis gyroscope bias offset correction, upper word
066  private static final int XA_BIAS_LOW =
067      0x4C; // X-axis accelerometer bias offset correction, lower word
068  private static final int XA_BIAS_HIGH =
069      0x4E; // X-axis accelerometer bias offset correction, upper word
070  private static final int YA_BIAS_LOW =
071      0x50; // Y-axis accelerometer bias offset correction, lower word
072  private static final int YA_BIAS_HIGH =
073      0x52; // Y-axis accelerometer bias offset correction, upper word
074  private static final int ZA_BIAS_LOW =
075      0x54; // Z-axis accelerometer bias offset correction, lower word
076  private static final int ZA_BIAS_HIGH =
077      0x56; // Z-axis accelerometer bias offset correction, upper word
078  private static final int FILT_CTRL = 0x5C; // Filter control
079  private static final int MSC_CTRL = 0x60; // Miscellaneous control
080  private static final int UP_SCALE = 0x62; // Clock scale factor, PPS mode
081  private static final int DEC_RATE = 0x64; // Decimation rate control (output data rate)
082  private static final int NULL_CNFG = 0x66; // Auto-null configuration control
083  private static final int GLOB_CMD = 0x68; // Global commands
084  private static final int FIRM_REV = 0x6C; // Firmware revision
085  private static final int FIRM_DM = 0x6E; // Firmware revision date, month and day
086  private static final int FIRM_Y = 0x70; // Firmware revision date, year
087  private static final int PROD_ID = 0x72; // Product identification
088  private static final int SERIAL_NUM = 0x74; // Serial number (relative to assembly lot)
089  private static final int USER_SCR1 = 0x76; // User scratch register 1
090  private static final int USER_SCR2 = 0x78; // User scratch register 2
091  private static final int USER_SCR3 = 0x7A; // User scratch register 3
092  private static final int FLSHCNT_LOW = 0x7C; // Flash update count, lower word
093  private static final int FLSHCNT_HIGH = 0x7E; // Flash update count, upper word
094
095  // Weight between the previous and current gyro angles represents 1 second for the timestamp,
096  // this is the point at which we ignore the previous angle because it is too old to be of use.
097  // The IMU timestamp conversion is 1 = 49.02us, the value 1_000_000 is the number of microseconds
098  // we average over.
099  private static final double AVERAGE_RATE_SCALING_FACTOR = 49.02 / 1_000_000;
100
101  private static final byte[] m_autospi_allAngle_packet = {
102    X_DELTANG_OUT,
103    FLASH_CNT,
104    X_DELTANG_LOW,
105    FLASH_CNT,
106    Y_DELTANG_OUT,
107    FLASH_CNT,
108    Y_DELTANG_LOW,
109    FLASH_CNT,
110    Z_DELTANG_OUT,
111    FLASH_CNT,
112    Z_DELTANG_LOW,
113    FLASH_CNT,
114    X_GYRO_OUT,
115    FLASH_CNT,
116    Y_GYRO_OUT,
117    FLASH_CNT,
118    Z_GYRO_OUT,
119    FLASH_CNT,
120    X_ACCL_OUT,
121    FLASH_CNT,
122    Y_ACCL_OUT,
123    FLASH_CNT,
124    Z_ACCL_OUT,
125    FLASH_CNT
126  };
127
128  /** ADIS16470 calibration times. */
129  public enum CalibrationTime {
130    /** 32 ms calibration time. */
131    _32ms(0),
132    /** 64 ms calibration time. */
133    _64ms(1),
134    /** 128 ms calibration time. */
135    _128ms(2),
136    /** 256 ms calibration time. */
137    _256ms(3),
138    /** 512 ms calibration time. */
139    _512ms(4),
140    /** 1 s calibration time. */
141    _1s(5),
142    /** 2 s calibration time. */
143    _2s(6),
144    /** 4 s calibration time. */
145    _4s(7),
146    /** 8 s calibration time. */
147    _8s(8),
148    /** 16 s calibration time. */
149    _16s(9),
150    /** 32 s calibration time. */
151    _32s(10),
152    /** 64 s calibration time. */
153    _64s(11);
154
155    private final int value;
156
157    CalibrationTime(int value) {
158      this.value = value;
159    }
160  }
161
162  /**
163   * IMU axes.
164   *
165   * <p>kX, kY, and kZ refer to the IMU's X, Y, and Z axes respectively. kYaw, kPitch, and kRoll are
166   * configured by the user to refer to an X, Y, or Z axis.
167   */
168  public enum IMUAxis {
169    /** The IMU's X axis. */
170    kX,
171    /** The IMU's Y axis. */
172    kY,
173    /** The IMU's Z axis. */
174    kZ,
175    /** The user-configured yaw axis. */
176    kYaw,
177    /** The user-configured pitch axis. */
178    kPitch,
179    /** The user-configured roll axis. */
180    kRoll,
181  }
182
183  // Static Constants
184  private static final double kDeltaAngleSf = 2160.0 / 2147483648.0; // 2160 / (2^31)
185  private static final double kGrav = 9.81;
186
187  // User-specified axes
188  private IMUAxis m_yaw_axis;
189  private IMUAxis m_pitch_axis;
190  private IMUAxis m_roll_axis;
191
192  // Instant raw outputs
193  private double m_gyro_rate_x = 0.0;
194  private double m_gyro_rate_y = 0.0;
195  private double m_gyro_rate_z = 0.0;
196  private double m_accel_x = 0.0;
197  private double m_accel_y = 0.0;
198  private double m_accel_z = 0.0;
199
200  // Integrated gyro angle
201  private double m_integ_angle_x = 0.0;
202  private double m_integ_angle_y = 0.0;
203  private double m_integ_angle_z = 0.0;
204
205  // Complementary filter variables
206  private double m_dt = 0.0;
207  private double m_alpha = 0.0;
208  private static final double kTau = 1.0;
209  private double m_compAngleX = 0.0;
210  private double m_compAngleY = 0.0;
211  private double m_accelAngleX = 0.0;
212  private double m_accelAngleY = 0.0;
213
214  // State variables
215  private volatile boolean m_thread_active = false;
216  private int m_calibration_time = 0;
217  private volatile boolean m_first_run = true;
218  private volatile boolean m_thread_idle = false;
219  private boolean m_auto_configured = false;
220  private double m_scaled_sample_rate = 2500.0;
221
222  // Resources
223  private SPI m_spi;
224  private SPI.Port m_spi_port;
225  private DigitalInput m_auto_interrupt;
226  private DigitalInput m_reset_in;
227  private DigitalOutput m_status_led;
228  private Thread m_acquire_task;
229  private boolean m_connected;
230
231  private SimDevice m_simDevice;
232  private SimBoolean m_simConnected;
233  private SimDouble m_simGyroAngleX;
234  private SimDouble m_simGyroAngleY;
235  private SimDouble m_simGyroAngleZ;
236  private SimDouble m_simGyroRateX;
237  private SimDouble m_simGyroRateY;
238  private SimDouble m_simGyroRateZ;
239  private SimDouble m_simAccelX;
240  private SimDouble m_simAccelY;
241  private SimDouble m_simAccelZ;
242
243  /**
244   * Creates a new ADIS16740 IMU object.
245   *
246   * <p>The default setup is the onboard SPI port with a calibration time of 1 second. Yaw, pitch,
247   * and roll are kZ, kX, and kY respectively.
248   */
249  public ADIS16470_IMU() {
250    this(IMUAxis.kZ, IMUAxis.kX, IMUAxis.kY, SPI.Port.kOnboardCS0, CalibrationTime._1s);
251  }
252
253  /**
254   * Creates a new ADIS16740 IMU object.
255   *
256   * <p>The default setup is the onboard SPI port with a calibration time of 1 second.
257   *
258   * <p><b><i>Input axes limited to kX, kY and kZ. Specifying kYaw, kPitch,or kRoll will result in
259   * an error.</i></b>
260   *
261   * @param yaw_axis The axis that measures the yaw
262   * @param pitch_axis The axis that measures the pitch
263   * @param roll_axis The axis that measures the roll
264   */
265  public ADIS16470_IMU(IMUAxis yaw_axis, IMUAxis pitch_axis, IMUAxis roll_axis) {
266    this(yaw_axis, pitch_axis, roll_axis, SPI.Port.kOnboardCS0, CalibrationTime._1s);
267  }
268
269  /**
270   * Creates a new ADIS16740 IMU object.
271   *
272   * <p><b><i>Input axes limited to kX, kY and kZ. Specifying kYaw, kPitch,or kRoll will result in
273   * an error.</i></b>
274   *
275   * @param yaw_axis The axis that measures the yaw
276   * @param pitch_axis The axis that measures the pitch
277   * @param roll_axis The axis that measures the roll
278   * @param port The SPI Port the gyro is plugged into
279   * @param cal_time Calibration time
280   */
281  @SuppressWarnings("this-escape")
282  public ADIS16470_IMU(
283      IMUAxis yaw_axis,
284      IMUAxis pitch_axis,
285      IMUAxis roll_axis,
286      SPI.Port port,
287      CalibrationTime cal_time) {
288    if (yaw_axis == IMUAxis.kYaw
289        || yaw_axis == IMUAxis.kPitch
290        || yaw_axis == IMUAxis.kRoll
291        || pitch_axis == IMUAxis.kYaw
292        || pitch_axis == IMUAxis.kPitch
293        || pitch_axis == IMUAxis.kRoll
294        || roll_axis == IMUAxis.kYaw
295        || roll_axis == IMUAxis.kPitch
296        || roll_axis == IMUAxis.kRoll) {
297      DriverStation.reportError(
298          "ADIS16470 constructor only allows IMUAxis.kX, IMUAxis.kY or IMUAxis.kZ as arguments.",
299          false);
300      DriverStation.reportError(
301          "Constructing ADIS with default axes. (IMUAxis.kZ is defined as Yaw)", false);
302      yaw_axis = IMUAxis.kZ;
303      pitch_axis = IMUAxis.kY;
304      roll_axis = IMUAxis.kX;
305    }
306
307    m_yaw_axis = yaw_axis;
308    m_pitch_axis = pitch_axis;
309    m_roll_axis = roll_axis;
310
311    m_calibration_time = cal_time.value;
312    m_spi_port = port;
313
314    m_acquire_task = new Thread(this::acquire);
315
316    m_simDevice = SimDevice.create("Gyro:ADIS16470", port.value);
317    if (m_simDevice != null) {
318      m_simConnected = m_simDevice.createBoolean("connected", SimDevice.Direction.kInput, true);
319      m_simGyroAngleX = m_simDevice.createDouble("gyro_angle_x", SimDevice.Direction.kInput, 0.0);
320      m_simGyroAngleY = m_simDevice.createDouble("gyro_angle_y", SimDevice.Direction.kInput, 0.0);
321      m_simGyroAngleZ = m_simDevice.createDouble("gyro_angle_z", SimDevice.Direction.kInput, 0.0);
322      m_simGyroRateX = m_simDevice.createDouble("gyro_rate_x", SimDevice.Direction.kInput, 0.0);
323      m_simGyroRateY = m_simDevice.createDouble("gyro_rate_y", SimDevice.Direction.kInput, 0.0);
324      m_simGyroRateZ = m_simDevice.createDouble("gyro_rate_z", SimDevice.Direction.kInput, 0.0);
325      m_simAccelX = m_simDevice.createDouble("accel_x", SimDevice.Direction.kInput, 0.0);
326      m_simAccelY = m_simDevice.createDouble("accel_y", SimDevice.Direction.kInput, 0.0);
327      m_simAccelZ = m_simDevice.createDouble("accel_z", SimDevice.Direction.kInput, 0.0);
328    }
329
330    if (m_simDevice == null) {
331      // Force the IMU reset pin to toggle on startup (doesn't require DS enable)
332      // Relies on the RIO hardware by default configuring an output as low
333      // and configuring an input as high Z. The 10k pull-up resistor internal to the
334      // IMU then forces the reset line high for normal operation.
335      var reset_out = new DigitalOutput(27); // Drive SPI CS2 (IMU RST) low
336      Timer.delay(0.01);
337      reset_out.close();
338      m_reset_in = new DigitalInput(27); // Set SPI CS2 (IMU RST) high
339      Timer.delay(0.25); // Wait for reset to complete
340
341      if (!switchToStandardSPI()) {
342        return;
343      }
344      boolean needsFlash = false;
345      // Set IMU internal decimation to 4 (ODR = 2000 SPS / (4 + 1) = 400Hz), BW = 200Hz
346      if (readRegister(DEC_RATE) != 0x0004) {
347        writeRegister(DEC_RATE, 0x0004);
348        needsFlash = true;
349        DriverStation.reportWarning(
350            "ADIS16470: DEC_RATE register configuration inconsistent! Scheduling flash update.",
351            false);
352      }
353
354      // Set data ready polarity (HIGH = Good Data), Disable gSense Compensation and PoP
355      if (readRegister(MSC_CTRL) != 0x0001) {
356        writeRegister(MSC_CTRL, 0x0001);
357        needsFlash = true;
358        DriverStation.reportWarning(
359            "ADIS16470: MSC_CTRL register configuration inconsistent! Scheduling flash update.",
360            false);
361      }
362
363      // Disable IMU internal Bartlett filter (200Hz bandwidth is sufficient)
364      if (readRegister(FILT_CTRL) != 0x0000) {
365        writeRegister(FILT_CTRL, 0x0000);
366        needsFlash = true;
367        DriverStation.reportWarning(
368            "ADIS16470: FILT_CTRL register configuration inconsistent! Scheduling flash update.",
369            false);
370      }
371
372      // If any registers on the IMU don't match the config, trigger a flash update
373      if (needsFlash) {
374        DriverStation.reportWarning(
375            "ADIS16470: Register configuration changed! Starting IMU flash update.", false);
376        writeRegister(GLOB_CMD, 0x0008);
377        // Wait long enough for the flash update to finish (72ms minimum as per the datasheet)
378        Timer.delay(0.3);
379        DriverStation.reportWarning("ADIS16470: Flash update finished!", false);
380      } else {
381        DriverStation.reportWarning(
382            "ADIS16470: Flash and RAM configuration consistent. No flash update required!", false);
383      }
384
385      // Configure continuous bias calibration time based on user setting
386      writeRegister(NULL_CNFG, m_calibration_time | 0x0700);
387
388      // Notify DS that IMU calibration delay is active
389      DriverStation.reportWarning("ADIS16470: Starting initial calibration delay.", false);
390
391      // Wait for samples to accumulate internal to the IMU (110% of user-defined
392      // time)
393      try {
394        Thread.sleep((long) (Math.pow(2.0, m_calibration_time) / 2000 * 64 * 1.1 * 1000));
395      } catch (InterruptedException e) {
396      }
397
398      // Write offset calibration command to IMU
399      writeRegister(GLOB_CMD, 0x0001);
400
401      if (!switchToAutoSPI()) {
402        return;
403      }
404
405      // Let the user know the IMU was initialized successfully
406      DriverStation.reportWarning("ADIS16470 IMU Successfully Initialized!", false);
407
408      // Drive "Ready" LED low
409      m_status_led = new DigitalOutput(28); // Set SPI CS3 (IMU Ready LED) low
410    }
411
412    // Report usage and post data to DS
413    HAL.report(tResourceType.kResourceType_ADIS16470, 0);
414    m_connected = true;
415  }
416
417  /**
418   * Checks the connection status of the IMU.
419   *
420   * @return True if the IMU is connected, false otherwise.
421   */
422  public boolean isConnected() {
423    if (m_simConnected != null) {
424      return m_simConnected.get();
425    }
426    return m_connected;
427  }
428
429  private static int toUShort(int upper, int lower) {
430    return ((upper & 0xFF) << 8) + (lower & 0xFF);
431  }
432
433  private static int toShort(int upper, int lower) {
434    return (short) (((upper & 0xFF) << 8) + (lower & 0xFF));
435  }
436
437  private static int toInt(int msb, int byte2, int byte3, int lsb) {
438    return (msb & 0xFF) << 24 | (byte2 & 0xFF) << 16 | (byte3 & 0xFF) << 8 | (lsb & 0xFF);
439  }
440
441  /**
442   * Switch to standard SPI mode.
443   *
444   * @return True if successful, false otherwise.
445   */
446  private boolean switchToStandardSPI() {
447    // Check to see whether the acquire thread is active. If so, wait for it to stop
448    // producing data.
449    if (m_thread_active) {
450      m_thread_active = false;
451      while (!m_thread_idle) {
452        try {
453          Thread.sleep(10);
454        } catch (InterruptedException e) {
455        }
456      }
457      System.out.println("Paused the IMU processing thread successfully!");
458      // Maybe we're in auto SPI mode? If so, kill auto SPI, and then SPI.
459      if (m_spi != null && m_auto_configured) {
460        m_spi.stopAuto();
461        // We need to get rid of all the garbage left in the auto SPI buffer after
462        // stopping it.
463        // Sometimes data magically reappears, so we have to check the buffer size a
464        // couple of times to be sure we got it all. Yuck.
465        int[] trashBuffer = new int[200];
466        try {
467          Thread.sleep(100);
468        } catch (InterruptedException e) {
469        }
470        int data_count = m_spi.readAutoReceivedData(trashBuffer, 0, 0);
471        while (data_count > 0) {
472          m_spi.readAutoReceivedData(trashBuffer, Math.min(data_count, 200), 0);
473          data_count = m_spi.readAutoReceivedData(trashBuffer, 0, 0);
474        }
475        System.out.println("Paused auto SPI successfully.");
476      }
477    }
478    if (m_spi == null) {
479      m_spi = new SPI(m_spi_port);
480      m_spi.setClockRate(2000000);
481      m_spi.setMode(SPI.Mode.kMode3);
482      m_spi.setChipSelectActiveLow();
483    }
484    readRegister(PROD_ID); // Dummy read
485    // Validate the product ID
486    if (readRegister(PROD_ID) != 16982) {
487      DriverStation.reportError("Could not find an ADIS16470", false);
488      close();
489      return false;
490    }
491    return true;
492  }
493
494  /**
495   * Switch to auto SPI mode.
496   *
497   * @return True if successful, false otherwise.
498   */
499  boolean switchToAutoSPI() {
500    // No SPI port has been set up. Go set one up first.
501    if (m_spi == null && !switchToStandardSPI()) {
502      DriverStation.reportError("Failed to start/restart auto SPI", false);
503      return false;
504    }
505    // Only set up the interrupt if needed.
506    if (m_auto_interrupt == null) {
507      // Configure interrupt on SPI CS1
508      m_auto_interrupt = new DigitalInput(26);
509    }
510    // The auto SPI controller gets angry if you try to set up two instances on one
511    // bus.
512    if (!m_auto_configured) {
513      m_spi.initAuto(8200);
514      m_auto_configured = true;
515    }
516    // Do we need to change auto SPI settings?
517    m_spi.setAutoTransmitData(m_autospi_allAngle_packet, 2);
518    // Configure auto stall time
519    m_spi.configureAutoStall(5, 1000, 1);
520    // Kick off auto SPI (Note: Device configuration impossible after auto SPI is
521    // activated)
522    // DR High = Data good (data capture should be triggered on the rising edge)
523    m_spi.startAutoTrigger(m_auto_interrupt, true, false);
524    // Check to see if the acquire thread is running. If not, kick one off.
525    if (!m_acquire_task.isAlive()) {
526      m_first_run = true;
527      m_thread_active = true;
528      m_acquire_task.start();
529      System.out.println("Processing thread activated!");
530    } else {
531      // The thread was running, re-init run variables and start it up again.
532      m_first_run = true;
533      m_thread_active = true;
534      System.out.println("Processing thread activated!");
535    }
536    // Looks like the thread didn't start for some reason. Abort.
537    if (!m_acquire_task.isAlive()) {
538      DriverStation.reportError("Failed to start/restart the acquire() thread.", false);
539      close();
540      return false;
541    }
542    return true;
543  }
544
545  /**
546   * Configures calibration time.
547   *
548   * @param new_cal_time New calibration time
549   * @return 0 if success, 1 if no change, 2 if error.
550   */
551  public int configCalTime(CalibrationTime new_cal_time) {
552    if (m_calibration_time == new_cal_time.value) {
553      return 1;
554    }
555    if (!switchToStandardSPI()) {
556      DriverStation.reportError("Failed to configure/reconfigure standard SPI.", false);
557      return 2;
558    }
559    m_calibration_time = new_cal_time.value;
560    writeRegister(NULL_CNFG, m_calibration_time | 0x700);
561    if (!switchToAutoSPI()) {
562      DriverStation.reportError("Failed to configure/reconfigure auto SPI.", false);
563      return 2;
564    }
565    return 0;
566  }
567
568  /**
569   * Configures the decimation rate of the IMU.
570   *
571   * @param decimationRate The new decimation value.
572   * @return 0 if success, 1 if no change, 2 if error.
573   */
574  public int configDecRate(int decimationRate) {
575    // Switches the active SPI port to standard SPI mode, writes a new value to the DECIMATE
576    // register in the IMU, adjusts the sample scale factor, and re-enables auto SPI.
577    if (!switchToStandardSPI()) {
578      DriverStation.reportError("Failed to configure/reconfigure standard SPI.", false);
579      return 2;
580    }
581    if (decimationRate > 1999) {
582      DriverStation.reportError("Attempted to write an invalid decimation value.", false);
583      decimationRate = 1999;
584    }
585    m_scaled_sample_rate = (decimationRate + 1.0) / 2000.0 * 1000000.0;
586    writeRegister(DEC_RATE, decimationRate);
587    System.out.println("Decimation register: " + readRegister(DEC_RATE));
588    if (!switchToAutoSPI()) {
589      DriverStation.reportError("Failed to configure/reconfigure auto SPI.", false);
590      return 2;
591    }
592    return 0;
593  }
594
595  /**
596   * Calibrate the gyro. It's important to make sure that the robot is not moving while the
597   * calibration is in progress, this is typically done when the robot is first turned on while it's
598   * sitting at rest before the match starts.
599   */
600  public void calibrate() {
601    if (!switchToStandardSPI()) {
602      DriverStation.reportError("Failed to configure/reconfigure standard SPI.", false);
603    }
604    writeRegister(GLOB_CMD, 0x0001);
605    if (!switchToAutoSPI()) {
606      DriverStation.reportError("Failed to configure/reconfigure auto SPI.", false);
607    }
608  }
609
610  private int readRegister(int reg) {
611    final byte[] buf = {(byte) (reg & 0x7f), 0};
612
613    m_spi.write(buf, 2);
614    m_spi.read(false, buf, 2);
615
616    return toUShort(buf[0], buf[1]);
617  }
618
619  private void writeRegister(int reg, int val) {
620    // low byte
621    final byte[] buf = {(byte) (0x80 | reg), (byte) (val & 0xff)};
622    m_spi.write(buf, 2);
623    // high byte
624    buf[0] = (byte) (0x81 | reg);
625    buf[1] = (byte) (val >> 8);
626    m_spi.write(buf, 2);
627  }
628
629  /** Delete (free) the spi port used for the IMU. */
630  @Override
631  public void close() {
632    if (m_thread_active) {
633      m_thread_active = false;
634      try {
635        if (m_acquire_task != null) {
636          m_acquire_task.join();
637          m_acquire_task = null;
638        }
639      } catch (InterruptedException e) {
640      }
641      if (m_spi != null) {
642        if (m_auto_configured) {
643          m_spi.stopAuto();
644        }
645        m_spi.close();
646        m_auto_configured = false;
647        if (m_auto_interrupt != null) {
648          m_auto_interrupt.close();
649          m_auto_interrupt = null;
650        }
651        m_spi = null;
652      }
653    }
654    if (m_simDevice != null) {
655      m_simDevice.close();
656      m_simDevice = null;
657    }
658    System.out.println("Finished cleaning up after the IMU driver.");
659  }
660
661  private void acquire() {
662    // Set data packet length
663    final int dataset_len = 27; // 26 data points + timestamp
664    final int BUFFER_SIZE = 4000;
665
666    // Set up buffers and variables
667    int[] buffer = new int[BUFFER_SIZE];
668    double previous_timestamp = 0.0;
669    double compAngleX = 0.0;
670    double compAngleY = 0.0;
671    while (true) {
672      // Wait for data
673      try {
674        Thread.sleep(10);
675      } catch (InterruptedException e) {
676      }
677
678      if (m_thread_active) {
679        m_thread_idle = false;
680
681        // Read number of bytes currently stored in the buffer
682        int data_count = m_spi.readAutoReceivedData(buffer, 0, 0);
683        // Check if frame is incomplete
684        int data_remainder = data_count % dataset_len;
685        // Remove incomplete data from read count
686        int data_to_read = data_count - data_remainder;
687        // Want to cap the data to read in a single read at the buffer size
688        if (data_to_read > BUFFER_SIZE) {
689          DriverStation.reportWarning(
690              "ADIS16470 data processing thread overrun has occurred!", false);
691          data_to_read = BUFFER_SIZE - (BUFFER_SIZE % dataset_len);
692        }
693        // Read data from DMA buffer (only complete sets)
694        m_spi.readAutoReceivedData(buffer, data_to_read, 0);
695
696        // Could be multiple data sets in the buffer. Handle each one.
697        for (int i = 0; i < data_to_read; i += dataset_len) {
698          // Timestamp is at buffer[i]
699          m_dt = (buffer[i] - previous_timestamp) / 1000000.0;
700          // Get delta angle value for all 3 axes and scale by the elapsed time
701          // (based on timestamp)
702          double elapsed_time = m_scaled_sample_rate / (buffer[i] - previous_timestamp);
703          double delta_angle_x =
704              toInt(buffer[i + 3], buffer[i + 4], buffer[i + 5], buffer[i + 6])
705                  * kDeltaAngleSf
706                  / elapsed_time;
707          double delta_angle_y =
708              toInt(buffer[i + 7], buffer[i + 8], buffer[i + 9], buffer[i + 10])
709                  * kDeltaAngleSf
710                  / elapsed_time;
711          double delta_angle_z =
712              toInt(buffer[i + 11], buffer[i + 12], buffer[i + 13], buffer[i + 14])
713                  * kDeltaAngleSf
714                  / elapsed_time;
715
716          double gyro_rate_x = toShort(buffer[i + 15], buffer[i + 16]) / 10.0;
717          double gyro_rate_y = toShort(buffer[i + 17], buffer[i + 18]) / 10.0;
718          double gyro_rate_z = toShort(buffer[i + 19], buffer[i + 20]) / 10.0;
719
720          double accel_x = toShort(buffer[i + 21], buffer[i + 22]) / 800.0;
721          double accel_y = toShort(buffer[i + 23], buffer[i + 24]) / 800.0;
722          double accel_z = toShort(buffer[i + 25], buffer[i + 26]) / 800.0;
723
724          // Convert scaled sensor data to SI units (for tilt calculations)
725          // TODO: Should the unit outputs be selectable?
726          double gyro_rate_x_si = Math.toRadians(gyro_rate_x);
727          double gyro_rate_y_si = Math.toRadians(gyro_rate_y);
728          // double gyro_rate_z_si = Math.toRadians(gyro_rate_z);
729          double accel_x_si = accel_x * kGrav;
730          double accel_y_si = accel_y * kGrav;
731          double accel_z_si = accel_z * kGrav;
732
733          // Store timestamp for next iteration
734          previous_timestamp = buffer[i];
735
736          m_alpha = kTau / (kTau + m_dt);
737
738          // Run inclinometer calculations
739          double accelAngleX = Math.atan2(accel_x_si, Math.hypot(accel_y_si, accel_z_si));
740          double accelAngleY = Math.atan2(accel_y_si, Math.hypot(accel_x_si, accel_z_si));
741          if (m_first_run) {
742            compAngleX = accelAngleX;
743            compAngleY = accelAngleY;
744          } else {
745            accelAngleX = formatAccelRange(accelAngleX, accel_z_si);
746            accelAngleY = formatAccelRange(accelAngleY, accel_z_si);
747            compAngleX = compFilterProcess(compAngleX, accelAngleX, -gyro_rate_y_si);
748            compAngleY = compFilterProcess(compAngleY, accelAngleY, gyro_rate_x_si);
749          }
750
751          synchronized (this) {
752            // Push data to global variables
753            if (m_first_run) {
754              // Don't accumulate first run. previous_timestamp will be "very" old and the
755              // integration will end up way off
756              reset();
757            } else {
758              m_integ_angle_x += delta_angle_x;
759              m_integ_angle_y += delta_angle_y;
760              m_integ_angle_z += delta_angle_z;
761            }
762            m_gyro_rate_x = gyro_rate_x;
763            m_gyro_rate_y = gyro_rate_y;
764            m_gyro_rate_z = gyro_rate_z;
765            m_accel_x = accel_x;
766            m_accel_y = accel_y;
767            m_accel_z = accel_z;
768            m_compAngleX = Math.toDegrees(compAngleX);
769            m_compAngleY = Math.toDegrees(compAngleY);
770            m_accelAngleX = Math.toDegrees(accelAngleX);
771            m_accelAngleY = Math.toDegrees(accelAngleY);
772          }
773          m_first_run = false;
774        }
775      } else {
776        m_thread_idle = true;
777        previous_timestamp = 0.0;
778        compAngleX = 0.0;
779        compAngleY = 0.0;
780      }
781    }
782  }
783
784  private double formatFastConverge(double compAngle, double accAngle) {
785    if (compAngle > accAngle + Math.PI) {
786      compAngle = compAngle - 2.0 * Math.PI;
787    } else if (accAngle > compAngle + Math.PI) {
788      compAngle = compAngle + 2.0 * Math.PI;
789    }
790    return compAngle;
791  }
792
793  private double formatAccelRange(double accelAngle, double accelZ) {
794    if (accelZ < 0.0) {
795      accelAngle = Math.PI - accelAngle;
796    } else if (accelZ > 0.0 && accelAngle < 0.0) {
797      accelAngle = 2.0 * Math.PI + accelAngle;
798    }
799    return accelAngle;
800  }
801
802  private double compFilterProcess(double compAngle, double accelAngle, double omega) {
803    compAngle = formatFastConverge(compAngle, accelAngle);
804    compAngle = m_alpha * (compAngle + omega * m_dt) + (1.0 - m_alpha) * accelAngle;
805    return MathUtil.angleModulus(compAngle);
806  }
807
808  /**
809   * Reset the gyro.
810   *
811   * <p>Resets the gyro accumulations to a heading of zero. This can be used if there is significant
812   * drift in the gyro and it needs to be recalibrated after running.
813   */
814  public void reset() {
815    synchronized (this) {
816      m_integ_angle_x = 0.0;
817      m_integ_angle_y = 0.0;
818      m_integ_angle_z = 0.0;
819    }
820  }
821
822  /**
823   * Allow the designated gyro angle to be set to a given value. This may happen with unread values
824   * in the buffer, it is suggested that the IMU is not moving when this method is run.
825   *
826   * @param axis IMUAxis that will be changed
827   * @param angle A double in degrees (CCW positive)
828   */
829  public void setGyroAngle(IMUAxis axis, double angle) {
830    axis =
831        switch (axis) {
832          case kYaw -> m_yaw_axis;
833          case kPitch -> m_pitch_axis;
834          case kRoll -> m_roll_axis;
835          default -> axis;
836        };
837
838    switch (axis) {
839      case kX -> setGyroAngleX(angle);
840      case kY -> setGyroAngleY(angle);
841      case kZ -> setGyroAngleZ(angle);
842      default -> {
843        // NOP
844      }
845    }
846  }
847
848  /**
849   * Allow the gyro angle X to be set to a given value. This may happen with unread values in the
850   * buffer, it is suggested that the IMU is not moving when this method is run.
851   *
852   * @param angle A double in degrees (CCW positive)
853   */
854  public void setGyroAngleX(double angle) {
855    synchronized (this) {
856      m_integ_angle_x = angle;
857    }
858  }
859
860  /**
861   * Allow the gyro angle Y to be set to a given value. This may happen with unread values in the
862   * buffer, it is suggested that the IMU is not moving when this method is run.
863   *
864   * @param angle A double in degrees (CCW positive)
865   */
866  public void setGyroAngleY(double angle) {
867    synchronized (this) {
868      m_integ_angle_y = angle;
869    }
870  }
871
872  /**
873   * Allow the gyro angle Z to be set to a given value. This may happen with unread values in the
874   * buffer, it is suggested that the IMU is not moving when this method is run.
875   *
876   * @param angle A double in degrees (CCW positive)
877   */
878  public void setGyroAngleZ(double angle) {
879    synchronized (this) {
880      m_integ_angle_z = angle;
881    }
882  }
883
884  /**
885   * Returns the axis angle in degrees (CCW positive).
886   *
887   * @param axis The IMUAxis whose angle to return.
888   * @return The axis angle in degrees (CCW positive).
889   */
890  public synchronized double getAngle(IMUAxis axis) {
891    axis =
892        switch (axis) {
893          case kYaw -> m_yaw_axis;
894          case kPitch -> m_pitch_axis;
895          case kRoll -> m_roll_axis;
896          default -> axis;
897        };
898
899    return switch (axis) {
900      case kX -> {
901        if (m_simGyroAngleX != null) {
902          yield m_simGyroAngleX.get();
903        }
904        yield m_integ_angle_x;
905      }
906      case kY -> {
907        if (m_simGyroAngleY != null) {
908          yield m_simGyroAngleY.get();
909        }
910        yield m_integ_angle_y;
911      }
912      case kZ -> {
913        if (m_simGyroAngleZ != null) {
914          yield m_simGyroAngleZ.get();
915        }
916        yield m_integ_angle_z;
917      }
918      default -> 0.0;
919    };
920  }
921
922  /**
923   * Returns the Yaw axis angle in degrees (CCW positive).
924   *
925   * @return The Yaw axis angle in degrees (CCW positive).
926   */
927  public synchronized double getAngle() {
928    return getAngle(m_yaw_axis);
929  }
930
931  /**
932   * Returns the axis angular rate in degrees per second (CCW positive).
933   *
934   * @param axis The IMUAxis whose rate to return.
935   * @return Axis angular rate in degrees per second (CCW positive).
936   */
937  public synchronized double getRate(IMUAxis axis) {
938    axis =
939        switch (axis) {
940          case kYaw -> m_yaw_axis;
941          case kPitch -> m_pitch_axis;
942          case kRoll -> m_roll_axis;
943          default -> axis;
944        };
945
946    return switch (axis) {
947      case kX -> {
948        if (m_simGyroRateX != null) {
949          yield m_simGyroRateX.get();
950        }
951        yield m_gyro_rate_x;
952      }
953      case kY -> {
954        if (m_simGyroRateY != null) {
955          yield m_simGyroRateY.get();
956        }
957        yield m_gyro_rate_y;
958      }
959      case kZ -> {
960        if (m_simGyroRateZ != null) {
961          yield m_simGyroRateZ.get();
962        }
963        yield m_gyro_rate_z;
964      }
965      default -> 0.0;
966    };
967  }
968
969  /**
970   * Returns the Yaw axis angular rate in degrees per second (CCW positive).
971   *
972   * @return Yaw axis angular rate in degrees per second (CCW positive).
973   */
974  public synchronized double getRate() {
975    return getRate(m_yaw_axis);
976  }
977
978  /**
979   * Returns which axis, kX, kY, or kZ, is set to the yaw axis.
980   *
981   * @return IMUAxis Yaw Axis
982   */
983  public IMUAxis getYawAxis() {
984    return m_yaw_axis;
985  }
986
987  /**
988   * Returns which axis, kX, kY, or kZ, is set to the pitch axis.
989   *
990   * @return IMUAxis Pitch Axis
991   */
992  public IMUAxis getPitchAxis() {
993    return m_pitch_axis;
994  }
995
996  /**
997   * Returns which axis, kX, kY, or kZ, is set to the roll axis.
998   *
999   * @return IMUAxis Roll Axis
1000   */
1001  public IMUAxis getRollAxis() {
1002    return m_roll_axis;
1003  }
1004
1005  /**
1006   * Returns the acceleration in the X axis in meters per second squared.
1007   *
1008   * @return The acceleration in the X axis in meters per second squared.
1009   */
1010  public synchronized double getAccelX() {
1011    return m_accel_x * kGrav;
1012  }
1013
1014  /**
1015   * Returns the acceleration in the Y axis in meters per second squared.
1016   *
1017   * @return The acceleration in the Y axis in meters per second squared.
1018   */
1019  public synchronized double getAccelY() {
1020    return m_accel_y * kGrav;
1021  }
1022
1023  /**
1024   * Returns the acceleration in the Z axis in meters per second squared.
1025   *
1026   * @return The acceleration in the Z axis in meters per second squared.
1027   */
1028  public synchronized double getAccelZ() {
1029    return m_accel_z * kGrav;
1030  }
1031
1032  /**
1033   * Returns the complementary angle around the X axis computed from accelerometer and gyro rate
1034   * measurements.
1035   *
1036   * @return The X-axis complementary angle in degrees.
1037   */
1038  public synchronized double getXComplementaryAngle() {
1039    return m_compAngleX;
1040  }
1041
1042  /**
1043   * Returns the complementary angle around the Y axis computed from accelerometer and gyro rate
1044   * measurements.
1045   *
1046   * @return The Y-axis complementary angle in degrees.
1047   */
1048  public synchronized double getYComplementaryAngle() {
1049    return m_compAngleY;
1050  }
1051
1052  /**
1053   * Returns the X-axis filtered acceleration angle in degrees.
1054   *
1055   * @return The X-axis filtered acceleration angle in degrees.
1056   */
1057  public synchronized double getXFilteredAccelAngle() {
1058    return m_accelAngleX;
1059  }
1060
1061  /**
1062   * Returns the Y-axis filtered acceleration angle in degrees.
1063   *
1064   * @return The Y-axis filtered acceleration angle in degrees.
1065   */
1066  public synchronized double getYFilteredAccelAngle() {
1067    return m_accelAngleY;
1068  }
1069
1070  /**
1071   * Gets the SPI port number.
1072   *
1073   * @return The SPI port number.
1074   */
1075  public int getPort() {
1076    return m_spi_port.value;
1077  }
1078
1079  @Override
1080  public void initSendable(SendableBuilder builder) {
1081    builder.setSmartDashboardType("Gyro");
1082    builder.addDoubleProperty("Value", () -> getAngle(m_yaw_axis), null);
1083  }
1084}