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