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