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