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