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