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}