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