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}