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