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 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 final int value; 112 113 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 @SuppressWarnings("this-escape") 261 public ADIS16448_IMU(final IMUAxis yaw_axis, SPI.Port port, CalibrationTime cal_time) { 262 m_yaw_axis = yaw_axis; 263 m_spi_port = port; 264 265 m_acquire_task = new Thread(new AcquireTask(this)); 266 267 m_simDevice = SimDevice.create("Gyro:ADIS16448", port.value); 268 if (m_simDevice != null) { 269 m_simConnected = m_simDevice.createBoolean("connected", SimDevice.Direction.kInput, true); 270 m_simGyroAngleX = m_simDevice.createDouble("gyro_angle_x", SimDevice.Direction.kInput, 0.0); 271 m_simGyroAngleY = m_simDevice.createDouble("gyro_angle_y", SimDevice.Direction.kInput, 0.0); 272 m_simGyroAngleZ = m_simDevice.createDouble("gyro_angle_z", SimDevice.Direction.kInput, 0.0); 273 m_simGyroRateX = m_simDevice.createDouble("gyro_rate_x", SimDevice.Direction.kInput, 0.0); 274 m_simGyroRateY = m_simDevice.createDouble("gyro_rate_y", SimDevice.Direction.kInput, 0.0); 275 m_simGyroRateZ = m_simDevice.createDouble("gyro_rate_z", SimDevice.Direction.kInput, 0.0); 276 m_simAccelX = m_simDevice.createDouble("accel_x", SimDevice.Direction.kInput, 0.0); 277 m_simAccelY = m_simDevice.createDouble("accel_y", SimDevice.Direction.kInput, 0.0); 278 m_simAccelZ = m_simDevice.createDouble("accel_z", SimDevice.Direction.kInput, 0.0); 279 } 280 281 if (m_simDevice == null) { 282 // Force the IMU reset pin to toggle on startup (doesn't require DS enable) 283 m_reset_out = new DigitalOutput(18); // Drive MXP DIO8 low 284 Timer.delay(0.01); // Wait 10ms 285 m_reset_out.close(); 286 m_reset_in = new DigitalInput(18); // Set MXP DIO8 high 287 Timer.delay(0.25); // Wait 250ms 288 289 configCalTime(cal_time); 290 291 if (!switchToStandardSPI()) { 292 return; 293 } 294 295 // Set IMU internal decimation to 819.2 SPS 296 writeRegister(SMPL_PRD, 0x0001); 297 // Enable Data Ready (LOW = Good Data) on DIO1 (PWM0 on MXP) 298 writeRegister(MSC_CTRL, 0x0016); 299 // Disable IMU internal Bartlett filter 300 writeRegister(SENS_AVG, 0x0400); 301 // Clear offset registers 302 writeRegister(XGYRO_OFF, 0x0000); 303 writeRegister(YGYRO_OFF, 0x0000); 304 writeRegister(ZGYRO_OFF, 0x0000); 305 306 // Configure standard SPI 307 if (!switchToAutoSPI()) { 308 return; 309 } 310 // Notify DS that IMU calibration delay is active 311 DriverStation.reportWarning( 312 "ADIS16448 IMU Detected. Starting initial calibration delay.", false); 313 // Wait for whatever time the user set as the start-up delay 314 try { 315 Thread.sleep((long) (m_calibration_time.value * 1.2 * 1000)); 316 } catch (InterruptedException e) { 317 } 318 // Execute calibration routine 319 calibrate(); 320 // Reset accumulated offsets 321 reset(); 322 // Indicate to the acquire loop that we're done starting up 323 m_start_up_mode = false; 324 // Let the user know the IMU was initiallized successfully 325 DriverStation.reportWarning("ADIS16448 IMU Successfully Initialized!", false); 326 // Drive MXP PWM5 (IMU ready LED) low (active low) 327 m_status_led = new DigitalOutput(19); 328 } 329 330 // Report usage and post data to DS 331 HAL.report(tResourceType.kResourceType_ADIS16448, 0); 332 m_connected = true; 333 } 334 335 public boolean isConnected() { 336 if (m_simConnected != null) { 337 return m_simConnected.get(); 338 } 339 return m_connected; 340 } 341 342 /** */ 343 private static int toUShort(byte[] buf) { 344 return (((buf[0] & 0xFF) << 8) + ((buf[1] & 0xFF) << 0)); 345 } 346 347 private static int toUByte(int... buf) { 348 return (buf[0] & 0xFF); 349 } 350 351 public static int toUShort(int... buf) { 352 return (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF)); 353 } 354 355 /** */ 356 private static long toULong(int sint) { 357 return sint & 0x00000000FFFFFFFFL; 358 } 359 360 /** */ 361 private static int toShort(int... buf) { 362 return (short) (((buf[0] & 0xFF) << 8) + ((buf[1] & 0xFF))); 363 } 364 365 /** */ 366 private static int toInt(int... buf) { 367 return (buf[0] & 0xFF) << 24 | (buf[1] & 0xFF) << 16 | (buf[2] & 0xFF) << 8 | (buf[3] & 0xFF); 368 } 369 370 /** */ 371 private boolean switchToStandardSPI() { 372 // Check to see whether the acquire thread is active. If so, wait for it to stop 373 // producing data. 374 if (m_thread_active) { 375 m_thread_active = false; 376 while (!m_thread_idle) { 377 try { 378 Thread.sleep(10); 379 } catch (InterruptedException e) { 380 } 381 } 382 System.out.println("Paused the IMU processing thread successfully!"); 383 // Maybe we're in auto SPI mode? If so, kill auto SPI, and then SPI. 384 if (m_spi != null && m_auto_configured) { 385 m_spi.stopAuto(); 386 // We need to get rid of all the garbage left in the auto SPI buffer after 387 // stopping it. 388 // Sometimes data magically reappears, so we have to check the buffer size a 389 // couple of times 390 // to be sure we got it all. Yuck. 391 int[] trashBuffer = new int[200]; 392 try { 393 Thread.sleep(100); 394 } catch (InterruptedException e) { 395 } 396 int data_count = m_spi.readAutoReceivedData(trashBuffer, 0, 0); 397 while (data_count > 0) { 398 /* Dequeue 200 at a time, or the remainder of the buffer if less than 200 */ 399 m_spi.readAutoReceivedData(trashBuffer, Math.min(200, data_count), 0); 400 /* Update remaining buffer count */ 401 data_count = m_spi.readAutoReceivedData(trashBuffer, 0, 0); 402 } 403 System.out.println("Paused auto SPI successfully."); 404 } 405 } 406 // There doesn't seem to be a SPI port active. Let's try to set one up 407 if (m_spi == null) { 408 System.out.println("Setting up a new SPI port."); 409 m_spi = new SPI(m_spi_port); 410 m_spi.setClockRate(1000000); 411 m_spi.setMode(SPI.Mode.kMode3); 412 m_spi.setChipSelectActiveLow(); 413 readRegister(PROD_ID); // Dummy read 414 415 // Validate the product ID 416 if (readRegister(PROD_ID) != 16448) { 417 DriverStation.reportError("Could not find ADIS16448", false); 418 close(); 419 return false; 420 } 421 return true; 422 } else { 423 // Maybe the SPI port is active, but not in auto SPI mode? Try to read the 424 // product ID. 425 readRegister(PROD_ID); // dummy read 426 if (readRegister(PROD_ID) != 16448) { 427 DriverStation.reportError("Could not find an ADIS16448", false); 428 close(); 429 return false; 430 } else { 431 return true; 432 } 433 } 434 } 435 436 /** */ 437 boolean switchToAutoSPI() { 438 // No SPI port has been set up. Go set one up first. 439 if (m_spi == null) { 440 if (!switchToStandardSPI()) { 441 DriverStation.reportError("Failed to start/restart auto SPI", false); 442 return false; 443 } 444 } 445 // Only set up the interrupt if needed. 446 if (m_auto_interrupt == null) { 447 m_auto_interrupt = new DigitalInput(10); // MXP DIO0 448 } 449 // The auto SPI controller gets angry if you try to set up two instances on one 450 // bus. 451 if (!m_auto_configured) { 452 m_spi.initAuto(8200); 453 m_auto_configured = true; 454 } 455 // Set auto SPI packet data and size 456 m_spi.setAutoTransmitData(new byte[] {GLOB_CMD}, 27); 457 // Configure auto stall time 458 m_spi.configureAutoStall(100, 1000, 255); 459 // Kick off auto SPI (Note: Device configuration impossible after auto SPI is 460 // activated) 461 m_spi.startAutoTrigger(m_auto_interrupt, true, false); 462 463 // Check to see if the acquire thread is running. If not, kick one off. 464 if (!m_acquire_task.isAlive()) { 465 m_first_run = true; 466 m_thread_active = true; 467 m_acquire_task.start(); 468 System.out.println("New IMU Processing thread activated!"); 469 } else { 470 // The thread was running, re-init run variables and start it up again. 471 m_first_run = true; 472 m_thread_active = true; 473 System.out.println("Old IMU Processing thread re-activated!"); 474 } 475 // Looks like the thread didn't start for some reason. Abort. 476 if (!m_acquire_task.isAlive()) { 477 DriverStation.reportError("Failed to start/restart the acquire() thread.", false); 478 close(); 479 return false; 480 } 481 return true; 482 } 483 484 public int configDecRate(int m_decRate) { 485 int writeValue; 486 int readbackValue; 487 if (!switchToStandardSPI()) { 488 DriverStation.reportError("Failed to configure/reconfigure standard SPI.", false); 489 return 2; 490 } 491 492 /* Check max */ 493 if (m_decRate > 9) { 494 DriverStation.reportError( 495 "Attempted to write an invalid decimation value. Capping at 9", false); 496 m_decRate = 9; 497 } 498 if (m_decRate < 0) { 499 DriverStation.reportError( 500 "Attempted to write an invalid decimation value. Capping at 0", false); 501 m_decRate = 0; 502 } 503 504 /* Shift decimation setting to correct position and select internal sync */ 505 writeValue = (m_decRate << 8) | 0x1; 506 507 /* Apply to IMU */ 508 writeRegister(SMPL_PRD, writeValue); 509 510 /* Perform read back to verify write */ 511 readbackValue = readRegister(SMPL_PRD); 512 513 /* Throw error for invalid write */ 514 if (readbackValue != writeValue) { 515 DriverStation.reportError("ADIS16448 SMPL_PRD write failed.", false); 516 } 517 518 if (!switchToAutoSPI()) { 519 DriverStation.reportError("Failed to configure/reconfigure auto SPI.", false); 520 return 2; 521 } 522 return 0; 523 } 524 525 /** 526 * Configures calibration time 527 * 528 * @param new_cal_time New calibration time 529 * @return 1 if the new calibration time is the same as the current one else 0 530 */ 531 public final int configCalTime(CalibrationTime new_cal_time) { 532 if (m_calibration_time == new_cal_time) { 533 return 1; 534 } else { 535 m_calibration_time = new_cal_time; 536 m_avg_size = m_calibration_time.value * 819; 537 initOffsetBuffer(m_avg_size); 538 return 0; 539 } 540 } 541 542 private void initOffsetBuffer(int size) { 543 // Avoid exceptions in the case of bad arguments 544 if (size < 1) { 545 size = 1; 546 } 547 // Set average size to size (correct bad values) 548 m_avg_size = size; 549 synchronized (this) { 550 // Resize vector 551 m_offset_data_gyro_rate_x = new double[size]; 552 m_offset_data_gyro_rate_y = new double[size]; 553 m_offset_data_gyro_rate_z = new double[size]; 554 // Set accumulate count to 0 555 m_accum_count = 0; 556 } 557 } 558 559 /** 560 * Calibrate the gyro. It's important to make sure that the robot is not moving while the 561 * calibration is in progress, this is typically done when the robot is first turned on while it's 562 * sitting at rest before the match starts. 563 */ 564 public void calibrate() { 565 synchronized (this) { 566 int gyroAverageSize = Math.min(m_accum_count, m_avg_size); 567 double accum_gyro_rate_x = 0.0; 568 double accum_gyro_rate_y = 0.0; 569 double accum_gyro_rate_z = 0.0; 570 for (int i = 0; i < gyroAverageSize; i++) { 571 accum_gyro_rate_x += m_offset_data_gyro_rate_x[i]; 572 accum_gyro_rate_y += m_offset_data_gyro_rate_y[i]; 573 accum_gyro_rate_z += m_offset_data_gyro_rate_z[i]; 574 } 575 m_gyro_rate_offset_x = accum_gyro_rate_x / gyroAverageSize; 576 m_gyro_rate_offset_y = accum_gyro_rate_y / gyroAverageSize; 577 m_gyro_rate_offset_z = accum_gyro_rate_z / gyroAverageSize; 578 m_integ_gyro_angle_x = 0.0; 579 m_integ_gyro_angle_y = 0.0; 580 m_integ_gyro_angle_z = 0.0; 581 // System.out.println("Avg Size: " + gyroAverageSize + "X Off: " + 582 // m_gyro_rate_offset_x + "Y Off: " + m_gyro_rate_offset_y + "Z Off: " + 583 // m_gyro_rate_offset_z); 584 } 585 } 586 587 /** 588 * Sets the yaw axis 589 * 590 * @param yaw_axis The new yaw axis to use 591 * @return 1 if the new yaw axis is the same as the current one else 0. 592 */ 593 public int setYawAxis(IMUAxis yaw_axis) { 594 if (m_yaw_axis == yaw_axis) { 595 return 1; 596 } 597 m_yaw_axis = yaw_axis; 598 reset(); 599 return 0; 600 } 601 602 private int readRegister(final int reg) { 603 // ByteBuffer buf = ByteBuffer.allocateDirect(2); 604 final byte[] buf = new byte[2]; 605 // buf.order(ByteOrder.BIG_ENDIAN); 606 buf[0] = (byte) (reg & 0x7f); 607 buf[1] = (byte) 0; 608 609 m_spi.write(buf, 2); 610 m_spi.read(false, buf, 2); 611 612 return toUShort(buf); 613 } 614 615 private void writeRegister(final int reg, final int val) { 616 final byte[] buf = new byte[2]; 617 // low byte 618 buf[0] = (byte) (0x80 | reg); 619 buf[1] = (byte) (val & 0xff); 620 m_spi.write(buf, 2); 621 // high byte 622 buf[0] = (byte) (0x81 | reg); 623 buf[1] = (byte) (val >> 8); 624 m_spi.write(buf, 2); 625 } 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 /** */ 668 private void acquire() { 669 // Set data packet length 670 final int dataset_len = 29; // 18 data points + timestamp 671 final int BUFFER_SIZE = 4000; 672 673 // Set up buffers and variables 674 int[] buffer = new int[BUFFER_SIZE]; 675 int data_count = 0; 676 int data_remainder = 0; 677 int data_to_read = 0; 678 int bufferAvgIndex = 0; 679 double previous_timestamp = 0.0; 680 double delta_angle = 0.0; 681 double gyro_rate_x = 0.0; 682 double gyro_rate_y = 0.0; 683 double gyro_rate_z = 0.0; 684 double accel_x = 0.0; 685 double accel_y = 0.0; 686 double accel_z = 0.0; 687 double mag_x = 0.0; 688 double mag_y = 0.0; 689 double mag_z = 0.0; 690 double baro = 0.0; 691 double temp = 0.0; 692 double gyro_rate_x_si = 0.0; 693 double gyro_rate_y_si = 0.0; 694 double gyro_rate_z_si = 0.0; 695 double accel_x_si = 0.0; 696 double accel_y_si = 0.0; 697 double accel_z_si = 0.0; 698 double compAngleX = 0.0; 699 double compAngleY = 0.0; 700 double accelAngleX = 0.0; 701 double accelAngleY = 0.0; 702 703 while (true) { 704 // Sleep loop for 5ms 705 try { 706 Thread.sleep(5); 707 } catch (InterruptedException e) { 708 } 709 710 if (m_thread_active) { 711 m_thread_idle = false; 712 713 data_count = 714 m_spi.readAutoReceivedData( 715 buffer, 0, 0); // Read number of bytes currently stored in the buffer 716 data_remainder = 717 data_count % dataset_len; // Check if frame is incomplete. Add 1 because of timestamp 718 data_to_read = data_count - data_remainder; // Remove incomplete data from read count 719 if (data_to_read > BUFFER_SIZE) { 720 DriverStation.reportWarning( 721 "ADIS16448 data processing thread overrun has occurred!", false); 722 data_to_read = BUFFER_SIZE - (BUFFER_SIZE % dataset_len); 723 } 724 m_spi.readAutoReceivedData( 725 buffer, data_to_read, 0); // Read data from DMA buffer (only complete sets) 726 727 // Could be multiple data sets in the buffer. Handle each one. 728 for (int i = 0; i < data_to_read; i += dataset_len) { 729 // Calculate CRC-16 on each data packet 730 int calc_crc = 0x0000FFFF; // Starting word 731 int read_byte = 0; 732 int imu_crc = 0; 733 for (int k = 5; 734 k < 27; 735 k += 2) { // Cycle through XYZ GYRO, XYZ ACCEL, XYZ MAG, BARO, TEMP (Ignore Status & 736 // CRC) 737 read_byte = buffer[i + k + 1]; // Process LSB 738 calc_crc = (calc_crc >>> 8) ^ adiscrc[(calc_crc & 0x000000FF) ^ read_byte]; 739 read_byte = buffer[i + k]; // Process MSB 740 calc_crc = (calc_crc >>> 8) ^ adiscrc[(calc_crc & 0x000000FF) ^ read_byte]; 741 } 742 calc_crc = ~calc_crc & 0xFFFF; // Complement 743 calc_crc = ((calc_crc << 8) | (calc_crc >> 8)) & 0xFFFF; // Flip LSB & MSB 744 imu_crc = toUShort(buffer[i + 27], buffer[i + 28]); // Extract DUT CRC from data buffer 745 746 if (calc_crc == imu_crc) { 747 // Timestamp is at buffer[i] 748 m_dt = ((double) buffer[i] - previous_timestamp) / 1000000.0; 749 750 // Scale sensor data 751 gyro_rate_x = (toShort(buffer[i + 5], buffer[i + 6]) * 0.04); 752 gyro_rate_y = (toShort(buffer[i + 7], buffer[i + 8]) * 0.04); 753 gyro_rate_z = (toShort(buffer[i + 9], buffer[i + 10]) * 0.04); 754 accel_x = (toShort(buffer[i + 11], buffer[i + 12]) * 0.833); 755 accel_y = (toShort(buffer[i + 13], buffer[i + 14]) * 0.833); 756 accel_z = (toShort(buffer[i + 15], buffer[i + 16]) * 0.833); 757 mag_x = (toShort(buffer[i + 17], buffer[i + 18]) * 0.1429); 758 mag_y = (toShort(buffer[i + 19], buffer[i + 20]) * 0.1429); 759 mag_z = (toShort(buffer[i + 21], buffer[i + 22]) * 0.1429); 760 baro = (toShort(buffer[i + 23], buffer[i + 24]) * 0.02); 761 temp = (toShort(buffer[i + 25], buffer[i + 26]) * 0.07386 + 31.0); 762 763 // Convert scaled sensor data to SI units (for tilt calculations) 764 // TODO: Should the unit outputs be selectable? 765 gyro_rate_x_si = gyro_rate_x * deg_to_rad; 766 gyro_rate_y_si = gyro_rate_y * deg_to_rad; 767 gyro_rate_z_si = gyro_rate_z * deg_to_rad; 768 accel_x_si = accel_x * grav; 769 accel_y_si = accel_y * grav; 770 accel_z_si = accel_z * grav; 771 // Store timestamp for next iteration 772 previous_timestamp = buffer[i]; 773 // Calculate alpha for use with the complementary filter 774 m_alpha = m_tau / (m_tau + m_dt); 775 // Calculate complementary filter 776 if (m_first_run) { 777 // Set up inclinometer calculations for first run 778 accelAngleX = 779 Math.atan2( 780 -accel_x_si, 781 Math.sqrt((accel_y_si * accel_y_si) + (-accel_z_si * -accel_z_si))); 782 accelAngleY = 783 Math.atan2( 784 accel_y_si, 785 Math.sqrt((-accel_x_si * -accel_x_si) + (-accel_z_si * -accel_z_si))); 786 compAngleX = accelAngleX; 787 compAngleY = accelAngleY; 788 } else { 789 // Run inclinometer calculations 790 accelAngleX = 791 Math.atan2( 792 -accel_x_si, 793 Math.sqrt((accel_y_si * accel_y_si) + (-accel_z_si * -accel_z_si))); 794 accelAngleY = 795 Math.atan2( 796 accel_y_si, 797 Math.sqrt((-accel_x_si * -accel_x_si) + (-accel_z_si * -accel_z_si))); 798 accelAngleX = formatAccelRange(accelAngleX, -accel_z_si); 799 accelAngleY = formatAccelRange(accelAngleY, -accel_z_si); 800 compAngleX = compFilterProcess(compAngleX, accelAngleX, -gyro_rate_y_si); 801 compAngleY = compFilterProcess(compAngleY, accelAngleY, -gyro_rate_x_si); 802 } 803 804 // Update global variables and state 805 synchronized (this) { 806 // Ignore first, integrated sample 807 if (m_first_run) { 808 m_integ_gyro_angle_x = 0.0; 809 m_integ_gyro_angle_y = 0.0; 810 m_integ_gyro_angle_z = 0.0; 811 } else { 812 // Accumulate gyro for offset calibration 813 // Add to buffer 814 bufferAvgIndex = m_accum_count % m_avg_size; 815 m_offset_data_gyro_rate_x[bufferAvgIndex] = gyro_rate_x; 816 m_offset_data_gyro_rate_y[bufferAvgIndex] = gyro_rate_y; 817 m_offset_data_gyro_rate_z[bufferAvgIndex] = gyro_rate_z; 818 // Increment counter 819 m_accum_count++; 820 } 821 if (!m_start_up_mode) { 822 m_gyro_rate_x = gyro_rate_x; 823 m_gyro_rate_y = gyro_rate_y; 824 m_gyro_rate_z = gyro_rate_z; 825 m_accel_x = accel_x; 826 m_accel_y = accel_y; 827 m_accel_z = accel_z; 828 m_mag_x = mag_x; 829 m_mag_y = mag_y; 830 m_mag_z = mag_z; 831 m_baro = baro; 832 m_temp = temp; 833 m_compAngleX = compAngleX * rad_to_deg; 834 m_compAngleY = compAngleY * rad_to_deg; 835 m_accelAngleX = accelAngleX * rad_to_deg; 836 m_accelAngleY = accelAngleY * rad_to_deg; 837 // Accumulate gyro for angle integration and publish to global variables 838 m_integ_gyro_angle_x += (gyro_rate_x - m_gyro_rate_offset_x) * m_dt; 839 m_integ_gyro_angle_y += (gyro_rate_y - m_gyro_rate_offset_y) * m_dt; 840 m_integ_gyro_angle_z += (gyro_rate_z - m_gyro_rate_offset_z) * m_dt; 841 } 842 // System.out.println("Good CRC"); 843 } 844 m_first_run = false; 845 } else { 846 // System.out.println("Bad CRC"); 847 /* 848 * System.out.println("Calc CRC: " + calc_crc); 849 * System.out.println("IMU CRC: " + imu_crc); 850 * System.out.println( 851 * buffer[i] + " " + 852 * (buffer[i + 1]) + " " + (buffer[i + 2]) + " " + 853 * (buffer[i + 3]) + " " + (buffer[i + 4]) + " " + 854 * (buffer[i + 5]) + " " + (buffer[i + 6]) + " " + 855 * (buffer[i + 7]) + " " + (buffer[i + 8]) + " " + 856 * (buffer[i + 9]) + " " + (buffer[i + 10]) + " " + 857 * (buffer[i + 11]) + " " + (buffer[i + 12]) + " " + 858 * (buffer[i + 13]) + " " + (buffer[i + 14]) + " " + 859 * (buffer[i + 15]) + " " + (buffer[i + 16]) + " " + 860 * (buffer[i + 17]) + " " + (buffer[i + 18]) + " " + 861 * (buffer[i + 19]) + " " + (buffer[i + 20]) + " " + 862 * (buffer[i + 21]) + " " + (buffer[i + 22]) + " " + 863 * (buffer[i + 23]) + " " + (buffer[i + 24]) + " " + 864 * (buffer[i + 25]) + " " + (buffer[i + 26]) + " " + 865 * (buffer[i + 27]) + " " + (buffer[i + 28])); 866 */ 867 } 868 } 869 } else { 870 m_thread_idle = true; 871 data_count = 0; 872 data_remainder = 0; 873 data_to_read = 0; 874 previous_timestamp = 0.0; 875 delta_angle = 0.0; 876 gyro_rate_x = 0.0; 877 gyro_rate_y = 0.0; 878 gyro_rate_z = 0.0; 879 accel_x = 0.0; 880 accel_y = 0.0; 881 accel_z = 0.0; 882 mag_x = 0.0; 883 mag_y = 0.0; 884 mag_z = 0.0; 885 baro = 0.0; 886 temp = 0.0; 887 gyro_rate_x_si = 0.0; 888 gyro_rate_y_si = 0.0; 889 gyro_rate_z_si = 0.0; 890 accel_x_si = 0.0; 891 accel_y_si = 0.0; 892 accel_z_si = 0.0; 893 compAngleX = 0.0; 894 compAngleY = 0.0; 895 accelAngleX = 0.0; 896 accelAngleY = 0.0; 897 } 898 } 899 } 900 901 /** 902 * @param compAngle 903 * @param accAngle 904 * @return 905 */ 906 private double formatFastConverge(double compAngle, double accAngle) { 907 if (compAngle > accAngle + Math.PI) { 908 compAngle = compAngle - 2.0 * Math.PI; 909 } else if (accAngle > compAngle + Math.PI) { 910 compAngle = compAngle + 2.0 * Math.PI; 911 } 912 return compAngle; 913 } 914 915 /** 916 * @param compAngle 917 * @return 918 */ 919 private double formatRange0to2PI(double compAngle) { 920 while (compAngle >= 2 * Math.PI) { 921 compAngle = compAngle - 2.0 * Math.PI; 922 } 923 while (compAngle < 0.0) { 924 compAngle = compAngle + 2.0 * Math.PI; 925 } 926 return compAngle; 927 } 928 929 /** 930 * @param accelAngle 931 * @param accelZ 932 * @return 933 */ 934 private double formatAccelRange(double accelAngle, double accelZ) { 935 if (accelZ < 0.0) { 936 accelAngle = Math.PI - accelAngle; 937 } else if (accelZ > 0.0 && accelAngle < 0.0) { 938 accelAngle = 2.0 * Math.PI + accelAngle; 939 } 940 return accelAngle; 941 } 942 943 /** 944 * @param compAngle 945 * @param accelAngle 946 * @param omega 947 * @return 948 */ 949 private double compFilterProcess(double compAngle, double accelAngle, double omega) { 950 compAngle = formatFastConverge(compAngle, accelAngle); 951 compAngle = m_alpha * (compAngle + omega * m_dt) + (1.0 - m_alpha) * accelAngle; 952 compAngle = formatRange0to2PI(compAngle); 953 if (compAngle > Math.PI) { 954 compAngle = compAngle - 2.0 * Math.PI; 955 } 956 return compAngle; 957 } 958 959 /** 960 * @return Yaw axis angle in degrees (CCW positive) 961 */ 962 public synchronized double getAngle() { 963 switch (m_yaw_axis) { 964 case kX: 965 return getGyroAngleX(); 966 case kY: 967 return getGyroAngleY(); 968 case kZ: 969 return getGyroAngleZ(); 970 default: 971 return 0.0; 972 } 973 } 974 975 /** 976 * @return Yaw axis angular rate in degrees per second (CCW positive) 977 */ 978 public synchronized double getRate() { 979 switch (m_yaw_axis) { 980 case kX: 981 return getGyroRateX(); 982 case kY: 983 return getGyroRateY(); 984 case kZ: 985 return getGyroRateZ(); 986 default: 987 return 0.0; 988 } 989 } 990 991 /** 992 * @return Yaw Axis 993 */ 994 public IMUAxis getYawAxis() { 995 return m_yaw_axis; 996 } 997 998 /** 999 * @return accumulated gyro angle in the X axis in degrees 1000 */ 1001 public synchronized double getGyroAngleX() { 1002 if (m_simGyroAngleX != null) { 1003 return m_simGyroAngleX.get(); 1004 } 1005 return m_integ_gyro_angle_x; 1006 } 1007 1008 /** 1009 * @return accumulated gyro angle in the Y axis in degrees 1010 */ 1011 public synchronized double getGyroAngleY() { 1012 if (m_simGyroAngleY != null) { 1013 return m_simGyroAngleY.get(); 1014 } 1015 return m_integ_gyro_angle_y; 1016 } 1017 1018 /** 1019 * @return accumulated gyro angle in the Z axis in degrees 1020 */ 1021 public synchronized double getGyroAngleZ() { 1022 if (m_simGyroAngleZ != null) { 1023 return m_simGyroAngleZ.get(); 1024 } 1025 return m_integ_gyro_angle_z; 1026 } 1027 1028 /** 1029 * @return gyro angular rate in the X axis in degrees per second 1030 */ 1031 public synchronized double getGyroRateX() { 1032 if (m_simGyroRateX != null) { 1033 return m_simGyroRateX.get(); 1034 } 1035 return m_gyro_rate_x; 1036 } 1037 1038 /** 1039 * @return gyro angular rate in the Y axis in degrees per second 1040 */ 1041 public synchronized double getGyroRateY() { 1042 if (m_simGyroRateY != null) { 1043 return m_simGyroRateY.get(); 1044 } 1045 return m_gyro_rate_y; 1046 } 1047 1048 /** 1049 * @return gyro angular rate in the Z axis in degrees per second 1050 */ 1051 public synchronized double getGyroRateZ() { 1052 if (m_simGyroRateZ != null) { 1053 return m_simGyroRateZ.get(); 1054 } 1055 return m_gyro_rate_z; 1056 } 1057 1058 /** 1059 * @return urrent acceleration in the X axis in meters per second squared 1060 */ 1061 public synchronized double getAccelX() { 1062 if (m_simAccelX != null) { 1063 return m_simAccelX.get(); 1064 } 1065 return m_accel_x * 9.81; 1066 } 1067 1068 /** 1069 * @return current acceleration in the Y axis in meters per second squared 1070 */ 1071 public synchronized double getAccelY() { 1072 if (m_simAccelY != null) { 1073 return m_simAccelY.get(); 1074 } 1075 return m_accel_y * 9.81; 1076 } 1077 1078 /** 1079 * @return current acceleration in the Z axis in meters per second squared 1080 */ 1081 public synchronized double getAccelZ() { 1082 if (m_simAccelZ != null) { 1083 return m_simAccelZ.get(); 1084 } 1085 return m_accel_z * 9.81; 1086 } 1087 1088 /** 1089 * @return Magnetic field strength in the X axis in Tesla 1090 */ 1091 public synchronized double getMagneticFieldX() { 1092 // mG to T 1093 return m_mag_x * 1e-7; 1094 } 1095 1096 /** 1097 * @return Magnetic field strength in the Y axis in Tesla 1098 */ 1099 public synchronized double getMagneticFieldY() { 1100 // mG to T 1101 return m_mag_y * 1e-7; 1102 } 1103 1104 /** 1105 * @return Magnetic field strength in the Z axis in Tesla 1106 */ 1107 public synchronized double getMagneticFieldZ() { 1108 // mG to T 1109 return m_mag_z * 1e-7; 1110 } 1111 1112 /** 1113 * @return X-axis complementary angle in degrees 1114 */ 1115 public synchronized double getXComplementaryAngle() { 1116 return m_compAngleX; 1117 } 1118 1119 /** 1120 * @return Y-axis complementary angle in degrees 1121 */ 1122 public synchronized double getYComplementaryAngle() { 1123 return m_compAngleY; 1124 } 1125 1126 /** 1127 * @return X-axis filtered acceleration angle in degrees 1128 */ 1129 public synchronized double getXFilteredAccelAngle() { 1130 return m_accelAngleX; 1131 } 1132 1133 /** 1134 * @return Y-axis filtered acceleration angle in degrees 1135 */ 1136 public synchronized double getYFilteredAccelAngle() { 1137 return m_accelAngleY; 1138 } 1139 1140 /** 1141 * @return Barometric Pressure in PSI 1142 */ 1143 public synchronized double getBarometricPressure() { 1144 // mbar to PSI conversion 1145 return m_baro * 0.0145; 1146 } 1147 1148 /** 1149 * @return Temperature in degrees Celsius 1150 */ 1151 public synchronized double getTemperature() { 1152 return m_temp; 1153 } 1154 1155 /** 1156 * Get the SPI port number. 1157 * 1158 * @return The SPI port number. 1159 */ 1160 public int getPort() { 1161 return m_spi_port.value; 1162 } 1163 1164 @Override 1165 public void initSendable(SendableBuilder builder) { 1166 builder.setSmartDashboardType("Gyro"); 1167 builder.addDoubleProperty("Value", this::getAngle, null); 1168 } 1169}