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.AccumulatorResult; 008import edu.wpi.first.hal.FRCNetComm.tResourceType; 009import edu.wpi.first.hal.HAL; 010import edu.wpi.first.hal.SPIJNI; 011import java.nio.ByteBuffer; 012import java.nio.ByteOrder; 013import java.nio.IntBuffer; 014 015/** Represents an SPI bus port. */ 016public class SPI implements AutoCloseable { 017 public enum Port { 018 kOnboardCS0(SPIJNI.ONBOARD_CS0_PORT), 019 kOnboardCS1(SPIJNI.ONBOARD_CS1_PORT), 020 kOnboardCS2(SPIJNI.ONBOARD_CS2_PORT), 021 kOnboardCS3(SPIJNI.ONBOARD_CS3_PORT), 022 kMXP(SPIJNI.MXP_PORT); 023 024 public final int value; 025 026 Port(int value) { 027 this.value = value; 028 } 029 } 030 031 public enum Mode { 032 /** Clock idle low, data sampled on rising edge. */ 033 kMode0(SPIJNI.SPI_MODE0), 034 /** Clock idle low, data sampled on falling edge. */ 035 kMode1(SPIJNI.SPI_MODE1), 036 /** Clock idle high, data sampled on falling edge. */ 037 kMode2(SPIJNI.SPI_MODE2), 038 /** Clock idle high, data sampled on rising edge. */ 039 kMode3(SPIJNI.SPI_MODE3); 040 041 public final int value; 042 043 Mode(int value) { 044 this.value = value; 045 } 046 } 047 048 private int m_port; 049 private int m_mode; 050 051 /** 052 * Constructor. 053 * 054 * @param port the physical SPI port 055 */ 056 public SPI(Port port) { 057 m_port = port.value; 058 059 SPIJNI.spiInitialize(m_port); 060 061 m_mode = 0; 062 SPIJNI.spiSetMode(m_port, m_mode); 063 064 HAL.report(tResourceType.kResourceType_SPI, port.value + 1); 065 } 066 067 public int getPort() { 068 return m_port; 069 } 070 071 @Override 072 public void close() { 073 if (m_accum != null) { 074 m_accum.close(); 075 m_accum = null; 076 } 077 SPIJNI.spiClose(m_port); 078 } 079 080 /** 081 * Configure the rate of the generated clock signal. The default value is 500,000 Hz. The maximum 082 * value is 4,000,000 Hz. 083 * 084 * @param hz The clock rate in Hertz. 085 */ 086 public final void setClockRate(int hz) { 087 SPIJNI.spiSetSpeed(m_port, hz); 088 } 089 090 /** 091 * Sets the mode for the SPI device. 092 * 093 * <p>Mode 0 is Clock idle low, data sampled on rising edge. 094 * 095 * <p>Mode 1 is Clock idle low, data sampled on falling edge. 096 * 097 * <p>Mode 2 is Clock idle high, data sampled on falling edge. 098 * 099 * <p>Mode 3 is Clock idle high, data sampled on rising edge. 100 * 101 * @param mode The mode to set. 102 */ 103 public final void setMode(Mode mode) { 104 m_mode = mode.value & 0x3; 105 SPIJNI.spiSetMode(m_port, m_mode); 106 } 107 108 /** Configure the chip select line to be active high. */ 109 public final void setChipSelectActiveHigh() { 110 SPIJNI.spiSetChipSelectActiveHigh(m_port); 111 } 112 113 /** Configure the chip select line to be active low. */ 114 public final void setChipSelectActiveLow() { 115 SPIJNI.spiSetChipSelectActiveLow(m_port); 116 } 117 118 /** 119 * Write data to the peripheral device. Blocks until there is space in the output FIFO. 120 * 121 * <p>If not running in output only mode, also saves the data received on the CIPO input during 122 * the transfer into the receive FIFO. 123 * 124 * @param dataToSend The buffer containing the data to send. 125 * @param size The number of bytes to send. 126 * @return Number of bytes written or -1 on error. 127 */ 128 public int write(byte[] dataToSend, int size) { 129 if (dataToSend.length < size) { 130 throw new IllegalArgumentException("buffer is too small, must be at least " + size); 131 } 132 return SPIJNI.spiWriteB(m_port, dataToSend, (byte) size); 133 } 134 135 /** 136 * Write data to the peripheral device. Blocks until there is space in the output FIFO. 137 * 138 * <p>If not running in output only mode, also saves the data received on the CIPO input during 139 * the transfer into the receive FIFO. 140 * 141 * @param dataToSend The buffer containing the data to send. 142 * @param size The number of bytes to send. 143 * @return Number of bytes written or -1 on error. 144 */ 145 public int write(ByteBuffer dataToSend, int size) { 146 if (dataToSend.hasArray()) { 147 return write(dataToSend.array(), size); 148 } 149 if (!dataToSend.isDirect()) { 150 throw new IllegalArgumentException("must be a direct buffer"); 151 } 152 if (dataToSend.capacity() < size) { 153 throw new IllegalArgumentException("buffer is too small, must be at least " + size); 154 } 155 return SPIJNI.spiWrite(m_port, dataToSend, (byte) size); 156 } 157 158 /** 159 * Read a word from the receive FIFO. 160 * 161 * <p>Waits for the current transfer to complete if the receive FIFO is empty. 162 * 163 * <p>If the receive FIFO is empty, there is no active transfer, and initiate is false, errors. 164 * 165 * @param initiate If true, this function pushes "0" into the transmit buffer and initiates a 166 * transfer. If false, this function assumes that data is already in the receive FIFO from a 167 * previous write. 168 * @param dataReceived Buffer in which to store bytes read. 169 * @param size Number of bytes to read. 170 * @return Number of bytes read or -1 on error. 171 */ 172 public int read(boolean initiate, byte[] dataReceived, int size) { 173 if (dataReceived.length < size) { 174 throw new IllegalArgumentException("buffer is too small, must be at least " + size); 175 } 176 return SPIJNI.spiReadB(m_port, initiate, dataReceived, (byte) size); 177 } 178 179 /** 180 * Read a word from the receive FIFO. 181 * 182 * <p>Waits for the current transfer to complete if the receive FIFO is empty. 183 * 184 * <p>If the receive FIFO is empty, there is no active transfer, and initiate is false, errors. 185 * 186 * @param initiate If true, this function pushes "0" into the transmit buffer and initiates a 187 * transfer. If false, this function assumes that data is already in the receive FIFO from a 188 * previous write. 189 * @param dataReceived The buffer to be filled with the received data. 190 * @param size The length of the transaction, in bytes 191 * @return Number of bytes read or -1 on error. 192 */ 193 public int read(boolean initiate, ByteBuffer dataReceived, int size) { 194 if (dataReceived.hasArray()) { 195 return read(initiate, dataReceived.array(), size); 196 } 197 if (!dataReceived.isDirect()) { 198 throw new IllegalArgumentException("must be a direct buffer"); 199 } 200 if (dataReceived.capacity() < size) { 201 throw new IllegalArgumentException("buffer is too small, must be at least " + size); 202 } 203 return SPIJNI.spiRead(m_port, initiate, dataReceived, (byte) size); 204 } 205 206 /** 207 * Perform a simultaneous read/write transaction with the device. 208 * 209 * @param dataToSend The data to be written out to the device 210 * @param dataReceived Buffer to receive data from the device 211 * @param size The length of the transaction, in bytes 212 * @return TODO 213 */ 214 public int transaction(byte[] dataToSend, byte[] dataReceived, int size) { 215 if (dataToSend.length < size) { 216 throw new IllegalArgumentException("dataToSend is too small, must be at least " + size); 217 } 218 if (dataReceived.length < size) { 219 throw new IllegalArgumentException("dataReceived is too small, must be at least " + size); 220 } 221 return SPIJNI.spiTransactionB(m_port, dataToSend, dataReceived, (byte) size); 222 } 223 224 /** 225 * Perform a simultaneous read/write transaction with the device. 226 * 227 * @param dataToSend The data to be written out to the device. 228 * @param dataReceived Buffer to receive data from the device. 229 * @param size The length of the transaction, in bytes 230 * @return TODO 231 */ 232 public int transaction(ByteBuffer dataToSend, ByteBuffer dataReceived, int size) { 233 if (dataToSend.hasArray() && dataReceived.hasArray()) { 234 return transaction(dataToSend.array(), dataReceived.array(), size); 235 } 236 if (!dataToSend.isDirect()) { 237 throw new IllegalArgumentException("dataToSend must be a direct buffer"); 238 } 239 if (dataToSend.capacity() < size) { 240 throw new IllegalArgumentException("dataToSend is too small, must be at least " + size); 241 } 242 if (!dataReceived.isDirect()) { 243 throw new IllegalArgumentException("dataReceived must be a direct buffer"); 244 } 245 if (dataReceived.capacity() < size) { 246 throw new IllegalArgumentException("dataReceived is too small, must be at least " + size); 247 } 248 return SPIJNI.spiTransaction(m_port, dataToSend, dataReceived, (byte) size); 249 } 250 251 /** 252 * Initialize automatic SPI transfer engine. 253 * 254 * <p>Only a single engine is available, and use of it blocks use of all other chip select usage 255 * on the same physical SPI port while it is running. 256 * 257 * @param bufferSize buffer size in bytes 258 */ 259 public void initAuto(int bufferSize) { 260 SPIJNI.spiInitAuto(m_port, bufferSize); 261 } 262 263 /** Frees the automatic SPI transfer engine. */ 264 public void freeAuto() { 265 SPIJNI.spiFreeAuto(m_port); 266 } 267 268 /** 269 * Set the data to be transmitted by the engine. 270 * 271 * <p>Up to 16 bytes are configurable, and may be followed by up to 127 zero bytes. 272 * 273 * @param dataToSend data to send (maximum 16 bytes) 274 * @param zeroSize number of zeros to send after the data 275 */ 276 public void setAutoTransmitData(byte[] dataToSend, int zeroSize) { 277 SPIJNI.spiSetAutoTransmitData(m_port, dataToSend, zeroSize); 278 } 279 280 /** 281 * Start running the automatic SPI transfer engine at a periodic rate. 282 * 283 * <p>{@link #initAuto(int)} and {@link #setAutoTransmitData(byte[], int)} must be called before 284 * calling this function. 285 * 286 * @param period period between transfers, in seconds (us resolution) 287 */ 288 public void startAutoRate(double period) { 289 SPIJNI.spiStartAutoRate(m_port, period); 290 } 291 292 /** 293 * Start running the automatic SPI transfer engine when a trigger occurs. 294 * 295 * <p>{@link #initAuto(int)} and {@link #setAutoTransmitData(byte[], int)} must be called before 296 * calling this function. 297 * 298 * @param source digital source for the trigger (may be an analog trigger) 299 * @param rising trigger on the rising edge 300 * @param falling trigger on the falling edge 301 */ 302 public void startAutoTrigger(DigitalSource source, boolean rising, boolean falling) { 303 SPIJNI.spiStartAutoTrigger( 304 m_port, 305 source.getPortHandleForRouting(), 306 source.getAnalogTriggerTypeForRouting(), 307 rising, 308 falling); 309 } 310 311 /** Stop running the automatic SPI transfer engine. */ 312 public void stopAuto() { 313 SPIJNI.spiStopAuto(m_port); 314 } 315 316 /** Force the engine to make a single transfer. */ 317 public void forceAutoRead() { 318 SPIJNI.spiForceAutoRead(m_port); 319 } 320 321 /** 322 * Read data that has been transferred by the automatic SPI transfer engine. 323 * 324 * <p>Transfers may be made a byte at a time, so it's necessary for the caller to handle cases 325 * where an entire transfer has not been completed. 326 * 327 * <p>Each received data sequence consists of a timestamp followed by the received data bytes, one 328 * byte per word (in the least significant byte). The length of each received data sequence is the 329 * same as the combined size of the data and zeroSize set in setAutoTransmitData(). 330 * 331 * <p>Blocks until numToRead words have been read or timeout expires. May be called with 332 * numToRead=0 to retrieve how many words are available. 333 * 334 * @param buffer buffer where read words are stored 335 * @param numToRead number of words to read 336 * @param timeout timeout in seconds (ms resolution) 337 * @return Number of words remaining to be read 338 */ 339 public int readAutoReceivedData(ByteBuffer buffer, int numToRead, double timeout) { 340 if (!buffer.isDirect()) { 341 throw new IllegalArgumentException("must be a direct buffer"); 342 } 343 if (buffer.capacity() < numToRead * 4) { 344 throw new IllegalArgumentException( 345 "buffer is too small, must be at least " + (numToRead * 4)); 346 } 347 return SPIJNI.spiReadAutoReceivedData(m_port, buffer, numToRead, timeout); 348 } 349 350 /** 351 * Read data that has been transferred by the automatic SPI transfer engine. 352 * 353 * <p>Transfers may be made a byte at a time, so it's necessary for the caller to handle cases 354 * where an entire transfer has not been completed. 355 * 356 * <p>Each received data sequence consists of a timestamp followed by the received data bytes, one 357 * byte per word (in the least significant byte). The length of each received data sequence is the 358 * same as the combined size of the data and zeroSize set in setAutoTransmitData(). 359 * 360 * <p>Blocks until numToRead words have been read or timeout expires. May be called with 361 * numToRead=0 to retrieve how many words are available. 362 * 363 * @param buffer array where read words are stored 364 * @param numToRead number of words to read 365 * @param timeout timeout in seconds (ms resolution) 366 * @return Number of words remaining to be read 367 */ 368 public int readAutoReceivedData(int[] buffer, int numToRead, double timeout) { 369 if (buffer.length < numToRead) { 370 throw new IllegalArgumentException("buffer is too small, must be at least " + numToRead); 371 } 372 return SPIJNI.spiReadAutoReceivedData(m_port, buffer, numToRead, timeout); 373 } 374 375 /** 376 * Get the number of bytes dropped by the automatic SPI transfer engine due to the receive buffer 377 * being full. 378 * 379 * @return Number of bytes dropped 380 */ 381 public int getAutoDroppedCount() { 382 return SPIJNI.spiGetAutoDroppedCount(m_port); 383 } 384 385 /** 386 * Configure the Auto SPI Stall time between reads. 387 * 388 * @param csToSclkTicks the number of ticks to wait before asserting the cs pin 389 * @param stallTicks the number of ticks to stall for 390 * @param pow2BytesPerRead the number of bytes to read before stalling 391 */ 392 public void configureAutoStall(int csToSclkTicks, int stallTicks, int pow2BytesPerRead) { 393 SPIJNI.spiConfigureAutoStall(m_port, csToSclkTicks, stallTicks, pow2BytesPerRead); 394 } 395 396 private static final int kAccumulateDepth = 2048; 397 398 private static class Accumulator implements AutoCloseable { 399 Accumulator( 400 int port, 401 int xferSize, 402 int validMask, 403 int validValue, 404 int dataShift, 405 int dataSize, 406 boolean isSigned, 407 boolean bigEndian) { 408 m_notifier = new Notifier(this::update); 409 m_buf = 410 ByteBuffer.allocateDirect((xferSize + 1) * kAccumulateDepth * 4) 411 .order(ByteOrder.nativeOrder()); 412 m_intBuf = m_buf.asIntBuffer(); 413 m_xferSize = xferSize + 1; // +1 for timestamp 414 m_validMask = validMask; 415 m_validValue = validValue; 416 m_dataShift = dataShift; 417 m_dataMax = 1 << dataSize; 418 m_dataMsbMask = 1 << (dataSize - 1); 419 m_isSigned = isSigned; 420 m_bigEndian = bigEndian; 421 m_port = port; 422 } 423 424 @Override 425 public void close() { 426 m_notifier.close(); 427 } 428 429 final Notifier m_notifier; 430 final ByteBuffer m_buf; 431 final IntBuffer m_intBuf; 432 final Object m_mutex = new Object(); 433 434 long m_value; 435 int m_count; 436 int m_lastValue; 437 long m_lastTimestamp; 438 double m_integratedValue; 439 440 int m_center; 441 int m_deadband; 442 double m_integratedCenter; 443 444 final int m_validMask; 445 final int m_validValue; 446 final int m_dataMax; // one more than max data value 447 final int m_dataMsbMask; // data field MSB mask (for signed) 448 final int m_dataShift; // data field shift right amount, in bits 449 final int m_xferSize; // SPI transfer size, in bytes 450 final boolean m_isSigned; // is data field signed? 451 final boolean m_bigEndian; // is response big endian? 452 final int m_port; 453 454 void update() { 455 synchronized (m_mutex) { 456 boolean done = false; 457 while (!done) { 458 done = true; 459 460 // get amount of data available 461 int numToRead = SPIJNI.spiReadAutoReceivedData(m_port, m_buf, 0, 0); 462 463 // only get whole responses 464 numToRead -= numToRead % m_xferSize; 465 if (numToRead > m_xferSize * kAccumulateDepth) { 466 numToRead = m_xferSize * kAccumulateDepth; 467 done = false; 468 } 469 if (numToRead == 0) { 470 return; // no samples 471 } 472 473 // read buffered data 474 SPIJNI.spiReadAutoReceivedData(m_port, m_buf, numToRead, 0); 475 476 // loop over all responses 477 for (int off = 0; off < numToRead; off += m_xferSize) { 478 // get timestamp from first word 479 long timestamp = m_intBuf.get(off) & 0xffffffffL; 480 481 // convert from bytes 482 int resp = 0; 483 if (m_bigEndian) { 484 for (int i = 1; i < m_xferSize; ++i) { 485 resp <<= 8; 486 resp |= m_intBuf.get(off + i) & 0xff; 487 } 488 } else { 489 for (int i = m_xferSize - 1; i >= 1; --i) { 490 resp <<= 8; 491 resp |= m_intBuf.get(off + i) & 0xff; 492 } 493 } 494 495 // process response 496 if ((resp & m_validMask) == m_validValue) { 497 // valid sensor data; extract data field 498 int data = resp >> m_dataShift; 499 data &= m_dataMax - 1; 500 // 2s complement conversion if signed MSB is set 501 if (m_isSigned && (data & m_dataMsbMask) != 0) { 502 data -= m_dataMax; 503 } 504 // center offset 505 int dataNoCenter = data; 506 data -= m_center; 507 // only accumulate if outside deadband 508 if (data < -m_deadband || data > m_deadband) { 509 m_value += data; 510 if (m_count != 0) { 511 // timestamps use the 1us FPGA clock; also handle rollover 512 if (timestamp >= m_lastTimestamp) { 513 m_integratedValue += 514 dataNoCenter * (timestamp - m_lastTimestamp) * 1e-6 - m_integratedCenter; 515 } else { 516 m_integratedValue += 517 dataNoCenter * ((1L << 32) - m_lastTimestamp + timestamp) * 1e-6 518 - m_integratedCenter; 519 } 520 } 521 } 522 ++m_count; 523 m_lastValue = data; 524 } else { 525 // no data from the sensor; just clear the last value 526 m_lastValue = 0; 527 } 528 m_lastTimestamp = timestamp; 529 } 530 } 531 } 532 } 533 } 534 535 private Accumulator m_accum; 536 537 /** 538 * Initialize the accumulator. 539 * 540 * @param period Time between reads 541 * @param cmd SPI command to send to request data 542 * @param xferSize SPI transfer size, in bytes 543 * @param validMask Mask to apply to received data for validity checking 544 * @param validValue After validMask is applied, required matching value for validity checking 545 * @param dataShift Bit shift to apply to received data to get actual data value 546 * @param dataSize Size (in bits) of data field 547 * @param isSigned Is data field signed? 548 * @param bigEndian Is device big endian? 549 */ 550 public void initAccumulator( 551 double period, 552 int cmd, 553 int xferSize, 554 int validMask, 555 int validValue, 556 int dataShift, 557 int dataSize, 558 boolean isSigned, 559 boolean bigEndian) { 560 initAuto(xferSize * 2048); 561 byte[] cmdBytes = new byte[] {0, 0, 0, 0}; 562 if (bigEndian) { 563 for (int i = xferSize - 1; i >= 0; --i) { 564 cmdBytes[i] = (byte) (cmd & 0xff); 565 cmd >>= 8; 566 } 567 } else { 568 cmdBytes[0] = (byte) (cmd & 0xff); 569 cmd >>= 8; 570 cmdBytes[1] = (byte) (cmd & 0xff); 571 cmd >>= 8; 572 cmdBytes[2] = (byte) (cmd & 0xff); 573 cmd >>= 8; 574 cmdBytes[3] = (byte) (cmd & 0xff); 575 } 576 setAutoTransmitData(cmdBytes, xferSize - 4); 577 startAutoRate(period); 578 579 m_accum = 580 new Accumulator( 581 m_port, xferSize, validMask, validValue, dataShift, dataSize, isSigned, bigEndian); 582 m_accum.m_notifier.startPeriodic(period * 1024); 583 } 584 585 /** Frees the accumulator. */ 586 public void freeAccumulator() { 587 if (m_accum != null) { 588 m_accum.close(); 589 m_accum = null; 590 } 591 freeAuto(); 592 } 593 594 /** Resets the accumulator to zero. */ 595 public void resetAccumulator() { 596 if (m_accum == null) { 597 return; 598 } 599 synchronized (m_accum.m_mutex) { 600 m_accum.m_value = 0; 601 m_accum.m_count = 0; 602 m_accum.m_lastValue = 0; 603 m_accum.m_lastTimestamp = 0; 604 m_accum.m_integratedValue = 0; 605 } 606 } 607 608 /** 609 * Set the center value of the accumulator. 610 * 611 * <p>The center value is subtracted from each value before it is added to the accumulator. This 612 * is used for the center value of devices like gyros and accelerometers to make integration work 613 * and to take the device offset into account when integrating. 614 * 615 * @param center The accumulator's center value. 616 */ 617 public void setAccumulatorCenter(int center) { 618 if (m_accum == null) { 619 return; 620 } 621 synchronized (m_accum.m_mutex) { 622 m_accum.m_center = center; 623 } 624 } 625 626 /** 627 * Set the accumulator's deadband. 628 * 629 * @param deadband The accumulator's deadband. 630 */ 631 public void setAccumulatorDeadband(int deadband) { 632 if (m_accum == null) { 633 return; 634 } 635 synchronized (m_accum.m_mutex) { 636 m_accum.m_deadband = deadband; 637 } 638 } 639 640 /** 641 * Read the last value read by the accumulator engine. 642 * 643 * @return The last value read by the accumulator engine. 644 */ 645 public int getAccumulatorLastValue() { 646 if (m_accum == null) { 647 return 0; 648 } 649 synchronized (m_accum.m_mutex) { 650 m_accum.update(); 651 return m_accum.m_lastValue; 652 } 653 } 654 655 /** 656 * Read the accumulated value. 657 * 658 * @return The 64-bit value accumulated since the last Reset(). 659 */ 660 public long getAccumulatorValue() { 661 if (m_accum == null) { 662 return 0; 663 } 664 synchronized (m_accum.m_mutex) { 665 m_accum.update(); 666 return m_accum.m_value; 667 } 668 } 669 670 /** 671 * Read the number of accumulated values. 672 * 673 * <p>Read the count of the accumulated values since the accumulator was last Reset(). 674 * 675 * @return The number of times samples from the channel were accumulated. 676 */ 677 public int getAccumulatorCount() { 678 if (m_accum == null) { 679 return 0; 680 } 681 synchronized (m_accum.m_mutex) { 682 m_accum.update(); 683 return m_accum.m_count; 684 } 685 } 686 687 /** 688 * Read the average of the accumulated value. 689 * 690 * @return The accumulated average value (value / count). 691 */ 692 public double getAccumulatorAverage() { 693 if (m_accum == null) { 694 return 0; 695 } 696 synchronized (m_accum.m_mutex) { 697 m_accum.update(); 698 if (m_accum.m_count == 0) { 699 return 0.0; 700 } 701 return ((double) m_accum.m_value) / m_accum.m_count; 702 } 703 } 704 705 /** 706 * Read the accumulated value and the number of accumulated values atomically. 707 * 708 * <p>This function reads the value and count atomically. This can be used for averaging. 709 * 710 * @param result AccumulatorResult object to store the results in. 711 */ 712 public void getAccumulatorOutput(AccumulatorResult result) { 713 if (result == null) { 714 throw new IllegalArgumentException("Null parameter `result'"); 715 } 716 if (m_accum == null) { 717 result.value = 0; 718 result.count = 0; 719 return; 720 } 721 synchronized (m_accum.m_mutex) { 722 m_accum.update(); 723 result.value = m_accum.m_value; 724 result.count = m_accum.m_count; 725 } 726 } 727 728 /** 729 * Set the center value of the accumulator integrator. 730 * 731 * <p>The center value is subtracted from each value*dt before it is added to the integrated 732 * value. This is used for the center value of devices like gyros and accelerometers to take the 733 * device offset into account when integrating. 734 * 735 * @param center The accumulator integrator's center value. 736 */ 737 public void setAccumulatorIntegratedCenter(double center) { 738 if (m_accum == null) { 739 return; 740 } 741 synchronized (m_accum.m_mutex) { 742 m_accum.m_integratedCenter = center; 743 } 744 } 745 746 /** 747 * Read the integrated value. This is the sum of (each value * time between values). 748 * 749 * @return The integrated value accumulated since the last Reset(). 750 */ 751 public double getAccumulatorIntegratedValue() { 752 if (m_accum == null) { 753 return 0; 754 } 755 synchronized (m_accum.m_mutex) { 756 m_accum.update(); 757 return m_accum.m_integratedValue; 758 } 759 } 760 761 /** 762 * Read the average of the integrated value. This is the sum of (each value times the time between 763 * values), divided by the count. 764 * 765 * @return The average of the integrated value accumulated since the last Reset(). 766 */ 767 public double getAccumulatorIntegratedAverage() { 768 if (m_accum == null) { 769 return 0; 770 } 771 synchronized (m_accum.m_mutex) { 772 m_accum.update(); 773 if (m_accum.m_count <= 1) { 774 return 0.0; 775 } 776 // count-1 due to not integrating the first value received 777 return m_accum.m_integratedValue / (m_accum.m_count - 1); 778 } 779 } 780}