001/*----------------------------------------------------------------------------*/
002/* Copyright (c) FIRST 2016-2017. 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;
011import java.nio.ByteOrder;
012
013import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType;
014import edu.wpi.first.wpilibj.hal.HAL;
015import edu.wpi.first.wpilibj.hal.SPIJNI;
016
017/**
018 * Represents a SPI bus port.
019 */
020public class SPI extends SensorBase {
021
022  public enum Port {
023    kOnboardCS0(0), kOnboardCS1(1), kOnboardCS2(2), kOnboardCS3(3), kMXP(4);
024
025    @SuppressWarnings("MemberName")
026    public int value;
027
028    private Port(int value) {
029      this.value = value;
030    }
031  }
032
033  private static int devices = 0;
034
035  private byte m_port;
036  private int m_bitOrder;
037  private int m_clockPolarity;
038  private int m_dataOnTrailing;
039
040  /**
041   * Constructor.
042   *
043   * @param port the physical SPI port
044   */
045  public SPI(Port port) {
046    m_port = (byte) port.value;
047    devices++;
048
049    SPIJNI.spiInitialize(m_port);
050
051    HAL.report(tResourceType.kResourceType_SPI, devices);
052  }
053
054  /**
055   * Free the resources used by this object.
056   */
057  public void free() {
058    SPIJNI.spiClose(m_port);
059  }
060
061  /**
062   * Configure the rate of the generated clock signal. The default value is 500,000 Hz. The maximum
063   * value is 4,000,000 Hz.
064   *
065   * @param hz The clock rate in Hertz.
066   */
067  public final void setClockRate(int hz) {
068    SPIJNI.spiSetSpeed(m_port, hz);
069  }
070
071  /**
072   * Configure the order that bits are sent and received on the wire to be most significant bit
073   * first.
074   */
075  public final void setMSBFirst() {
076    m_bitOrder = 1;
077    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
078  }
079
080  /**
081   * Configure the order that bits are sent and received on the wire to be least significant bit
082   * first.
083   */
084  public final void setLSBFirst() {
085    m_bitOrder = 0;
086    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
087  }
088
089  /**
090   * Configure the clock output line to be active low. This is sometimes called clock polarity high
091   * or clock idle high.
092   */
093  public final void setClockActiveLow() {
094    m_clockPolarity = 1;
095    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
096  }
097
098  /**
099   * Configure the clock output line to be active high. This is sometimes called clock polarity low
100   * or clock idle low.
101   */
102  public final void setClockActiveHigh() {
103    m_clockPolarity = 0;
104    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
105  }
106
107  /**
108   * Configure that the data is stable on the falling edge and the data changes on the rising edge.
109   */
110  public final void setSampleDataOnFalling() {
111    m_dataOnTrailing = 1;
112    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
113  }
114
115  /**
116   * Configure that the data is stable on the rising edge and the data changes on the falling edge.
117   */
118  public final void setSampleDataOnRising() {
119    m_dataOnTrailing = 0;
120    SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
121  }
122
123  /**
124   * Configure the chip select line to be active high.
125   */
126  public final void setChipSelectActiveHigh() {
127    SPIJNI.spiSetChipSelectActiveHigh(m_port);
128  }
129
130  /**
131   * Configure the chip select line to be active low.
132   */
133  public final void setChipSelectActiveLow() {
134    SPIJNI.spiSetChipSelectActiveLow(m_port);
135  }
136
137  /**
138   * Write data to the slave device. Blocks until there is space in the output FIFO.
139   *
140   * <p>If not running in output only mode, also saves the data received on the MISO input during
141   * the transfer into the receive FIFO.
142   */
143  public int write(byte[] dataToSend, int size) {
144    ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(size);
145    dataToSendBuffer.put(dataToSend);
146    return SPIJNI.spiWrite(m_port, dataToSendBuffer, (byte) size);
147  }
148
149  /**
150   * Write data to the slave 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 MISO input during
153   * the transfer into the receive FIFO.
154   *
155   * @param dataToSend The buffer containing the data to send. Must be created using
156   *                   ByteBuffer.allocateDirect().
157   */
158  public int write(ByteBuffer dataToSend, int size) {
159    if (!dataToSend.isDirect()) {
160      throw new IllegalArgumentException("must be a direct buffer");
161    }
162    if (dataToSend.capacity() < size) {
163      throw new IllegalArgumentException("buffer is too small, must be at least " + size);
164    }
165    return SPIJNI.spiWrite(m_port, dataToSend, (byte) size);
166  }
167
168  /**
169   * Read a word from the receive FIFO.
170   *
171   * <p>Waits for the current transfer to complete if the receive FIFO is empty.
172   *
173   * <p>If the receive FIFO is empty, there is no active transfer, and initiate is false, errors.
174   *
175   * @param initiate If true, this function pushes "0" into the transmit buffer and initiates a
176   *                 transfer. If false, this function assumes that data is already in the receive
177   *                 FIFO from a previous write.
178   */
179  public int read(boolean initiate, byte[] dataReceived, int size) {
180    final int retVal;
181    ByteBuffer dataReceivedBuffer = ByteBuffer.allocateDirect(size);
182    ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(size);
183    if (initiate) {
184      retVal = SPIJNI.spiTransaction(m_port, dataToSendBuffer, dataReceivedBuffer, (byte) size);
185    } else {
186      retVal = SPIJNI.spiRead(m_port, dataReceivedBuffer, (byte) size);
187    }
188    dataReceivedBuffer.get(dataReceived);
189    return retVal;
190  }
191
192  /**
193   * Read a word from the receive FIFO.
194   *
195   * <p>Waits for the current transfer to complete if the receive FIFO is empty.
196   *
197   * <p>If the receive FIFO is empty, there is no active transfer, and initiate is false, errors.
198   *
199   * @param initiate     If true, this function pushes "0" into the transmit buffer and initiates
200   *                     a transfer. If false, this function assumes that data is already in the
201   *                     receive FIFO from a previous write.
202   * @param dataReceived The buffer to be filled with the received data. Must be created using
203   *                     ByteBuffer.allocateDirect().
204   * @param size         The length of the transaction, in bytes
205   */
206  public int read(boolean initiate, ByteBuffer dataReceived, int size) {
207    if (!dataReceived.isDirect()) {
208      throw new IllegalArgumentException("must be a direct buffer");
209    }
210    if (dataReceived.capacity() < size) {
211      throw new IllegalArgumentException("buffer is too small, must be at least " + size);
212    }
213    if (initiate) {
214      ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(size);
215      return SPIJNI.spiTransaction(m_port, dataToSendBuffer, dataReceived, (byte) size);
216    }
217    return SPIJNI.spiRead(m_port, 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   */
227  public int transaction(byte[] dataToSend, byte[] dataReceived, int size) {
228    ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(size);
229    dataToSendBuffer.put(dataToSend);
230    ByteBuffer dataReceivedBuffer = ByteBuffer.allocateDirect(size);
231    int retVal = SPIJNI.spiTransaction(m_port, dataToSendBuffer, dataReceivedBuffer, (byte) size);
232    dataReceivedBuffer.get(dataReceived);
233    return retVal;
234  }
235
236  /**
237   * Perform a simultaneous read/write transaction with the device
238   *
239   * @param dataToSend   The data to be written out to the device. Must be created using
240   *                     ByteBuffer.allocateDirect().
241   * @param dataReceived Buffer to receive data from the device. Must be created using
242   *                     ByteBuffer.allocateDirect().
243   * @param size         The length of the transaction, in bytes
244   */
245  public int transaction(ByteBuffer dataToSend, ByteBuffer dataReceived, int size) {
246    if (!dataToSend.isDirect()) {
247      throw new IllegalArgumentException("dataToSend must be a direct buffer");
248    }
249    if (dataToSend.capacity() < size) {
250      throw new IllegalArgumentException("dataToSend is too small, must be at least " + size);
251    }
252    if (!dataReceived.isDirect()) {
253      throw new IllegalArgumentException("dataReceived must be a direct buffer");
254    }
255    if (dataReceived.capacity() < size) {
256      throw new IllegalArgumentException("dataReceived is too small, must be at least " + size);
257    }
258    return SPIJNI.spiTransaction(m_port, dataToSend, dataReceived, (byte) size);
259  }
260
261  /**
262   * Initialize the accumulator.
263   *
264   * @param period     Time between reads
265   * @param cmd        SPI command to send to request data
266   * @param xferSize   SPI transfer size, in bytes
267   * @param validMask  Mask to apply to received data for validity checking
268   * @param validValue After validMask is applied, required matching value for validity checking
269   * @param dataShift  Bit shift to apply to received data to get actual data value
270   * @param dataSize   Size (in bits) of data field
271   * @param isSigned   Is data field signed?
272   * @param bigEndian  Is device big endian?
273   */
274  public void initAccumulator(double period, int cmd, int xferSize,
275                              int validMask, int validValue,
276                              int dataShift, int dataSize,
277                              boolean isSigned, boolean bigEndian) {
278    SPIJNI.spiInitAccumulator(m_port, (int) (period * 1.0e6), cmd,
279        (byte) xferSize, validMask, validValue, (byte) dataShift,
280        (byte) dataSize, isSigned, bigEndian);
281  }
282
283  /**
284   * Frees the accumulator.
285   */
286  public void freeAccumulator() {
287    SPIJNI.spiFreeAccumulator(m_port);
288  }
289
290  /**
291   * Resets the accumulator to zero.
292   */
293  public void resetAccumulator() {
294    SPIJNI.spiResetAccumulator(m_port);
295  }
296
297  /**
298   * Set the center value of the accumulator.
299   *
300   * <p>The center value is subtracted from each value before it is added to the accumulator. This
301   * is used for the center value of devices like gyros and accelerometers to make integration work
302   * and to take the device offset into account when integrating.
303   */
304  public void setAccumulatorCenter(int center) {
305    SPIJNI.spiSetAccumulatorCenter(m_port, center);
306  }
307
308  /**
309   * Set the accumulator's deadband.
310   */
311  public void setAccumulatorDeadband(int deadband) {
312    SPIJNI.spiSetAccumulatorDeadband(m_port, deadband);
313  }
314
315  /**
316   * Read the last value read by the accumulator engine.
317   */
318  public int getAccumulatorLastValue() {
319    return SPIJNI.spiGetAccumulatorLastValue(m_port);
320  }
321
322  /**
323   * Read the accumulated value.
324   *
325   * @return The 64-bit value accumulated since the last Reset().
326   */
327  public long getAccumulatorValue() {
328    return SPIJNI.spiGetAccumulatorValue(m_port);
329  }
330
331  /**
332   * Read the number of accumulated values.
333   *
334   * <p>Read the count of the accumulated values since the accumulator was last Reset().
335   *
336   * @return The number of times samples from the channel were accumulated.
337   */
338  public int getAccumulatorCount() {
339    return SPIJNI.spiGetAccumulatorCount(m_port);
340  }
341
342  /**
343   * Read the average of the accumulated value.
344   *
345   * @return The accumulated average value (value / count).
346   */
347  public double getAccumulatorAverage() {
348    return SPIJNI.spiGetAccumulatorAverage(m_port);
349  }
350
351  /**
352   * Read the accumulated value and the number of accumulated values atomically.
353   *
354   * <p>This function reads the value and count atomically. This can be used for averaging.
355   *
356   * @param result AccumulatorResult object to store the results in.
357   */
358  public void getAccumulatorOutput(AccumulatorResult result) {
359    if (result == null) {
360      throw new IllegalArgumentException("Null parameter `result'");
361    }
362    ByteBuffer value = ByteBuffer.allocateDirect(8);
363    // set the byte order
364    value.order(ByteOrder.LITTLE_ENDIAN);
365    ByteBuffer count = ByteBuffer.allocateDirect(8);
366    // set the byte order
367    count.order(ByteOrder.LITTLE_ENDIAN);
368    SPIJNI.spiGetAccumulatorOutput(m_port, value.asLongBuffer(), count.asLongBuffer());
369    result.value = value.asLongBuffer().get(0);
370    result.count = count.asLongBuffer().get(0);
371  }
372}