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