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
005/*----------------------------------------------------------------------------*/
006/* Copyright (c) 2016-2020 Analog Devices Inc. All Rights Reserved.           */
007/* Open Source Software - may be modified and shared by FRC teams. The code   */
008/* must be accompanied by the FIRST BSD license file in the root directory of */
009/* the project.                                                               */
010/*                                                                            */
011/* Modified by Juan Chong - frcsupport@analog.com                             */
012/*----------------------------------------------------------------------------*/
013
014package edu.wpi.first.wpilibj;
015
016import edu.wpi.first.hal.FRCNetComm.tResourceType;
017import edu.wpi.first.hal.HAL;
018import edu.wpi.first.hal.SimBoolean;
019import edu.wpi.first.hal.SimDevice;
020import edu.wpi.first.hal.SimDouble;
021import edu.wpi.first.util.sendable.Sendable;
022import edu.wpi.first.util.sendable.SendableBuilder;
023
024// CHECKSTYLE.OFF: TypeName
025// CHECKSTYLE.OFF: MemberName
026// CHECKSTYLE.OFF: SummaryJavadoc
027// CHECKSTYLE.OFF: UnnecessaryParentheses
028// CHECKSTYLE.OFF: OverloadMethodsDeclarationOrder
029// CHECKSTYLE.OFF: NonEmptyAtclauseDescription
030// CHECKSTYLE.OFF: MissingOverride
031// CHECKSTYLE.OFF: AtclauseOrder
032// CHECKSTYLE.OFF: LocalVariableName
033// CHECKSTYLE.OFF: RedundantModifier
034// CHECKSTYLE.OFF: AbbreviationAsWordInName
035// CHECKSTYLE.OFF: ParameterName
036// CHECKSTYLE.OFF: EmptyCatchBlock
037// CHECKSTYLE.OFF: MissingJavadocMethod
038// CHECKSTYLE.OFF: MissingSwitchDefault
039// CHECKSTYLE.OFF: VariableDeclarationUsageDistance
040// CHECKSTYLE.OFF: ArrayTypeStyle
041
042/** This class is for the ADIS16448 IMU that connects to the RoboRIO MXP port. */
043@SuppressWarnings({
044  "unused",
045  "PMD.RedundantFieldInitializer",
046  "PMD.ImmutableField",
047  "PMD.SingularField",
048  "PMD.CollapsibleIfStatements",
049  "PMD.MissingOverride",
050  "PMD.EmptyIfStmt",
051  "PMD.EmptyStatementNotInLoop"
052})
053public class ADIS16448_IMU implements AutoCloseable, Sendable {
054  /** ADIS16448 Register Map Declaration */
055  private static final int FLASH_CNT = 0x00; // Flash memory write count
056
057  private static final int XGYRO_OUT = 0x04; // X-axis gyroscope output
058  private static final int YGYRO_OUT = 0x06; // Y-axis gyroscope output
059  private static final int ZGYRO_OUT = 0x08; // Z-axis gyroscope output
060  private static final int XACCL_OUT = 0x0A; // X-axis accelerometer output
061  private static final int YACCL_OUT = 0x0C; // Y-axis accelerometer output
062  private static final int ZACCL_OUT = 0x0E; // Z-axis accelerometer output
063  private static final int XMAGN_OUT = 0x10; // X-axis magnetometer output
064  private static final int YMAGN_OUT = 0x12; // Y-axis magnetometer output
065  private static final int ZMAGN_OUT = 0x14; // Z-axis magnetometer output
066  private static final int BARO_OUT = 0x16; // Barometer pressure measurement, high word
067  private static final int TEMP_OUT = 0x18; // Temperature output
068  private static final int XGYRO_OFF = 0x1A; // X-axis gyroscope bias offset factor
069  private static final int YGYRO_OFF = 0x1C; // Y-axis gyroscope bias offset factor
070  private static final int ZGYRO_OFF = 0x1E; // Z-axis gyroscope bias offset factor
071  private static final int XACCL_OFF = 0x20; // X-axis acceleration bias offset factor
072  private static final int YACCL_OFF = 0x22; // Y-axis acceleration bias offset factor
073  private static final int ZACCL_OFF = 0x24; // Z-axis acceleration bias offset factor
074  private static final int XMAGN_HIC = 0x26; // X-axis magnetometer, hard iron factor
075  private static final int YMAGN_HIC = 0x28; // Y-axis magnetometer, hard iron factor
076  private static final int ZMAGN_HIC = 0x2A; // Z-axis magnetometer, hard iron factor
077  private static final int XMAGN_SIC = 0x2C; // X-axis magnetometer, soft iron factor
078  private static final int YMAGN_SIC = 0x2E; // Y-axis magnetometer, soft iron factor
079  private static final int ZMAGN_SIC = 0x30; // Z-axis magnetometer, soft iron factor
080  private static final int GPIO_CTRL = 0x32; // GPIO control
081  private static final int MSC_CTRL = 0x34; // MISC control
082  private static final int SMPL_PRD = 0x36; // Sample clock/Decimation filter control
083  private static final int SENS_AVG = 0x38; // Digital filter control
084  private static final int SEQ_CNT = 0x3A; // MAGN_OUT and BARO_OUT counter
085  private static final int DIAG_STAT = 0x3C; // System status
086  private static final int GLOB_CMD = 0x3E; // System command
087  private static final int ALM_MAG1 = 0x40; // Alarm 1 amplitude threshold
088  private static final int ALM_MAG2 = 0x42; // Alarm 2 amplitude threshold
089  private static final int ALM_SMPL1 = 0x44; // Alarm 1 sample size
090  private static final int ALM_SMPL2 = 0x46; // Alarm 2 sample size
091  private static final int ALM_CTRL = 0x48; // Alarm control
092  private static final int LOT_ID1 = 0x52; // Lot identification number
093  private static final int LOT_ID2 = 0x54; // Lot identification number
094  private static final int PROD_ID = 0x56; // Product identifier
095  private static final int SERIAL_NUM = 0x58; // Lot-specific serial number
096
097  /** ADIS16448 calibration times. */
098  public enum CalibrationTime {
099    /** 32 ms calibration time */
100    _32ms(0),
101    /** 64 ms calibration time */
102    _64ms(1),
103    /** 128 ms calibration time */
104    _128ms(2),
105    /** 256 ms calibration time */
106    _256ms(3),
107    /** 512 ms calibration time */
108    _512ms(4),
109    /** 1 s calibration time */
110    _1s(5),
111    /** 2 s calibration time */
112    _2s(6),
113    /** 4 s calibration time */
114    _4s(7),
115    /** 8 s calibration time */
116    _8s(8),
117    /** 16 s calibration time */
118    _16s(9),
119    /** 32 s calibration time */
120    _32s(10),
121    /** 64 s calibration time */
122    _64s(11);
123
124    private final int value;
125
126    CalibrationTime(int value) {
127      this.value = value;
128    }
129  }
130
131  /** IMU axes. */
132  public enum IMUAxis {
133    /** The IMU's X axis. */
134    kX,
135    /** The IMU's Y axis. */
136    kY,
137    /** The IMU's Z axis. */
138    kZ
139  }
140
141  // * Static Constants */
142  private static final double rad_to_deg = 57.2957795;
143  private static final double deg_to_rad = 0.0174532;
144  private static final double grav = 9.81;
145
146  /* User-specified yaw axis */
147  private IMUAxis m_yaw_axis;
148
149  /* Offset data storage */
150  private double[] m_offset_data_gyro_rate_x;
151  private double[] m_offset_data_gyro_rate_y;
152  private double[] m_offset_data_gyro_rate_z;
153
154  /* Instant raw output variables */
155  private double m_gyro_rate_x = 0.0;
156  private double m_gyro_rate_y = 0.0;
157  private double m_gyro_rate_z = 0.0;
158  private double m_accel_x = 0.0;
159  private double m_accel_y = 0.0;
160  private double m_accel_z = 0.0;
161  private double m_mag_x = 0.0;
162  private double m_mag_y = 0.0;
163  private double m_mag_z = 0.0;
164  private double m_baro = 0.0;
165  private double m_temp = 0.0;
166
167  /* IMU gyro offset variables */
168  private double m_gyro_rate_offset_x = 0.0;
169  private double m_gyro_rate_offset_y = 0.0;
170  private double m_gyro_rate_offset_z = 0.0;
171  private int m_avg_size = 0;
172  private int m_accum_count = 0;
173
174  /* Integrated gyro angle variables */
175  private double m_integ_gyro_angle_x = 0.0;
176  private double m_integ_gyro_angle_y = 0.0;
177  private double m_integ_gyro_angle_z = 0.0;
178
179  /* Complementary filter variables */
180  private double m_dt = 0.0;
181  private double m_alpha = 0.0;
182  private double m_tau = 1.0;
183  private double m_compAngleX = 0.0;
184  private double m_compAngleY = 0.0;
185  private double m_accelAngleX = 0.0;
186  private double m_accelAngleY = 0.0;
187
188  /* State variables */
189  private volatile boolean m_thread_active = false;
190  private CalibrationTime m_calibration_time = CalibrationTime._32ms;
191  private volatile boolean m_first_run = true;
192  private volatile boolean m_thread_idle = false;
193  private boolean m_auto_configured = false;
194  private boolean m_start_up_mode = true;
195
196  /* Resources */
197  private SPI m_spi;
198  private SPI.Port m_spi_port;
199  private DigitalInput m_auto_interrupt;
200  private DigitalOutput m_reset_out;
201  private DigitalInput m_reset_in;
202  private DigitalOutput m_status_led;
203  private Thread m_acquire_task;
204  private boolean m_connected;
205
206  private SimDevice m_simDevice;
207  private SimBoolean m_simConnected;
208  private SimDouble m_simGyroAngleX;
209  private SimDouble m_simGyroAngleY;
210  private SimDouble m_simGyroAngleZ;
211  private SimDouble m_simGyroRateX;
212  private SimDouble m_simGyroRateY;
213  private SimDouble m_simGyroRateZ;
214  private SimDouble m_simAccelX;
215  private SimDouble m_simAccelY;
216  private SimDouble m_simAccelZ;
217
218  /* CRC-16 Look-Up Table */
219  int[] adiscrc =
220      new int[] {
221        0x0000, 0x17CE, 0x0FDF, 0x1811, 0x1FBE, 0x0870, 0x1061, 0x07AF,
222        0x1F3F, 0x08F1, 0x10E0, 0x072E, 0x0081, 0x174F, 0x0F5E, 0x1890,
223        0x1E3D, 0x09F3, 0x11E2, 0x062C, 0x0183, 0x164D, 0x0E5C, 0x1992,
224        0x0102, 0x16CC, 0x0EDD, 0x1913, 0x1EBC, 0x0972, 0x1163, 0x06AD,
225        0x1C39, 0x0BF7, 0x13E6, 0x0428, 0x0387, 0x1449, 0x0C58, 0x1B96,
226        0x0306, 0x14C8, 0x0CD9, 0x1B17, 0x1CB8, 0x0B76, 0x1367, 0x04A9,
227        0x0204, 0x15CA, 0x0DDB, 0x1A15, 0x1DBA, 0x0A74, 0x1265, 0x05AB,
228        0x1D3B, 0x0AF5, 0x12E4, 0x052A, 0x0285, 0x154B, 0x0D5A, 0x1A94,
229        0x1831, 0x0FFF, 0x17EE, 0x0020, 0x078F, 0x1041, 0x0850, 0x1F9E,
230        0x070E, 0x10C0, 0x08D1, 0x1F1F, 0x18B0, 0x0F7E, 0x176F, 0x00A1,
231        0x060C, 0x11C2, 0x09D3, 0x1E1D, 0x19B2, 0x0E7C, 0x166D, 0x01A3,
232        0x1933, 0x0EFD, 0x16EC, 0x0122, 0x068D, 0x1143, 0x0952, 0x1E9C,
233        0x0408, 0x13C6, 0x0BD7, 0x1C19, 0x1BB6, 0x0C78, 0x1469, 0x03A7,
234        0x1B37, 0x0CF9, 0x14E8, 0x0326, 0x0489, 0x1347, 0x0B56, 0x1C98,
235        0x1A35, 0x0DFB, 0x15EA, 0x0224, 0x058B, 0x1245, 0x0A54, 0x1D9A,
236        0x050A, 0x12C4, 0x0AD5, 0x1D1B, 0x1AB4, 0x0D7A, 0x156B, 0x02A5,
237        0x1021, 0x07EF, 0x1FFE, 0x0830, 0x0F9F, 0x1851, 0x0040, 0x178E,
238        0x0F1E, 0x18D0, 0x00C1, 0x170F, 0x10A0, 0x076E, 0x1F7F, 0x08B1,
239        0x0E1C, 0x19D2, 0x01C3, 0x160D, 0x11A2, 0x066C, 0x1E7D, 0x09B3,
240        0x1123, 0x06ED, 0x1EFC, 0x0932, 0x0E9D, 0x1953, 0x0142, 0x168C,
241        0x0C18, 0x1BD6, 0x03C7, 0x1409, 0x13A6, 0x0468, 0x1C79, 0x0BB7,
242        0x1327, 0x04E9, 0x1CF8, 0x0B36, 0x0C99, 0x1B57, 0x0346, 0x1488,
243        0x1225, 0x05EB, 0x1DFA, 0x0A34, 0x0D9B, 0x1A55, 0x0244, 0x158A,
244        0x0D1A, 0x1AD4, 0x02C5, 0x150B, 0x12A4, 0x056A, 0x1D7B, 0x0AB5,
245        0x0810, 0x1FDE, 0x07CF, 0x1001, 0x17AE, 0x0060, 0x1871, 0x0FBF,
246        0x172F, 0x00E1, 0x18F0, 0x0F3E, 0x0891, 0x1F5F, 0x074E, 0x1080,
247        0x162D, 0x01E3, 0x19F2, 0x0E3C, 0x0993, 0x1E5D, 0x064C, 0x1182,
248        0x0912, 0x1EDC, 0x06CD, 0x1103, 0x16AC, 0x0162, 0x1973, 0x0EBD,
249        0x1429, 0x03E7, 0x1BF6, 0x0C38, 0x0B97, 0x1C59, 0x0448, 0x1386,
250        0x0B16, 0x1CD8, 0x04C9, 0x1307, 0x14A8, 0x0366, 0x1B77, 0x0CB9,
251        0x0A14, 0x1DDA, 0x05CB, 0x1205, 0x15AA, 0x0264, 0x1A75, 0x0DBB,
252        0x152B, 0x02E5, 0x1AF4, 0x0D3A, 0x0A95, 0x1D5B, 0x054A, 0x1284
253      };
254
255  private static class AcquireTask implements Runnable {
256    private final ADIS16448_IMU imu;
257
258    public AcquireTask(final ADIS16448_IMU imu) {
259      this.imu = imu;
260    }
261
262    @Override
263    public void run() {
264      imu.acquire();
265    }
266  }
267
268  /** Creates a new ADIS16448_IMU object. */
269  public ADIS16448_IMU() {
270    this(IMUAxis.kZ, SPI.Port.kMXP, CalibrationTime._512ms);
271  }
272
273  /**
274   * Creates a new ADIS16448_IMU object.
275   *
276   * @param yaw_axis The axis that measures the yaw
277   * @param port The SPI Port the gyro is plugged into
278   * @param cal_time Calibration time
279   */
280  @SuppressWarnings("this-escape")
281  public ADIS16448_IMU(final IMUAxis yaw_axis, SPI.Port port, CalibrationTime cal_time) {
282    m_yaw_axis = yaw_axis;
283    m_spi_port = port;
284
285    m_acquire_task = new Thread(new AcquireTask(this));
286
287    m_simDevice = SimDevice.create("Gyro:ADIS16448", port.value);
288    if (m_simDevice != null) {
289      m_simConnected = m_simDevice.createBoolean("connected", SimDevice.Direction.kInput, true);
290      m_simGyroAngleX = m_simDevice.createDouble("gyro_angle_x", SimDevice.Direction.kInput, 0.0);
291      m_simGyroAngleY = m_simDevice.createDouble("gyro_angle_y", SimDevice.Direction.kInput, 0.0);
292      m_simGyroAngleZ = m_simDevice.createDouble("gyro_angle_z", SimDevice.Direction.kInput, 0.0);
293      m_simGyroRateX = m_simDevice.createDouble("gyro_rate_x", SimDevice.Direction.kInput, 0.0);
294      m_simGyroRateY = m_simDevice.createDouble("gyro_rate_y", SimDevice.Direction.kInput, 0.0);
295      m_simGyroRateZ = m_simDevice.createDouble("gyro_rate_z", SimDevice.Direction.kInput, 0.0);
296      m_simAccelX = m_simDevice.createDouble("accel_x", SimDevice.Direction.kInput, 0.0);
297      m_simAccelY = m_simDevice.createDouble("accel_y", SimDevice.Direction.kInput, 0.0);
298      m_simAccelZ = m_simDevice.createDouble("accel_z", SimDevice.Direction.kInput, 0.0);
299    }
300
301    if (m_simDevice == null) {
302      // Force the IMU reset pin to toggle on startup (doesn't require DS enable)
303      m_reset_out = new DigitalOutput(18); // Drive MXP DIO8 low
304      Timer.delay(0.01); // Wait 10ms
305      m_reset_out.close();
306      m_reset_in = new DigitalInput(18); // Set MXP DIO8 high
307      Timer.delay(0.25); // Wait 250ms
308
309      configCalTime(cal_time);
310
311      if (!switchToStandardSPI()) {
312        return;
313      }
314
315      // Set IMU internal decimation to 819.2 SPS
316      writeRegister(SMPL_PRD, 0x0001);
317      // Enable Data Ready (LOW = Good Data) on DIO1 (PWM0 on MXP)
318      writeRegister(MSC_CTRL, 0x0016);
319      // Disable IMU internal Bartlett filter
320      writeRegister(SENS_AVG, 0x0400);
321      // Clear offset registers
322      writeRegister(XGYRO_OFF, 0x0000);
323      writeRegister(YGYRO_OFF, 0x0000);
324      writeRegister(ZGYRO_OFF, 0x0000);
325
326      // Configure standard SPI
327      if (!switchToAutoSPI()) {
328        return;
329      }
330      // Notify DS that IMU calibration delay is active
331      DriverStation.reportWarning(
332          "ADIS16448 IMU Detected. Starting initial calibration delay.", false);
333      // Wait for whatever time the user set as the start-up delay
334      try {
335        Thread.sleep((long) (m_calibration_time.value * 1.2 * 1000));
336      } catch (InterruptedException e) {
337      }
338      // Execute calibration routine
339      calibrate();
340      // Reset accumulated offsets
341      reset();
342      // Indicate to the acquire loop that we're done starting up
343      m_start_up_mode = false;
344      // Let the user know the IMU was initiallized successfully
345      DriverStation.reportWarning("ADIS16448 IMU Successfully Initialized!", false);
346      // Drive MXP PWM5 (IMU ready LED) low (active low)
347      m_status_led = new DigitalOutput(19);
348    }
349
350    // Report usage and post data to DS
351    HAL.report(tResourceType.kResourceType_ADIS16448, 0);
352    m_connected = true;
353  }
354
355  /**
356   * Checks the connection status of the IMU.
357   *
358   * @return True if the IMU is connected, false otherwise.
359   */
360  public boolean isConnected() {
361    if (m_simConnected != null) {
362      return m_simConnected.get();
363    }
364    return m_connected;
365  }
366
367  private static int toUShort(byte[] buf) {
368    return (((buf[0] & 0xFF) << 8) + ((buf[1] & 0xFF) << 0));
369  }
370
371  private static int toUByte(int... buf) {
372    return (buf[0] & 0xFF);
373  }
374
375  private static int toUShort(int... buf) {
376    return (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF));
377  }
378
379  /** */
380  private static long toULong(int sint) {
381    return sint & 0x00000000FFFFFFFFL;
382  }
383
384  /** */
385  private static int toShort(int... buf) {
386    return (short) (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF));
387  }
388
389  /** */
390  private static int toInt(int... buf) {
391    return (buf[0] & 0xFF) << 24 | (buf[1] & 0xFF) << 16 | (buf[2] & 0xFF) << 8 | (buf[3] & 0xFF);
392  }
393
394  /** */
395  private boolean switchToStandardSPI() {
396    // Check to see whether the acquire thread is active. If so, wait for it to stop
397    // producing data.
398    if (m_thread_active) {
399      m_thread_active = false;
400      while (!m_thread_idle) {
401        try {
402          Thread.sleep(10);
403        } catch (InterruptedException e) {
404        }
405      }
406      System.out.println("Paused the IMU processing thread successfully!");
407      // Maybe we're in auto SPI mode? If so, kill auto SPI, and then SPI.
408      if (m_spi != null && m_auto_configured) {
409        m_spi.stopAuto();
410        // We need to get rid of all the garbage left in the auto SPI buffer after
411        // stopping it.
412        // Sometimes data magically reappears, so we have to check the buffer size a
413        // couple of times
414        // to be sure we got it all. Yuck.
415        int[] trashBuffer = new int[200];
416        try {
417          Thread.sleep(100);
418        } catch (InterruptedException e) {
419        }
420        int data_count = m_spi.readAutoReceivedData(trashBuffer, 0, 0);
421        while (data_count > 0) {
422          /* Dequeue 200 at a time, or the remainder of the buffer if less than 200 */
423          m_spi.readAutoReceivedData(trashBuffer, Math.min(200, data_count), 0);
424          /* Update remaining buffer count */
425          data_count = m_spi.readAutoReceivedData(trashBuffer, 0, 0);
426        }
427        System.out.println("Paused auto SPI successfully.");
428      }
429    }
430    // There doesn't seem to be a SPI port active. Let's try to set one up
431    if (m_spi == null) {
432      System.out.println("Setting up a new SPI port.");
433      m_spi = new SPI(m_spi_port);
434      m_spi.setClockRate(1000000);
435      m_spi.setMode(SPI.Mode.kMode3);
436      m_spi.setChipSelectActiveLow();
437      readRegister(PROD_ID); // Dummy read
438
439      // Validate the product ID
440      if (readRegister(PROD_ID) != 16448) {
441        DriverStation.reportError("Could not find ADIS16448", false);
442        close();
443        return false;
444      }
445      return true;
446    } else {
447      // Maybe the SPI port is active, but not in auto SPI mode? Try to read the
448      // product ID.
449      readRegister(PROD_ID); // dummy read
450      if (readRegister(PROD_ID) != 16448) {
451        DriverStation.reportError("Could not find an ADIS16448", false);
452        close();
453        return false;
454      } else {
455        return true;
456      }
457    }
458  }
459
460  /** */
461  boolean switchToAutoSPI() {
462    // No SPI port has been set up. Go set one up first.
463    if (m_spi == null) {
464      if (!switchToStandardSPI()) {
465        DriverStation.reportError("Failed to start/restart auto SPI", false);
466        return false;
467      }
468    }
469    // Only set up the interrupt if needed.
470    if (m_auto_interrupt == null) {
471      m_auto_interrupt = new DigitalInput(10); // MXP DIO0
472    }
473    // The auto SPI controller gets angry if you try to set up two instances on one
474    // bus.
475    if (!m_auto_configured) {
476      m_spi.initAuto(8200);
477      m_auto_configured = true;
478    }
479    // Set auto SPI packet data and size
480    m_spi.setAutoTransmitData(new byte[] {GLOB_CMD}, 27);
481    // Configure auto stall time
482    m_spi.configureAutoStall(100, 1000, 255);
483    // Kick off auto SPI (Note: Device configuration impossible after auto SPI is
484    // activated)
485    m_spi.startAutoTrigger(m_auto_interrupt, true, false);
486
487    // Check to see if the acquire thread is running. If not, kick one off.
488    if (!m_acquire_task.isAlive()) {
489      m_first_run = true;
490      m_thread_active = true;
491      m_acquire_task.start();
492      System.out.println("New IMU Processing thread activated!");
493    } else {
494      // The thread was running, re-init run variables and start it up again.
495      m_first_run = true;
496      m_thread_active = true;
497      System.out.println("Old IMU Processing thread re-activated!");
498    }
499    // Looks like the thread didn't start for some reason. Abort.
500    if (!m_acquire_task.isAlive()) {
501      DriverStation.reportError("Failed to start/restart the acquire() thread.", false);
502      close();
503      return false;
504    }
505    return true;
506  }
507
508  /**
509   * Configures the decimation rate of the IMU.
510   *
511   * @param decimationRate The new decimation value.
512   * @return 0 if success, 1 if no change, 2 if error.
513   */
514  public int configDecRate(int decimationRate) {
515    // Switches the active SPI port to standard SPI mode, writes a new value to
516    // the DECIMATE register in the IMU, and re-enables auto SPI.
517    //
518    // This function enters standard SPI mode, writes a new DECIMATE setting to
519    // the IMU, adjusts the sample scale factor, and re-enters auto SPI mode.
520    int writeValue;
521    int readbackValue;
522    if (!switchToStandardSPI()) {
523      DriverStation.reportError("Failed to configure/reconfigure standard SPI.", false);
524      return 2;
525    }
526
527    /* Check max */
528    if (decimationRate > 9) {
529      DriverStation.reportError(
530          "Attempted to write an invalid decimation value. Capping at 9", false);
531      decimationRate = 9;
532    }
533    if (decimationRate < 0) {
534      DriverStation.reportError(
535          "Attempted to write an invalid decimation value. Capping at 0", false);
536      decimationRate = 0;
537    }
538
539    /* Shift decimation setting to correct position and select internal sync */
540    writeValue = (decimationRate << 8) | 0x1;
541
542    /* Apply to IMU */
543    writeRegister(SMPL_PRD, writeValue);
544
545    /* Perform read back to verify write */
546    readbackValue = readRegister(SMPL_PRD);
547
548    /* Throw error for invalid write */
549    if (readbackValue != writeValue) {
550      DriverStation.reportError("ADIS16448 SMPL_PRD write failed.", false);
551    }
552
553    if (!switchToAutoSPI()) {
554      DriverStation.reportError("Failed to configure/reconfigure auto SPI.", false);
555      return 2;
556    }
557    return 0;
558  }
559
560  /**
561   * Configures calibration time
562   *
563   * @param new_cal_time New calibration time
564   * @return 1 if the new calibration time is the same as the current one else 0
565   */
566  public final int configCalTime(CalibrationTime new_cal_time) {
567    if (m_calibration_time == new_cal_time) {
568      return 1;
569    } else {
570      m_calibration_time = new_cal_time;
571      m_avg_size = m_calibration_time.value * 819;
572      initOffsetBuffer(m_avg_size);
573      return 0;
574    }
575  }
576
577  private void initOffsetBuffer(int size) {
578    // Avoid exceptions in the case of bad arguments
579    if (size < 1) {
580      size = 1;
581    }
582    // Set average size to size (correct bad values)
583    m_avg_size = size;
584    synchronized (this) {
585      // Resize vector
586      m_offset_data_gyro_rate_x = new double[size];
587      m_offset_data_gyro_rate_y = new double[size];
588      m_offset_data_gyro_rate_z = new double[size];
589      // Set accumulate count to 0
590      m_accum_count = 0;
591    }
592  }
593
594  /**
595   * Calibrate the gyro. It's important to make sure that the robot is not moving while the
596   * calibration is in progress, this is typically done when the robot is first turned on while it's
597   * sitting at rest before the match starts.
598   */
599  public void calibrate() {
600    synchronized (this) {
601      int gyroAverageSize = Math.min(m_accum_count, m_avg_size);
602      double accum_gyro_rate_x = 0.0;
603      double accum_gyro_rate_y = 0.0;
604      double accum_gyro_rate_z = 0.0;
605      for (int i = 0; i < gyroAverageSize; i++) {
606        accum_gyro_rate_x += m_offset_data_gyro_rate_x[i];
607        accum_gyro_rate_y += m_offset_data_gyro_rate_y[i];
608        accum_gyro_rate_z += m_offset_data_gyro_rate_z[i];
609      }
610      m_gyro_rate_offset_x = accum_gyro_rate_x / gyroAverageSize;
611      m_gyro_rate_offset_y = accum_gyro_rate_y / gyroAverageSize;
612      m_gyro_rate_offset_z = accum_gyro_rate_z / gyroAverageSize;
613      m_integ_gyro_angle_x = 0.0;
614      m_integ_gyro_angle_y = 0.0;
615      m_integ_gyro_angle_z = 0.0;
616      // System.out.println("Avg Size: " + gyroAverageSize + "X Off: " +
617      // m_gyro_rate_offset_x + "Y Off: " + m_gyro_rate_offset_y + "Z Off: " +
618      // m_gyro_rate_offset_z);
619    }
620  }
621
622  /**
623   * Sets the yaw axis
624   *
625   * @param yaw_axis The new yaw axis to use
626   * @return 1 if the new yaw axis is the same as the current one else 0.
627   */
628  public int setYawAxis(IMUAxis yaw_axis) {
629    if (m_yaw_axis == yaw_axis) {
630      return 1;
631    }
632    m_yaw_axis = yaw_axis;
633    reset();
634    return 0;
635  }
636
637  private int readRegister(final int reg) {
638    // ByteBuffer buf = ByteBuffer.allocateDirect(2);
639    final byte[] buf = new byte[2];
640    // buf.order(ByteOrder.BIG_ENDIAN);
641    buf[0] = (byte) (reg & 0x7f);
642    buf[1] = (byte) 0;
643
644    m_spi.write(buf, 2);
645    m_spi.read(false, buf, 2);
646
647    return toUShort(buf);
648  }
649
650  private void writeRegister(final int reg, final int val) {
651    final byte[] buf = new byte[2];
652    // low byte
653    buf[0] = (byte) (0x80 | reg);
654    buf[1] = (byte) (val & 0xff);
655    m_spi.write(buf, 2);
656    // high byte
657    buf[0] = (byte) (0x81 | reg);
658    buf[1] = (byte) (val >> 8);
659    m_spi.write(buf, 2);
660  }
661
662  /**
663   * Reset the gyro.
664   *
665   * <p>Resets the gyro accumulations to a heading of zero. This can be used if there is significant
666   * drift in the gyro and it needs to be recalibrated after running.
667   */
668  public void reset() {
669    synchronized (this) {
670      m_integ_gyro_angle_x = 0.0;
671      m_integ_gyro_angle_y = 0.0;
672      m_integ_gyro_angle_z = 0.0;
673    }
674  }
675
676  /** Delete (free) the spi port used for the IMU. */
677  @Override
678  public void close() {
679    if (m_thread_active) {
680      m_thread_active = false;
681      try {
682        if (m_acquire_task != null) {
683          m_acquire_task.join();
684          m_acquire_task = null;
685        }
686      } catch (InterruptedException e) {
687      }
688      if (m_spi != null) {
689        if (m_auto_configured) {
690          m_spi.stopAuto();
691        }
692        m_spi.close();
693        m_auto_configured = false;
694        if (m_auto_interrupt != null) {
695          m_auto_interrupt.close();
696          m_auto_interrupt = null;
697        }
698        m_spi = null;
699      }
700    }
701    if (m_simDevice != null) {
702      m_simDevice.close();
703      m_simDevice = null;
704    }
705    System.out.println("Finished cleaning up after the IMU driver.");
706  }
707
708  /** */
709  private void acquire() {
710    // Set data packet length
711    final int dataset_len = 29; // 18 data points + timestamp
712    final int BUFFER_SIZE = 4000;
713
714    // Set up buffers and variables
715    int[] buffer = new int[BUFFER_SIZE];
716    int data_count = 0;
717    int data_remainder = 0;
718    int data_to_read = 0;
719    int bufferAvgIndex = 0;
720    double previous_timestamp = 0.0;
721    double delta_angle = 0.0;
722    double gyro_rate_x = 0.0;
723    double gyro_rate_y = 0.0;
724    double gyro_rate_z = 0.0;
725    double accel_x = 0.0;
726    double accel_y = 0.0;
727    double accel_z = 0.0;
728    double mag_x = 0.0;
729    double mag_y = 0.0;
730    double mag_z = 0.0;
731    double baro = 0.0;
732    double temp = 0.0;
733    double gyro_rate_x_si = 0.0;
734    double gyro_rate_y_si = 0.0;
735    double gyro_rate_z_si = 0.0;
736    double accel_x_si = 0.0;
737    double accel_y_si = 0.0;
738    double accel_z_si = 0.0;
739    double compAngleX = 0.0;
740    double compAngleY = 0.0;
741    double accelAngleX = 0.0;
742    double accelAngleY = 0.0;
743
744    while (true) {
745      // Sleep loop for 5ms
746      try {
747        Thread.sleep(5);
748      } catch (InterruptedException e) {
749      }
750
751      if (m_thread_active) {
752        m_thread_idle = false;
753
754        data_count =
755            m_spi.readAutoReceivedData(
756                buffer, 0, 0); // Read number of bytes currently stored in the buffer
757        data_remainder =
758            data_count % dataset_len; // Check if frame is incomplete. Add 1 because of timestamp
759        data_to_read = data_count - data_remainder; // Remove incomplete data from read count
760        if (data_to_read > BUFFER_SIZE) {
761          DriverStation.reportWarning(
762              "ADIS16448 data processing thread overrun has occurred!", false);
763          data_to_read = BUFFER_SIZE - (BUFFER_SIZE % dataset_len);
764        }
765        m_spi.readAutoReceivedData(
766            buffer, data_to_read, 0); // Read data from DMA buffer (only complete sets)
767
768        // Could be multiple data sets in the buffer. Handle each one.
769        for (int i = 0; i < data_to_read; i += dataset_len) {
770          // Calculate CRC-16 on each data packet
771          int calc_crc = 0x0000FFFF; // Starting word
772          int read_byte = 0;
773          int imu_crc = 0;
774          for (int k = 5;
775              k < 27;
776              k += 2) { // Cycle through XYZ GYRO, XYZ ACCEL, XYZ MAG, BARO, TEMP (Ignore Status &
777            // CRC)
778            read_byte = buffer[i + k + 1]; // Process LSB
779            calc_crc = (calc_crc >>> 8) ^ adiscrc[(calc_crc & 0x000000FF) ^ read_byte];
780            read_byte = buffer[i + k]; // Process MSB
781            calc_crc = (calc_crc >>> 8) ^ adiscrc[(calc_crc & 0x000000FF) ^ read_byte];
782          }
783          calc_crc = ~calc_crc & 0xFFFF; // Complement
784          calc_crc = ((calc_crc << 8) | (calc_crc >> 8)) & 0xFFFF; // Flip LSB & MSB
785          imu_crc = toUShort(buffer[i + 27], buffer[i + 28]); // Extract DUT CRC from data buffer
786
787          if (calc_crc == imu_crc) {
788            // Timestamp is at buffer[i]
789            m_dt = ((double) buffer[i] - previous_timestamp) / 1000000.0;
790
791            // Scale sensor data
792            gyro_rate_x = (toShort(buffer[i + 5], buffer[i + 6]) * 0.04);
793            gyro_rate_y = (toShort(buffer[i + 7], buffer[i + 8]) * 0.04);
794            gyro_rate_z = (toShort(buffer[i + 9], buffer[i + 10]) * 0.04);
795            accel_x = (toShort(buffer[i + 11], buffer[i + 12]) * 0.833);
796            accel_y = (toShort(buffer[i + 13], buffer[i + 14]) * 0.833);
797            accel_z = (toShort(buffer[i + 15], buffer[i + 16]) * 0.833);
798            mag_x = (toShort(buffer[i + 17], buffer[i + 18]) * 0.1429);
799            mag_y = (toShort(buffer[i + 19], buffer[i + 20]) * 0.1429);
800            mag_z = (toShort(buffer[i + 21], buffer[i + 22]) * 0.1429);
801            baro = (toShort(buffer[i + 23], buffer[i + 24]) * 0.02);
802            temp = (toShort(buffer[i + 25], buffer[i + 26]) * 0.07386 + 31.0);
803
804            // Convert scaled sensor data to SI units (for tilt calculations)
805            // TODO: Should the unit outputs be selectable?
806            gyro_rate_x_si = gyro_rate_x * deg_to_rad;
807            gyro_rate_y_si = gyro_rate_y * deg_to_rad;
808            gyro_rate_z_si = gyro_rate_z * deg_to_rad;
809            accel_x_si = accel_x * grav;
810            accel_y_si = accel_y * grav;
811            accel_z_si = accel_z * grav;
812            // Store timestamp for next iteration
813            previous_timestamp = buffer[i];
814            // Calculate alpha for use with the complementary filter
815            m_alpha = m_tau / (m_tau + m_dt);
816            // Calculate complementary filter
817            if (m_first_run) {
818              // Set up inclinometer calculations for first run
819              accelAngleX =
820                  Math.atan2(
821                      -accel_x_si,
822                      Math.sqrt((accel_y_si * accel_y_si) + (-accel_z_si * -accel_z_si)));
823              accelAngleY =
824                  Math.atan2(
825                      accel_y_si,
826                      Math.sqrt((-accel_x_si * -accel_x_si) + (-accel_z_si * -accel_z_si)));
827              compAngleX = accelAngleX;
828              compAngleY = accelAngleY;
829            } else {
830              // Run inclinometer calculations
831              accelAngleX =
832                  Math.atan2(
833                      -accel_x_si,
834                      Math.sqrt((accel_y_si * accel_y_si) + (-accel_z_si * -accel_z_si)));
835              accelAngleY =
836                  Math.atan2(
837                      accel_y_si,
838                      Math.sqrt((-accel_x_si * -accel_x_si) + (-accel_z_si * -accel_z_si)));
839              accelAngleX = formatAccelRange(accelAngleX, -accel_z_si);
840              accelAngleY = formatAccelRange(accelAngleY, -accel_z_si);
841              compAngleX = compFilterProcess(compAngleX, accelAngleX, -gyro_rate_y_si);
842              compAngleY = compFilterProcess(compAngleY, accelAngleY, -gyro_rate_x_si);
843            }
844
845            // Update global variables and state
846            synchronized (this) {
847              // Ignore first, integrated sample
848              if (m_first_run) {
849                m_integ_gyro_angle_x = 0.0;
850                m_integ_gyro_angle_y = 0.0;
851                m_integ_gyro_angle_z = 0.0;
852              } else {
853                // Accumulate gyro for offset calibration
854                // Add to buffer
855                bufferAvgIndex = m_accum_count % m_avg_size;
856                m_offset_data_gyro_rate_x[bufferAvgIndex] = gyro_rate_x;
857                m_offset_data_gyro_rate_y[bufferAvgIndex] = gyro_rate_y;
858                m_offset_data_gyro_rate_z[bufferAvgIndex] = gyro_rate_z;
859                // Increment counter
860                m_accum_count++;
861              }
862              if (!m_start_up_mode) {
863                m_gyro_rate_x = gyro_rate_x;
864                m_gyro_rate_y = gyro_rate_y;
865                m_gyro_rate_z = gyro_rate_z;
866                m_accel_x = accel_x;
867                m_accel_y = accel_y;
868                m_accel_z = accel_z;
869                m_mag_x = mag_x;
870                m_mag_y = mag_y;
871                m_mag_z = mag_z;
872                m_baro = baro;
873                m_temp = temp;
874                m_compAngleX = compAngleX * rad_to_deg;
875                m_compAngleY = compAngleY * rad_to_deg;
876                m_accelAngleX = accelAngleX * rad_to_deg;
877                m_accelAngleY = accelAngleY * rad_to_deg;
878                // Accumulate gyro for angle integration and publish to global variables
879                m_integ_gyro_angle_x += (gyro_rate_x - m_gyro_rate_offset_x) * m_dt;
880                m_integ_gyro_angle_y += (gyro_rate_y - m_gyro_rate_offset_y) * m_dt;
881                m_integ_gyro_angle_z += (gyro_rate_z - m_gyro_rate_offset_z) * m_dt;
882              }
883              // System.out.println("Good CRC");
884            }
885            m_first_run = false;
886          } else {
887            // System.out.println("Bad CRC");
888            /*
889             * System.out.println("Calc CRC: " + calc_crc);
890             * System.out.println("IMU CRC: " + imu_crc);
891             * System.out.println(
892             * buffer[i] + " " +
893             * (buffer[i + 1]) + " " + (buffer[i + 2]) + " " +
894             * (buffer[i + 3]) + " " + (buffer[i + 4]) + " " +
895             * (buffer[i + 5]) + " " + (buffer[i + 6]) + " " +
896             * (buffer[i + 7]) + " " + (buffer[i + 8]) + " " +
897             * (buffer[i + 9]) + " " + (buffer[i + 10]) + " " +
898             * (buffer[i + 11]) + " " + (buffer[i + 12]) + " " +
899             * (buffer[i + 13]) + " " + (buffer[i + 14]) + " " +
900             * (buffer[i + 15]) + " " + (buffer[i + 16]) + " " +
901             * (buffer[i + 17]) + " " + (buffer[i + 18]) + " " +
902             * (buffer[i + 19]) + " " + (buffer[i + 20]) + " " +
903             * (buffer[i + 21]) + " " + (buffer[i + 22]) + " " +
904             * (buffer[i + 23]) + " " + (buffer[i + 24]) + " " +
905             * (buffer[i + 25]) + " " + (buffer[i + 26]) + " " +
906             * (buffer[i + 27]) + " " + (buffer[i + 28]));
907             */
908          }
909        }
910      } else {
911        m_thread_idle = true;
912        data_count = 0;
913        data_remainder = 0;
914        data_to_read = 0;
915        previous_timestamp = 0.0;
916        delta_angle = 0.0;
917        gyro_rate_x = 0.0;
918        gyro_rate_y = 0.0;
919        gyro_rate_z = 0.0;
920        accel_x = 0.0;
921        accel_y = 0.0;
922        accel_z = 0.0;
923        mag_x = 0.0;
924        mag_y = 0.0;
925        mag_z = 0.0;
926        baro = 0.0;
927        temp = 0.0;
928        gyro_rate_x_si = 0.0;
929        gyro_rate_y_si = 0.0;
930        gyro_rate_z_si = 0.0;
931        accel_x_si = 0.0;
932        accel_y_si = 0.0;
933        accel_z_si = 0.0;
934        compAngleX = 0.0;
935        compAngleY = 0.0;
936        accelAngleX = 0.0;
937        accelAngleY = 0.0;
938      }
939    }
940  }
941
942  /**
943   * @param compAngle
944   * @param accAngle
945   * @return
946   */
947  private double formatFastConverge(double compAngle, double accAngle) {
948    if (compAngle > accAngle + Math.PI) {
949      compAngle = compAngle - 2.0 * Math.PI;
950    } else if (accAngle > compAngle + Math.PI) {
951      compAngle = compAngle + 2.0 * Math.PI;
952    }
953    return compAngle;
954  }
955
956  /**
957   * @param compAngle
958   * @return
959   */
960  private double formatRange0to2PI(double compAngle) {
961    while (compAngle >= 2 * Math.PI) {
962      compAngle = compAngle - 2.0 * Math.PI;
963    }
964    while (compAngle < 0.0) {
965      compAngle = compAngle + 2.0 * Math.PI;
966    }
967    return compAngle;
968  }
969
970  /**
971   * @param accelAngle
972   * @param accelZ
973   * @return
974   */
975  private double formatAccelRange(double accelAngle, double accelZ) {
976    if (accelZ < 0.0) {
977      accelAngle = Math.PI - accelAngle;
978    } else if (accelZ > 0.0 && accelAngle < 0.0) {
979      accelAngle = 2.0 * Math.PI + accelAngle;
980    }
981    return accelAngle;
982  }
983
984  /**
985   * @param compAngle
986   * @param accelAngle
987   * @param omega
988   * @return
989   */
990  private double compFilterProcess(double compAngle, double accelAngle, double omega) {
991    compAngle = formatFastConverge(compAngle, accelAngle);
992    compAngle = m_alpha * (compAngle + omega * m_dt) + (1.0 - m_alpha) * accelAngle;
993    compAngle = formatRange0to2PI(compAngle);
994    if (compAngle > Math.PI) {
995      compAngle = compAngle - 2.0 * Math.PI;
996    }
997    return compAngle;
998  }
999
1000  /**
1001   * Returns the yaw axis angle in degrees (CCW positive).
1002   *
1003   * @return Yaw axis angle in degrees (CCW positive).
1004   */
1005  public synchronized double getAngle() {
1006    switch (m_yaw_axis) {
1007      case kX:
1008        return getGyroAngleX();
1009      case kY:
1010        return getGyroAngleY();
1011      case kZ:
1012        return getGyroAngleZ();
1013      default:
1014        return 0.0;
1015    }
1016  }
1017
1018  /**
1019   * Returns the yaw axis angular rate in degrees per second (CCW positive).
1020   *
1021   * @return Yaw axis angular rate in degrees per second (CCW positive).
1022   */
1023  public synchronized double getRate() {
1024    switch (m_yaw_axis) {
1025      case kX:
1026        return getGyroRateX();
1027      case kY:
1028        return getGyroRateY();
1029      case kZ:
1030        return getGyroRateZ();
1031      default:
1032        return 0.0;
1033    }
1034  }
1035
1036  /**
1037   * Returns which axis, kX, kY, or kZ, is set to the yaw axis.
1038   *
1039   * @return IMUAxis Yaw Axis
1040   */
1041  public IMUAxis getYawAxis() {
1042    return m_yaw_axis;
1043  }
1044
1045  /**
1046   * Returns the accumulated gyro angle in the X axis in degrees.
1047   *
1048   * @return The accumulated gyro angle in the X axis in degrees.
1049   */
1050  public synchronized double getGyroAngleX() {
1051    if (m_simGyroAngleX != null) {
1052      return m_simGyroAngleX.get();
1053    }
1054    return m_integ_gyro_angle_x;
1055  }
1056
1057  /**
1058   * Returns the accumulated gyro angle in the Y axis in degrees.
1059   *
1060   * @return The accumulated gyro angle in the Y axis in degrees.
1061   */
1062  public synchronized double getGyroAngleY() {
1063    if (m_simGyroAngleY != null) {
1064      return m_simGyroAngleY.get();
1065    }
1066    return m_integ_gyro_angle_y;
1067  }
1068
1069  /**
1070   * Returns the accumulated gyro angle in the Z axis in degrees.
1071   *
1072   * @return The accumulated gyro angle in the Z axis in degrees.
1073   */
1074  public synchronized double getGyroAngleZ() {
1075    if (m_simGyroAngleZ != null) {
1076      return m_simGyroAngleZ.get();
1077    }
1078    return m_integ_gyro_angle_z;
1079  }
1080
1081  /**
1082   * Returns the gyro angular rate in the X axis in degrees per second.
1083   *
1084   * @return The gyro angular rate in the X axis in degrees per second.
1085   */
1086  public synchronized double getGyroRateX() {
1087    if (m_simGyroRateX != null) {
1088      return m_simGyroRateX.get();
1089    }
1090    return m_gyro_rate_x;
1091  }
1092
1093  /**
1094   * Returns the gyro angular rate in the Y axis in degrees per second.
1095   *
1096   * @return The gyro angular rate in the Y axis in degrees per second.
1097   */
1098  public synchronized double getGyroRateY() {
1099    if (m_simGyroRateY != null) {
1100      return m_simGyroRateY.get();
1101    }
1102    return m_gyro_rate_y;
1103  }
1104
1105  /**
1106   * Returns the gyro angular rate in the Z axis in degrees per second.
1107   *
1108   * @return The gyro angular rate in the Z axis in degrees per second.
1109   */
1110  public synchronized double getGyroRateZ() {
1111    if (m_simGyroRateZ != null) {
1112      return m_simGyroRateZ.get();
1113    }
1114    return m_gyro_rate_z;
1115  }
1116
1117  /**
1118   * Returns the acceleration in the X axis in meters per second squared.
1119   *
1120   * @return The acceleration in the X axis in meters per second squared.
1121   */
1122  public synchronized double getAccelX() {
1123    if (m_simAccelX != null) {
1124      return m_simAccelX.get();
1125    }
1126    return m_accel_x * 9.81;
1127  }
1128
1129  /**
1130   * Returns the acceleration in the Y axis in meters per second squared.
1131   *
1132   * @return The acceleration in the Y axis in meters per second squared.
1133   */
1134  public synchronized double getAccelY() {
1135    if (m_simAccelY != null) {
1136      return m_simAccelY.get();
1137    }
1138    return m_accel_y * 9.81;
1139  }
1140
1141  /**
1142   * Returns the acceleration in the Z axis in meters per second squared.
1143   *
1144   * @return The acceleration in the Z axis in meters per second squared.
1145   */
1146  public synchronized double getAccelZ() {
1147    if (m_simAccelZ != null) {
1148      return m_simAccelZ.get();
1149    }
1150    return m_accel_z * 9.81;
1151  }
1152
1153  /**
1154   * Returns the magnetic field strength in the X axis in Tesla.
1155   *
1156   * @return The magnetic field strength in the X axis in Tesla.
1157   */
1158  public synchronized double getMagneticFieldX() {
1159    // mG to T
1160    return m_mag_x * 1e-7;
1161  }
1162
1163  /**
1164   * Returns the magnetic field strength in the Y axis in Tesla.
1165   *
1166   * @return The magnetic field strength in the Y axis in Tesla.
1167   */
1168  public synchronized double getMagneticFieldY() {
1169    // mG to T
1170    return m_mag_y * 1e-7;
1171  }
1172
1173  /**
1174   * Returns the magnetic field strength in the Z axis in Tesla.
1175   *
1176   * @return The magnetic field strength in the Z axis in Tesla.
1177   */
1178  public synchronized double getMagneticFieldZ() {
1179    // mG to T
1180    return m_mag_z * 1e-7;
1181  }
1182
1183  /**
1184   * Returns the complementary angle around the X axis computed from accelerometer and gyro rate
1185   * measurements.
1186   *
1187   * @return The X-axis complementary angle in degrees.
1188   */
1189  public synchronized double getXComplementaryAngle() {
1190    return m_compAngleX;
1191  }
1192
1193  /**
1194   * Returns the complementary angle around the Y axis computed from accelerometer and gyro rate
1195   * measurements.
1196   *
1197   * @return The Y-axis complementary angle in degrees.
1198   */
1199  public synchronized double getYComplementaryAngle() {
1200    return m_compAngleY;
1201  }
1202
1203  /**
1204   * Returns the X-axis filtered acceleration angle in degrees.
1205   *
1206   * @return The X-axis filtered acceleration angle in degrees.
1207   */
1208  public synchronized double getXFilteredAccelAngle() {
1209    return m_accelAngleX;
1210  }
1211
1212  /**
1213   * Returns the Y-axis filtered acceleration angle in degrees.
1214   *
1215   * @return The Y-axis filtered acceleration angle in degrees.
1216   */
1217  public synchronized double getYFilteredAccelAngle() {
1218    return m_accelAngleY;
1219  }
1220
1221  /**
1222   * Returns the barometric pressure in PSI.
1223   *
1224   * @return The barometric pressure in PSI.
1225   */
1226  public synchronized double getBarometricPressure() {
1227    // mbar to PSI conversion
1228    return m_baro * 0.0145;
1229  }
1230
1231  /**
1232   * Returns the temperature in degrees Celsius.
1233   *
1234   * @return The temperature in degrees Celsius.
1235   */
1236  public synchronized double getTemperature() {
1237    return m_temp;
1238  }
1239
1240  /**
1241   * Get the SPI port number.
1242   *
1243   * @return The SPI port number.
1244   */
1245  public int getPort() {
1246    return m_spi_port.value;
1247  }
1248
1249  @Override
1250  public void initSendable(SendableBuilder builder) {
1251    builder.setSmartDashboardType("Gyro");
1252    builder.addDoubleProperty("Value", this::getAngle, null);
1253  }
1254}