001/*----------------------------------------------------------------------------*/
002/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
003/* Open Source Software - may be modified and shared by FRC teams. The code   */
004/* must be accompanied by the FIRST BSD license file in the root directory of */
005/* the project.                                                               */
006/*----------------------------------------------------------------------------*/
007
008package edu.wpi.first.wpilibj;
009
010import java.nio.ByteBuffer;
011
012import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType;
013import edu.wpi.first.wpilibj.hal.HAL;
014import edu.wpi.first.wpilibj.hal.SPIJNI;
015
016/**
017 * Represents a SPI bus port.
018 */
019public class SPI {
020  public enum Port {
021    kOnboardCS0(0), kOnboardCS1(1), kOnboardCS2(2), kOnboardCS3(3), kMXP(4);
022
023    @SuppressWarnings("MemberName")
024    public int value;
025
026    Port(int value) {
027      this.value = value;
028    }
029  }
030
031  private static int devices = 0;
032
033  private int m_port;
034  private int m_bitOrder;
035  private int m_clockPolarity;
036  private int m_dataOnTrailing;
037
038  /**
039   * Constructor.
040   *
041   * @param port the physical SPI port
042   */
043  public SPI(Port port) {
044    m_port = (byte) port.value;
045    devices++;
046
047    SPIJNI.spiInitialize(m_port);
048
049    HAL.report(tResourceType.kResourceType_SPI, devices);
050  }
051
052  /**
053   * Free the resources used by this object.
054   */
055  public void free() {
056    if (m_accum != null) {
057      m_accum.free();
058      m_accum = null;
059    }
060    SPIJNI.spiClose(m_port);
061  }
062
063  /**
064   * Configure the rate of the generated clock signal. The default value is 500,000 Hz. The maximum
065   * value is 4,000,000 Hz.
066   *
067   * @param hz The clock rate in Hertz.
068   */
069  public final void setClockRate(int hz) {
070    SPIJNI.spiSetSpeed(m_port, hz);
071  }
072
073  /**
074   * Configure the order that bits are sent and received on the wire to be most significant bit
075   * first.
076   */
077  public final void setMSBFirst() {
078    m_bitOrder = 1;
079    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
080  }
081
082  /**
083   * Configure the order that bits are sent and received on the wire to be least significant bit
084   * first.
085   */
086  public final void setLSBFirst() {
087    m_bitOrder = 0;
088    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
089  }
090
091  /**
092   * Configure the clock output line to be active low. This is sometimes called clock polarity high
093   * or clock idle high.
094   */
095  public final void setClockActiveLow() {
096    m_clockPolarity = 1;
097    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
098  }
099
100  /**
101   * Configure the clock output line to be active high. This is sometimes called clock polarity low
102   * or clock idle low.
103   */
104  public final void setClockActiveHigh() {
105    m_clockPolarity = 0;
106    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
107  }
108
109  /**
110   * Configure that the data is stable on the falling edge and the data changes on the rising edge.
111   */
112  public final void setSampleDataOnFalling() {
113    m_dataOnTrailing = 1;
114    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
115  }
116
117  /**
118   * Configure that the data is stable on the rising edge and the data changes on the falling edge.
119   */
120  public final void setSampleDataOnRising() {
121    m_dataOnTrailing = 0;
122    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
123  }
124
125  /**
126   * Configure the chip select line to be active high.
127   */
128  public final void setChipSelectActiveHigh() {
129    SPIJNI.spiSetChipSelectActiveHigh(m_port);
130  }
131
132  /**
133   * Configure the chip select line to be active low.
134   */
135  public final void setChipSelectActiveLow() {
136    SPIJNI.spiSetChipSelectActiveLow(m_port);
137  }
138
139  /**
140   * Write data to the slave device. Blocks until there is space in the output FIFO.
141   *
142   * <p>If not running in output only mode, also saves the data received on the MISO input during
143   * the transfer into the receive FIFO.
144   */
145  public int write(byte[] dataToSend, int size) {
146    if (dataToSend.length < size) {
147      throw new IllegalArgumentException("buffer is too small, must be at least " + size);
148    }
149    return SPIJNI.spiWriteB(m_port, dataToSend, (byte) size);
150  }
151
152  /**
153   * Write data to the slave device. Blocks until there is space in the output FIFO.
154   *
155   * <p>If not running in output only mode, also saves the data received on the MISO input during
156   * the transfer into the receive FIFO.
157   *
158   * @param dataToSend The buffer containing the data to send.
159   */
160  public int write(ByteBuffer dataToSend, int size) {
161    if (dataToSend.hasArray()) {
162      return write(dataToSend.array(), size);
163    }
164    if (!dataToSend.isDirect()) {
165      throw new IllegalArgumentException("must be a direct buffer");
166    }
167    if (dataToSend.capacity() < size) {
168      throw new IllegalArgumentException("buffer is too small, must be at least " + size);
169    }
170    return SPIJNI.spiWrite(m_port, dataToSend, (byte) size);
171  }
172
173  /**
174   * Read a word from the receive FIFO.
175   *
176   * <p>Waits for the current transfer to complete if the receive FIFO is empty.
177   *
178   * <p>If the receive FIFO is empty, there is no active transfer, and initiate is false, errors.
179   *
180   * @param initiate If true, this function pushes "0" into the transmit buffer and initiates a
181   *                 transfer. If false, this function assumes that data is already in the receive
182   *                 FIFO from a previous write.
183   */
184  public int read(boolean initiate, byte[] dataReceived, int size) {
185    if (dataReceived.length < size) {
186      throw new IllegalArgumentException("buffer is too small, must be at least " + size);
187    }
188    return SPIJNI.spiReadB(m_port, initiate, dataReceived, (byte) size);
189  }
190
191  /**
192   * Read a word from the receive FIFO.
193   *
194   * <p>Waits for the current transfer to complete if the receive FIFO is empty.
195   *
196   * <p>If the receive FIFO is empty, there is no active transfer, and initiate is false, errors.
197   *
198   * @param initiate     If true, this function pushes "0" into the transmit buffer and initiates
199   *                     a transfer. If false, this function assumes that data is already in the
200   *                     receive FIFO from a previous write.
201   * @param dataReceived The buffer to be filled with the received data.
202   * @param size         The length of the transaction, in bytes
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   */
224  public int transaction(byte[] dataToSend, byte[] dataReceived, int size) {
225    if (dataToSend.length < size) {
226      throw new IllegalArgumentException("dataToSend is too small, must be at least " + size);
227    }
228    if (dataReceived.length < size) {
229      throw new IllegalArgumentException("dataReceived is too small, must be at least " + size);
230    }
231    return SPIJNI.spiTransactionB(m_port, dataToSend, dataReceived, (byte) size);
232  }
233
234  /**
235   * Perform a simultaneous read/write transaction with the device.
236   *
237   * @param dataToSend   The data to be written out to the device.
238   * @param dataReceived Buffer to receive data from the device.
239   * @param size         The length of the transaction, in bytes
240   */
241  public int transaction(ByteBuffer dataToSend, ByteBuffer dataReceived, int size) {
242    if (dataToSend.hasArray() && dataReceived.hasArray()) {
243      return transaction(dataToSend.array(), dataReceived.array(), size);
244    }
245    if (!dataToSend.isDirect()) {
246      throw new IllegalArgumentException("dataToSend must be a direct buffer");
247    }
248    if (dataToSend.capacity() < size) {
249      throw new IllegalArgumentException("dataToSend is too small, must be at least " + size);
250    }
251    if (!dataReceived.isDirect()) {
252      throw new IllegalArgumentException("dataReceived must be a direct buffer");
253    }
254    if (dataReceived.capacity() < size) {
255      throw new IllegalArgumentException("dataReceived is too small, must be at least " + size);
256    }
257    return SPIJNI.spiTransaction(m_port, dataToSend, dataReceived, (byte) size);
258  }
259
260  /**
261   * Initialize automatic SPI transfer engine.
262   *
263   * <p>Only a single engine is available, and use of it blocks use of all other
264   * chip select usage on the same physical SPI port while it is running.
265   *
266   * @param bufferSize buffer size in bytes
267   */
268  public void initAuto(int bufferSize) {
269    SPIJNI.spiInitAuto(m_port, bufferSize);
270  }
271
272  /**
273   * Frees the automatic SPI transfer engine.
274   */
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
283   * bytes.
284   *
285   * @param dataToSend data to send (maximum 16 bytes)
286   * @param zeroSize number of zeros to send after the data
287   */
288  public void setAutoTransmitData(byte[] dataToSend, int zeroSize) {
289    SPIJNI.spiSetAutoTransmitData(m_port, dataToSend, zeroSize);
290  }
291
292  /**
293   * Start running the automatic SPI transfer engine at a periodic rate.
294   *
295   * <p>{@link #initAuto(int)} and {@link #setAutoTransmitData(byte[], int)} must
296   * be called before calling this function.
297   *
298   * @param period period between transfers, in seconds (us resolution)
299   */
300  public void startAutoRate(double period) {
301    SPIJNI.spiStartAutoRate(m_port, period);
302  }
303
304  /**
305   * Start running the automatic SPI transfer engine when a trigger occurs.
306   *
307   * <p>{@link #initAuto(int)} and {@link #setAutoTransmitData(byte[], int)} must
308   * be called before calling this function.
309   *
310   * @param source digital source for the trigger (may be an analog trigger)
311   * @param rising trigger on the rising edge
312   * @param falling trigger on the falling edge
313   */
314  public void startAutoTrigger(DigitalSource source, boolean rising, boolean falling) {
315    SPIJNI.spiStartAutoTrigger(m_port, source.getPortHandleForRouting(),
316                               source.getAnalogTriggerTypeForRouting(), rising, falling);
317  }
318
319  /**
320   * Stop running the automatic SPI transfer engine.
321   */
322  public void stopAuto() {
323    SPIJNI.spiStopAuto(m_port);
324  }
325
326  /**
327   * Force the engine to make a single transfer.
328   */
329  public void forceAutoRead() {
330    SPIJNI.spiForceAutoRead(m_port);
331  }
332
333  /**
334   * Read data that has been transferred by the automatic SPI transfer engine.
335   *
336   * <p>Transfers may be made a byte at a time, so it's necessary for the caller
337   * to handle cases where an entire transfer has not been completed.
338   *
339   * <p>Blocks until numToRead bytes have been read or timeout expires.
340   * May be called with numToRead=0 to retrieve how many bytes are available.
341   *
342   * @param buffer buffer where read bytes are stored
343   * @param numToRead number of bytes to read
344   * @param timeout timeout in seconds (ms resolution)
345   * @return Number of bytes remaining to be read
346   */
347  public int readAutoReceivedData(ByteBuffer buffer, int numToRead, double timeout) {
348    if (buffer.hasArray()) {
349      return readAutoReceivedData(buffer.array(), numToRead, timeout);
350    }
351    if (!buffer.isDirect()) {
352      throw new IllegalArgumentException("must be a direct buffer");
353    }
354    if (buffer.capacity() < numToRead) {
355      throw new IllegalArgumentException("buffer is too small, must be at least " + numToRead);
356    }
357    return SPIJNI.spiReadAutoReceivedData(m_port, buffer, numToRead, timeout);
358  }
359
360  /**
361   * Read data that has been transferred by the automatic SPI transfer engine.
362   *
363   * <p>Transfers may be made a byte at a time, so it's necessary for the caller
364   * to handle cases where an entire transfer has not been completed.
365   *
366   * <p>Blocks until numToRead bytes have been read or timeout expires.
367   * May be called with numToRead=0 to retrieve how many bytes are available.
368   *
369   * @param buffer array where read bytes are stored
370   * @param numToRead number of bytes to read
371   * @param timeout timeout in seconds (ms resolution)
372   * @return Number of bytes remaining to be read
373   */
374  public int readAutoReceivedData(byte[] buffer, int numToRead, double timeout) {
375    if (buffer.length < numToRead) {
376      throw new IllegalArgumentException("buffer is too small, must be at least " + numToRead);
377    }
378    return SPIJNI.spiReadAutoReceivedData(m_port, buffer, numToRead, timeout);
379  }
380
381  /**
382   * Get the number of bytes dropped by the automatic SPI transfer engine due
383   * to the receive buffer being full.
384   *
385   * @return Number of bytes dropped
386   */
387  public int getAutoDroppedCount() {
388    return SPIJNI.spiGetAutoDroppedCount(m_port);
389  }
390
391  private static final int kAccumulateDepth = 2048;
392
393  private static class Accumulator {
394    Accumulator(int port, int xferSize, int validMask, int validValue, int dataShift,
395                int dataSize, boolean isSigned, boolean bigEndian) {
396      m_notifier = new Notifier(this::update);
397      m_buf = ByteBuffer.allocateDirect(xferSize * kAccumulateDepth);
398      m_xferSize = xferSize;
399      m_validMask = validMask;
400      m_validValue = validValue;
401      m_dataShift = dataShift;
402      m_dataMax = 1 << dataSize;
403      m_dataMsbMask = 1 << (dataSize - 1);
404      m_isSigned = isSigned;
405      m_bigEndian = bigEndian;
406      m_port = port;
407    }
408
409    void free() {
410      m_notifier.stop();
411    }
412
413    final Notifier m_notifier;
414    final ByteBuffer m_buf;
415    final Object m_mutex = new Object();
416
417    long m_value;
418    int m_count;
419    int m_lastValue;
420
421    int m_center;
422    int m_deadband;
423
424    final int m_validMask;
425    final int m_validValue;
426    final int m_dataMax;        // one more than max data value
427    final int m_dataMsbMask;    // data field MSB mask (for signed)
428    final int m_dataShift;      // data field shift right amount, in bits
429    final int m_xferSize;       // SPI transfer size, in bytes
430    final boolean m_isSigned;   // is data field signed?
431    final boolean m_bigEndian;  // is response big endian?
432    final int m_port;
433
434    void update() {
435      synchronized (m_mutex) {
436        boolean done = false;
437        while (!done) {
438          done = true;
439
440          // get amount of data available
441          int numToRead = SPIJNI.spiReadAutoReceivedData(m_port, m_buf, 0, 0);
442
443          // only get whole responses
444          numToRead -= numToRead % m_xferSize;
445          if (numToRead > m_xferSize * kAccumulateDepth) {
446            numToRead = m_xferSize * kAccumulateDepth;
447            done = false;
448          }
449          if (numToRead == 0) {
450            return;  // no samples
451          }
452
453          // read buffered data
454          SPIJNI.spiReadAutoReceivedData(m_port, m_buf, numToRead, 0);
455
456          // loop over all responses
457          for (int off = 0; off < numToRead; off += m_xferSize) {
458            // convert from bytes
459            int resp = 0;
460            if (m_bigEndian) {
461              for (int i = 0; i < m_xferSize; ++i) {
462                resp <<= 8;
463                resp |= ((int) m_buf.get(off + i)) & 0xff;
464              }
465            } else {
466              for (int i = m_xferSize - 1; i >= 0; --i) {
467                resp <<= 8;
468                resp |= ((int) m_buf.get(off + i)) & 0xff;
469              }
470            }
471
472            // process response
473            if ((resp & m_validMask) == m_validValue) {
474              // valid sensor data; extract data field
475              int data = resp >> m_dataShift;
476              data &= m_dataMax - 1;
477              // 2s complement conversion if signed MSB is set
478              if (m_isSigned && (data & m_dataMsbMask) != 0) {
479                data -= m_dataMax;
480              }
481              // center offset
482              data -= m_center;
483              // only accumulate if outside deadband
484              if (data < -m_deadband || data > m_deadband) {
485                m_value += data;
486              }
487              ++m_count;
488              m_lastValue = data;
489            } else {
490              // no data from the sensor; just clear the last value
491              m_lastValue = 0;
492            }
493          }
494        }
495      }
496    }
497  }
498
499  private Accumulator m_accum = null;
500
501  /**
502   * Initialize the accumulator.
503   *
504   * @param period     Time between reads
505   * @param cmd        SPI command to send to request data
506   * @param xferSize   SPI transfer size, in bytes
507   * @param validMask  Mask to apply to received data for validity checking
508   * @param validValue After validMask is applied, required matching value for validity checking
509   * @param dataShift  Bit shift to apply to received data to get actual data value
510   * @param dataSize   Size (in bits) of data field
511   * @param isSigned   Is data field signed?
512   * @param bigEndian  Is device big endian?
513   */
514  public void initAccumulator(double period, int cmd, int xferSize,
515                              int validMask, int validValue,
516                              int dataShift, int dataSize,
517                              boolean isSigned, boolean bigEndian) {
518    initAuto(xferSize * 2048);
519    byte[] cmdBytes = new byte[] {0, 0, 0, 0};
520    if (bigEndian) {
521      for (int i = xferSize - 1; i >= 0; --i) {
522        cmdBytes[i] = (byte) (cmd & 0xff);
523        cmd >>= 8;
524      }
525    } else {
526      cmdBytes[0] = (byte) (cmd & 0xff);
527      cmd >>= 8;
528      cmdBytes[1] = (byte) (cmd & 0xff);
529      cmd >>= 8;
530      cmdBytes[2] = (byte) (cmd & 0xff);
531      cmd >>= 8;
532      cmdBytes[3] = (byte) (cmd & 0xff);
533    }
534    setAutoTransmitData(cmdBytes, xferSize - 4);
535    startAutoRate(period);
536
537    m_accum = new Accumulator(m_port, xferSize, validMask, validValue, dataShift, dataSize,
538                              isSigned, bigEndian);
539    m_accum.m_notifier.startPeriodic(period * 1024);
540  }
541
542  /**
543   * Frees the accumulator.
544   */
545  public void freeAccumulator() {
546    if (m_accum != null) {
547      m_accum.free();
548      m_accum = null;
549    }
550    freeAuto();
551  }
552
553  /**
554   * Resets the accumulator to zero.
555   */
556  public void resetAccumulator() {
557    if (m_accum == null) {
558      return;
559    }
560    synchronized (m_accum.m_mutex) {
561      m_accum.m_value = 0;
562      m_accum.m_count = 0;
563      m_accum.m_lastValue = 0;
564    }
565  }
566
567  /**
568   * Set the center value of the accumulator.
569   *
570   * <p>The center value is subtracted from each value before it is added to the accumulator. This
571   * is used for the center value of devices like gyros and accelerometers to make integration work
572   * and to take the device offset into account when integrating.
573   */
574  public void setAccumulatorCenter(int center) {
575    if (m_accum == null) {
576      return;
577    }
578    synchronized (m_accum.m_mutex) {
579      m_accum.m_center = center;
580    }
581  }
582
583  /**
584   * Set the accumulator's deadband.
585   */
586  public void setAccumulatorDeadband(int deadband) {
587    if (m_accum == null) {
588      return;
589    }
590    synchronized (m_accum.m_mutex) {
591      m_accum.m_deadband = deadband;
592    }
593  }
594
595  /**
596   * Read the last value read by the accumulator engine.
597   */
598  public int getAccumulatorLastValue() {
599    if (m_accum == null) {
600      return 0;
601    }
602    synchronized (m_accum.m_mutex) {
603      m_accum.update();
604      return m_accum.m_lastValue;
605    }
606  }
607
608  /**
609   * Read the accumulated value.
610   *
611   * @return The 64-bit value accumulated since the last Reset().
612   */
613  public long getAccumulatorValue() {
614    if (m_accum == null) {
615      return 0;
616    }
617    synchronized (m_accum.m_mutex) {
618      m_accum.update();
619      return m_accum.m_value;
620    }
621  }
622
623  /**
624   * Read the number of accumulated values.
625   *
626   * <p>Read the count of the accumulated values since the accumulator was last Reset().
627   *
628   * @return The number of times samples from the channel were accumulated.
629   */
630  public int getAccumulatorCount() {
631    if (m_accum == null) {
632      return 0;
633    }
634    synchronized (m_accum.m_mutex) {
635      m_accum.update();
636      return m_accum.m_count;
637    }
638  }
639
640  /**
641   * Read the average of the accumulated value.
642   *
643   * @return The accumulated average value (value / count).
644   */
645  public double getAccumulatorAverage() {
646    if (m_accum == null) {
647      return 0;
648    }
649    synchronized (m_accum.m_mutex) {
650      m_accum.update();
651      if (m_accum.m_count == 0) {
652        return 0.0;
653      }
654      return ((double) m_accum.m_value) / m_accum.m_count;
655    }
656  }
657
658  /**
659   * Read the accumulated value and the number of accumulated values atomically.
660   *
661   * <p>This function reads the value and count atomically. This can be used for averaging.
662   *
663   * @param result AccumulatorResult object to store the results in.
664   */
665  public void getAccumulatorOutput(AccumulatorResult result) {
666    if (result == null) {
667      throw new IllegalArgumentException("Null parameter `result'");
668    }
669    if (m_accum == null) {
670      result.value = 0;
671      result.count = 0;
672      return;
673    }
674    synchronized (m_accum.m_mutex) {
675      m_accum.update();
676      result.value = m_accum.m_value;
677      result.count = m_accum.m_count;
678    }
679  }
680}