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