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 int prodId = readRegister(PROD_ID); 487 if (prodId != 16982 && prodId != 16470) { 488 DriverStation.reportError("Could not find an ADIS16470; got product ID " + prodId, false); 489 close(); 490 return false; 491 } 492 return true; 493 } 494 495 /** 496 * Switch to auto SPI mode. 497 * 498 * @return True if successful, false otherwise. 499 */ 500 boolean switchToAutoSPI() { 501 // No SPI port has been set up. Go set one up first. 502 if (m_spi == null && !switchToStandardSPI()) { 503 DriverStation.reportError("Failed to start/restart auto SPI", false); 504 return false; 505 } 506 // Only set up the interrupt if needed. 507 if (m_auto_interrupt == null) { 508 // Configure interrupt on SPI CS1 509 m_auto_interrupt = new DigitalInput(26); 510 } 511 // The auto SPI controller gets angry if you try to set up two instances on one 512 // bus. 513 if (!m_auto_configured) { 514 m_spi.initAuto(8200); 515 m_auto_configured = true; 516 } 517 // Do we need to change auto SPI settings? 518 m_spi.setAutoTransmitData(m_autospi_allAngle_packet, 2); 519 // Configure auto stall time 520 m_spi.configureAutoStall(5, 1000, 1); 521 // Kick off auto SPI (Note: Device configuration impossible after auto SPI is 522 // activated) 523 // DR High = Data good (data capture should be triggered on the rising edge) 524 m_spi.startAutoTrigger(m_auto_interrupt, true, false); 525 // Check to see if the acquire thread is running. If not, kick one off. 526 if (!m_acquire_task.isAlive()) { 527 m_first_run = true; 528 m_thread_active = true; 529 m_acquire_task.start(); 530 System.out.println("Processing thread activated!"); 531 } else { 532 // The thread was running, re-init run variables and start it up again. 533 m_first_run = true; 534 m_thread_active = true; 535 System.out.println("Processing thread activated!"); 536 } 537 // Looks like the thread didn't start for some reason. Abort. 538 if (!m_acquire_task.isAlive()) { 539 DriverStation.reportError("Failed to start/restart the acquire() thread.", false); 540 close(); 541 return false; 542 } 543 return true; 544 } 545 546 /** 547 * Configures calibration time. 548 * 549 * @param new_cal_time New calibration time 550 * @return 0 if success, 1 if no change, 2 if error. 551 */ 552 public int configCalTime(CalibrationTime new_cal_time) { 553 if (m_calibration_time == new_cal_time.value) { 554 return 1; 555 } 556 if (!switchToStandardSPI()) { 557 DriverStation.reportError("Failed to configure/reconfigure standard SPI.", false); 558 return 2; 559 } 560 m_calibration_time = new_cal_time.value; 561 writeRegister(NULL_CNFG, m_calibration_time | 0x700); 562 if (!switchToAutoSPI()) { 563 DriverStation.reportError("Failed to configure/reconfigure auto SPI.", false); 564 return 2; 565 } 566 return 0; 567 } 568 569 /** 570 * Configures the decimation rate of the IMU. 571 * 572 * @param decimationRate The new decimation value. 573 * @return 0 if success, 1 if no change, 2 if error. 574 */ 575 public int configDecRate(int decimationRate) { 576 // Switches the active SPI port to standard SPI mode, writes a new value to the DECIMATE 577 // register in the IMU, adjusts the sample scale factor, and re-enables auto SPI. 578 if (!switchToStandardSPI()) { 579 DriverStation.reportError("Failed to configure/reconfigure standard SPI.", false); 580 return 2; 581 } 582 if (decimationRate > 1999) { 583 DriverStation.reportError("Attempted to write an invalid decimation value.", false); 584 decimationRate = 1999; 585 } 586 m_scaled_sample_rate = (decimationRate + 1.0) / 2000.0 * 1000000.0; 587 writeRegister(DEC_RATE, decimationRate); 588 System.out.println("Decimation register: " + readRegister(DEC_RATE)); 589 if (!switchToAutoSPI()) { 590 DriverStation.reportError("Failed to configure/reconfigure auto SPI.", false); 591 return 2; 592 } 593 return 0; 594 } 595 596 /** 597 * Calibrate the gyro. It's important to make sure that the robot is not moving while the 598 * calibration is in progress, this is typically done when the robot is first turned on while it's 599 * sitting at rest before the match starts. 600 */ 601 public void calibrate() { 602 if (!switchToStandardSPI()) { 603 DriverStation.reportError("Failed to configure/reconfigure standard SPI.", false); 604 } 605 writeRegister(GLOB_CMD, 0x0001); 606 if (!switchToAutoSPI()) { 607 DriverStation.reportError("Failed to configure/reconfigure auto SPI.", false); 608 } 609 } 610 611 private int readRegister(int reg) { 612 final byte[] buf = {(byte) (reg & 0x7f), 0}; 613 614 m_spi.write(buf, 2); 615 m_spi.read(false, buf, 2); 616 617 return toUShort(buf[0], buf[1]); 618 } 619 620 private void writeRegister(int reg, int val) { 621 // low byte 622 final byte[] buf = {(byte) (0x80 | reg), (byte) (val & 0xff)}; 623 m_spi.write(buf, 2); 624 // high byte 625 buf[0] = (byte) (0x81 | reg); 626 buf[1] = (byte) (val >> 8); 627 m_spi.write(buf, 2); 628 } 629 630 /** Delete (free) the spi port used for the IMU. */ 631 @Override 632 public void close() { 633 if (m_thread_active) { 634 m_thread_active = false; 635 try { 636 if (m_acquire_task != null) { 637 m_acquire_task.join(); 638 m_acquire_task = null; 639 } 640 } catch (InterruptedException e) { 641 } 642 if (m_spi != null) { 643 if (m_auto_configured) { 644 m_spi.stopAuto(); 645 } 646 m_spi.close(); 647 m_auto_configured = false; 648 if (m_auto_interrupt != null) { 649 m_auto_interrupt.close(); 650 m_auto_interrupt = null; 651 } 652 m_spi = null; 653 } 654 } 655 if (m_simDevice != null) { 656 m_simDevice.close(); 657 m_simDevice = null; 658 } 659 System.out.println("Finished cleaning up after the IMU driver."); 660 } 661 662 private void acquire() { 663 // Set data packet length 664 final int dataset_len = 27; // 26 data points + timestamp 665 final int BUFFER_SIZE = 4000; 666 667 // Set up buffers and variables 668 int[] buffer = new int[BUFFER_SIZE]; 669 double previous_timestamp = 0.0; 670 double compAngleX = 0.0; 671 double compAngleY = 0.0; 672 while (true) { 673 // Wait for data 674 try { 675 Thread.sleep(10); 676 } catch (InterruptedException e) { 677 } 678 679 if (m_thread_active) { 680 m_thread_idle = false; 681 682 // Read number of bytes currently stored in the buffer 683 int data_count = m_spi.readAutoReceivedData(buffer, 0, 0); 684 // Check if frame is incomplete 685 int data_remainder = data_count % dataset_len; 686 // Remove incomplete data from read count 687 int data_to_read = data_count - data_remainder; 688 // Want to cap the data to read in a single read at the buffer size 689 if (data_to_read > BUFFER_SIZE) { 690 DriverStation.reportWarning( 691 "ADIS16470 data processing thread overrun has occurred!", false); 692 data_to_read = BUFFER_SIZE - (BUFFER_SIZE % dataset_len); 693 } 694 // Read data from DMA buffer (only complete sets) 695 m_spi.readAutoReceivedData(buffer, data_to_read, 0); 696 697 // Could be multiple data sets in the buffer. Handle each one. 698 for (int i = 0; i < data_to_read; i += dataset_len) { 699 // Timestamp is at buffer[i] 700 m_dt = (buffer[i] - previous_timestamp) / 1000000.0; 701 // Get delta angle value for all 3 axes and scale by the elapsed time 702 // (based on timestamp) 703 double elapsed_time = m_scaled_sample_rate / (buffer[i] - previous_timestamp); 704 double delta_angle_x = 705 toInt(buffer[i + 3], buffer[i + 4], buffer[i + 5], buffer[i + 6]) 706 * kDeltaAngleSf 707 / elapsed_time; 708 double delta_angle_y = 709 toInt(buffer[i + 7], buffer[i + 8], buffer[i + 9], buffer[i + 10]) 710 * kDeltaAngleSf 711 / elapsed_time; 712 double delta_angle_z = 713 toInt(buffer[i + 11], buffer[i + 12], buffer[i + 13], buffer[i + 14]) 714 * kDeltaAngleSf 715 / elapsed_time; 716 717 double gyro_rate_x = toShort(buffer[i + 15], buffer[i + 16]) / 10.0; 718 double gyro_rate_y = toShort(buffer[i + 17], buffer[i + 18]) / 10.0; 719 double gyro_rate_z = toShort(buffer[i + 19], buffer[i + 20]) / 10.0; 720 721 double accel_x = toShort(buffer[i + 21], buffer[i + 22]) / 800.0; 722 double accel_y = toShort(buffer[i + 23], buffer[i + 24]) / 800.0; 723 double accel_z = toShort(buffer[i + 25], buffer[i + 26]) / 800.0; 724 725 // Convert scaled sensor data to SI units (for tilt calculations) 726 // TODO: Should the unit outputs be selectable? 727 double gyro_rate_x_si = Math.toRadians(gyro_rate_x); 728 double gyro_rate_y_si = Math.toRadians(gyro_rate_y); 729 // double gyro_rate_z_si = Math.toRadians(gyro_rate_z); 730 double accel_x_si = accel_x * kGrav; 731 double accel_y_si = accel_y * kGrav; 732 double accel_z_si = accel_z * kGrav; 733 734 // Store timestamp for next iteration 735 previous_timestamp = buffer[i]; 736 737 m_alpha = kTau / (kTau + m_dt); 738 739 // Run inclinometer calculations 740 double accelAngleX = Math.atan2(accel_x_si, Math.hypot(accel_y_si, accel_z_si)); 741 double accelAngleY = Math.atan2(accel_y_si, Math.hypot(accel_x_si, accel_z_si)); 742 if (m_first_run) { 743 compAngleX = accelAngleX; 744 compAngleY = accelAngleY; 745 } else { 746 accelAngleX = formatAccelRange(accelAngleX, accel_z_si); 747 accelAngleY = formatAccelRange(accelAngleY, accel_z_si); 748 compAngleX = compFilterProcess(compAngleX, accelAngleX, -gyro_rate_y_si); 749 compAngleY = compFilterProcess(compAngleY, accelAngleY, gyro_rate_x_si); 750 } 751 752 synchronized (this) { 753 // Push data to global variables 754 if (m_first_run) { 755 // Don't accumulate first run. previous_timestamp will be "very" old and the 756 // integration will end up way off 757 reset(); 758 } else { 759 m_integ_angle_x += delta_angle_x; 760 m_integ_angle_y += delta_angle_y; 761 m_integ_angle_z += delta_angle_z; 762 } 763 m_gyro_rate_x = gyro_rate_x; 764 m_gyro_rate_y = gyro_rate_y; 765 m_gyro_rate_z = gyro_rate_z; 766 m_accel_x = accel_x; 767 m_accel_y = accel_y; 768 m_accel_z = accel_z; 769 m_compAngleX = Math.toDegrees(compAngleX); 770 m_compAngleY = Math.toDegrees(compAngleY); 771 m_accelAngleX = Math.toDegrees(accelAngleX); 772 m_accelAngleY = Math.toDegrees(accelAngleY); 773 } 774 m_first_run = false; 775 } 776 } else { 777 m_thread_idle = true; 778 previous_timestamp = 0.0; 779 compAngleX = 0.0; 780 compAngleY = 0.0; 781 } 782 } 783 } 784 785 private double formatFastConverge(double compAngle, double accAngle) { 786 if (compAngle > accAngle + Math.PI) { 787 compAngle = compAngle - 2.0 * Math.PI; 788 } else if (accAngle > compAngle + Math.PI) { 789 compAngle = compAngle + 2.0 * Math.PI; 790 } 791 return compAngle; 792 } 793 794 private double formatAccelRange(double accelAngle, double accelZ) { 795 if (accelZ < 0.0) { 796 accelAngle = Math.PI - accelAngle; 797 } else if (accelZ > 0.0 && accelAngle < 0.0) { 798 accelAngle = 2.0 * Math.PI + accelAngle; 799 } 800 return accelAngle; 801 } 802 803 private double compFilterProcess(double compAngle, double accelAngle, double omega) { 804 compAngle = formatFastConverge(compAngle, accelAngle); 805 compAngle = m_alpha * (compAngle + omega * m_dt) + (1.0 - m_alpha) * accelAngle; 806 return MathUtil.angleModulus(compAngle); 807 } 808 809 /** 810 * Reset the gyro. 811 * 812 * <p>Resets the gyro accumulations to a heading of zero. This can be used if there is significant 813 * drift in the gyro and it needs to be recalibrated after running. 814 */ 815 public void reset() { 816 synchronized (this) { 817 m_integ_angle_x = 0.0; 818 m_integ_angle_y = 0.0; 819 m_integ_angle_z = 0.0; 820 } 821 } 822 823 /** 824 * Allow the designated gyro angle to be set to a given value. This may happen with unread values 825 * in the buffer, it is suggested that the IMU is not moving when this method is run. 826 * 827 * @param axis IMUAxis that will be changed 828 * @param angle A double in degrees (CCW positive) 829 */ 830 public void setGyroAngle(IMUAxis axis, double angle) { 831 axis = 832 switch (axis) { 833 case kYaw -> m_yaw_axis; 834 case kPitch -> m_pitch_axis; 835 case kRoll -> m_roll_axis; 836 default -> axis; 837 }; 838 839 switch (axis) { 840 case kX -> setGyroAngleX(angle); 841 case kY -> setGyroAngleY(angle); 842 case kZ -> setGyroAngleZ(angle); 843 default -> { 844 // NOP 845 } 846 } 847 } 848 849 /** 850 * Allow the gyro angle X to be set to a given value. This may happen with unread values in the 851 * buffer, it is suggested that the IMU is not moving when this method is run. 852 * 853 * @param angle A double in degrees (CCW positive) 854 */ 855 public void setGyroAngleX(double angle) { 856 synchronized (this) { 857 m_integ_angle_x = angle; 858 } 859 } 860 861 /** 862 * Allow the gyro angle Y to be set to a given value. This may happen with unread values in the 863 * buffer, it is suggested that the IMU is not moving when this method is run. 864 * 865 * @param angle A double in degrees (CCW positive) 866 */ 867 public void setGyroAngleY(double angle) { 868 synchronized (this) { 869 m_integ_angle_y = angle; 870 } 871 } 872 873 /** 874 * Allow the gyro angle Z to be set to a given value. This may happen with unread values in the 875 * buffer, it is suggested that the IMU is not moving when this method is run. 876 * 877 * @param angle A double in degrees (CCW positive) 878 */ 879 public void setGyroAngleZ(double angle) { 880 synchronized (this) { 881 m_integ_angle_z = angle; 882 } 883 } 884 885 /** 886 * Returns the axis angle in degrees (CCW positive). 887 * 888 * @param axis The IMUAxis whose angle to return. 889 * @return The axis angle in degrees (CCW positive). 890 */ 891 public synchronized double getAngle(IMUAxis axis) { 892 axis = 893 switch (axis) { 894 case kYaw -> m_yaw_axis; 895 case kPitch -> m_pitch_axis; 896 case kRoll -> m_roll_axis; 897 default -> axis; 898 }; 899 900 return switch (axis) { 901 case kX -> { 902 if (m_simGyroAngleX != null) { 903 yield m_simGyroAngleX.get(); 904 } 905 yield m_integ_angle_x; 906 } 907 case kY -> { 908 if (m_simGyroAngleY != null) { 909 yield m_simGyroAngleY.get(); 910 } 911 yield m_integ_angle_y; 912 } 913 case kZ -> { 914 if (m_simGyroAngleZ != null) { 915 yield m_simGyroAngleZ.get(); 916 } 917 yield m_integ_angle_z; 918 } 919 default -> 0.0; 920 }; 921 } 922 923 /** 924 * Returns the Yaw axis angle in degrees (CCW positive). 925 * 926 * @return The Yaw axis angle in degrees (CCW positive). 927 */ 928 public synchronized double getAngle() { 929 return getAngle(m_yaw_axis); 930 } 931 932 /** 933 * Returns the axis angular rate in degrees per second (CCW positive). 934 * 935 * @param axis The IMUAxis whose rate to return. 936 * @return Axis angular rate in degrees per second (CCW positive). 937 */ 938 public synchronized double getRate(IMUAxis axis) { 939 axis = 940 switch (axis) { 941 case kYaw -> m_yaw_axis; 942 case kPitch -> m_pitch_axis; 943 case kRoll -> m_roll_axis; 944 default -> axis; 945 }; 946 947 return switch (axis) { 948 case kX -> { 949 if (m_simGyroRateX != null) { 950 yield m_simGyroRateX.get(); 951 } 952 yield m_gyro_rate_x; 953 } 954 case kY -> { 955 if (m_simGyroRateY != null) { 956 yield m_simGyroRateY.get(); 957 } 958 yield m_gyro_rate_y; 959 } 960 case kZ -> { 961 if (m_simGyroRateZ != null) { 962 yield m_simGyroRateZ.get(); 963 } 964 yield m_gyro_rate_z; 965 } 966 default -> 0.0; 967 }; 968 } 969 970 /** 971 * Returns the Yaw axis angular rate in degrees per second (CCW positive). 972 * 973 * @return Yaw axis angular rate in degrees per second (CCW positive). 974 */ 975 public synchronized double getRate() { 976 return getRate(m_yaw_axis); 977 } 978 979 /** 980 * Returns which axis, kX, kY, or kZ, is set to the yaw axis. 981 * 982 * @return IMUAxis Yaw Axis 983 */ 984 public IMUAxis getYawAxis() { 985 return m_yaw_axis; 986 } 987 988 /** 989 * Returns which axis, kX, kY, or kZ, is set to the pitch axis. 990 * 991 * @return IMUAxis Pitch Axis 992 */ 993 public IMUAxis getPitchAxis() { 994 return m_pitch_axis; 995 } 996 997 /** 998 * Returns which axis, kX, kY, or kZ, is set to the roll axis. 999 * 1000 * @return IMUAxis Roll Axis 1001 */ 1002 public IMUAxis getRollAxis() { 1003 return m_roll_axis; 1004 } 1005 1006 /** 1007 * Returns the acceleration in the X axis in meters per second squared. 1008 * 1009 * @return The acceleration in the X axis in meters per second squared. 1010 */ 1011 public synchronized double getAccelX() { 1012 return m_accel_x * kGrav; 1013 } 1014 1015 /** 1016 * Returns the acceleration in the Y axis in meters per second squared. 1017 * 1018 * @return The acceleration in the Y axis in meters per second squared. 1019 */ 1020 public synchronized double getAccelY() { 1021 return m_accel_y * kGrav; 1022 } 1023 1024 /** 1025 * Returns the acceleration in the Z axis in meters per second squared. 1026 * 1027 * @return The acceleration in the Z axis in meters per second squared. 1028 */ 1029 public synchronized double getAccelZ() { 1030 return m_accel_z * kGrav; 1031 } 1032 1033 /** 1034 * Returns the complementary angle around the X axis computed from accelerometer and gyro rate 1035 * measurements. 1036 * 1037 * @return The X-axis complementary angle in degrees. 1038 */ 1039 public synchronized double getXComplementaryAngle() { 1040 return m_compAngleX; 1041 } 1042 1043 /** 1044 * Returns the complementary angle around the Y axis computed from accelerometer and gyro rate 1045 * measurements. 1046 * 1047 * @return The Y-axis complementary angle in degrees. 1048 */ 1049 public synchronized double getYComplementaryAngle() { 1050 return m_compAngleY; 1051 } 1052 1053 /** 1054 * Returns the X-axis filtered acceleration angle in degrees. 1055 * 1056 * @return The X-axis filtered acceleration angle in degrees. 1057 */ 1058 public synchronized double getXFilteredAccelAngle() { 1059 return m_accelAngleX; 1060 } 1061 1062 /** 1063 * Returns the Y-axis filtered acceleration angle in degrees. 1064 * 1065 * @return The Y-axis filtered acceleration angle in degrees. 1066 */ 1067 public synchronized double getYFilteredAccelAngle() { 1068 return m_accelAngleY; 1069 } 1070 1071 /** 1072 * Gets the SPI port number. 1073 * 1074 * @return The SPI port number. 1075 */ 1076 public int getPort() { 1077 return m_spi_port.value; 1078 } 1079 1080 @Override 1081 public void initSendable(SendableBuilder builder) { 1082 builder.setSmartDashboardType("Gyro"); 1083 builder.addDoubleProperty("Value", () -> getAngle(m_yaw_axis), null); 1084 } 1085}