2016-01-02 03:02:34 -08:00
|
|
|
/*----------------------------------------------------------------------------*/
|
2018-01-02 09:20:21 -08:00
|
|
|
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
2016-01-02 03:02:34 -08:00
|
|
|
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
|
|
|
|
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
|
|
|
|
/* the project. */
|
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
|
|
2014-07-16 16:24:44 -04:00
|
|
|
package edu.wpi.first.wpilibj;
|
|
|
|
|
|
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
|
|
2016-07-10 16:24:57 -07:00
|
|
|
import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType;
|
|
|
|
|
import edu.wpi.first.wpilibj.hal.HAL;
|
2014-07-16 16:24:44 -04:00
|
|
|
import edu.wpi.first.wpilibj.hal.SPIJNI;
|
|
|
|
|
|
|
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Represents a SPI bus port.
|
2014-07-16 16:24:44 -04:00
|
|
|
*/
|
2018-05-22 23:33:17 -07:00
|
|
|
public class SPI implements AutoCloseable {
|
2015-06-25 15:07:55 -04:00
|
|
|
public enum Port {
|
|
|
|
|
kOnboardCS0(0), kOnboardCS1(1), kOnboardCS2(2), kOnboardCS3(3), kMXP(4);
|
|
|
|
|
|
2016-07-13 23:39:58 -07:00
|
|
|
@SuppressWarnings("MemberName")
|
|
|
|
|
public int value;
|
2015-06-25 15:07:55 -04:00
|
|
|
|
2017-10-17 21:47:55 -07:00
|
|
|
Port(int value) {
|
2016-07-13 23:39:58 -07:00
|
|
|
this.value = value;
|
2014-07-16 16:24:44 -04:00
|
|
|
}
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
2015-06-25 15:07:55 -04:00
|
|
|
|
|
|
|
|
private static int devices = 0;
|
|
|
|
|
|
2017-05-09 12:12:46 -07:00
|
|
|
private int m_port;
|
2016-05-20 12:07:40 -04:00
|
|
|
private int m_bitOrder;
|
|
|
|
|
private int m_clockPolarity;
|
|
|
|
|
private int m_dataOnTrailing;
|
2015-06-25 15:07:55 -04:00
|
|
|
|
|
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Constructor.
|
2015-06-25 15:07:55 -04:00
|
|
|
*
|
|
|
|
|
* @param port the physical SPI port
|
|
|
|
|
*/
|
|
|
|
|
public SPI(Port port) {
|
2016-07-13 23:39:58 -07:00
|
|
|
m_port = (byte) port.value;
|
2015-06-25 15:07:55 -04:00
|
|
|
devices++;
|
|
|
|
|
|
2015-11-01 09:11:52 -08:00
|
|
|
SPIJNI.spiInitialize(m_port);
|
2015-06-25 15:07:55 -04:00
|
|
|
|
2016-07-10 16:24:57 -07:00
|
|
|
HAL.report(tResourceType.kResourceType_SPI, devices);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
2018-05-22 23:33:17 -07:00
|
|
|
|
|
|
|
|
@Deprecated
|
|
|
|
|
public void free() {
|
|
|
|
|
close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void close() {
|
2017-12-13 23:41:37 -08:00
|
|
|
if (m_accum != null) {
|
2018-05-22 23:33:17 -07:00
|
|
|
m_accum.close();
|
2017-12-13 23:41:37 -08:00
|
|
|
m_accum = null;
|
|
|
|
|
}
|
2015-06-25 15:07:55 -04:00
|
|
|
SPIJNI.spiClose(m_port);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Configure the rate of the generated clock signal. The default value is 500,000 Hz. The maximum
|
|
|
|
|
* value is 4,000,000 Hz.
|
2015-06-25 15:07:55 -04:00
|
|
|
*
|
|
|
|
|
* @param hz The clock rate in Hertz.
|
|
|
|
|
*/
|
|
|
|
|
public final void setClockRate(int hz) {
|
|
|
|
|
SPIJNI.spiSetSpeed(m_port, hz);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Configure the order that bits are sent and received on the wire to be most significant bit
|
|
|
|
|
* first.
|
2015-06-25 15:07:55 -04:00
|
|
|
*/
|
|
|
|
|
public final void setMSBFirst() {
|
2016-05-20 12:07:40 -04:00
|
|
|
m_bitOrder = 1;
|
|
|
|
|
SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Configure the order that bits are sent and received on the wire to be least significant bit
|
|
|
|
|
* first.
|
2015-06-25 15:07:55 -04:00
|
|
|
*/
|
|
|
|
|
public final void setLSBFirst() {
|
2016-05-20 12:07:40 -04:00
|
|
|
m_bitOrder = 0;
|
|
|
|
|
SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Configure the clock output line to be active low. This is sometimes called clock polarity high
|
|
|
|
|
* or clock idle high.
|
2015-06-25 15:07:55 -04:00
|
|
|
*/
|
|
|
|
|
public final void setClockActiveLow() {
|
2016-05-20 12:07:40 -04:00
|
|
|
m_clockPolarity = 1;
|
|
|
|
|
SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Configure the clock output line to be active high. This is sometimes called clock polarity low
|
|
|
|
|
* or clock idle low.
|
2015-06-25 15:07:55 -04:00
|
|
|
*/
|
|
|
|
|
public final void setClockActiveHigh() {
|
2016-05-20 12:07:40 -04:00
|
|
|
m_clockPolarity = 0;
|
|
|
|
|
SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
2018-05-14 18:16:36 -07:00
|
|
|
/**
|
|
|
|
|
* Configure that the data is stable on the leading edge and the data changes on the trailing
|
|
|
|
|
* edge.
|
|
|
|
|
*/
|
|
|
|
|
public final void setSampleDataOnLeadingEdge() {
|
|
|
|
|
m_dataOnTrailing = 0;
|
|
|
|
|
SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Configure that the data is stable on the trailing edge and the data changes on the leading
|
|
|
|
|
* edge.
|
|
|
|
|
*/
|
|
|
|
|
public final void setSampleDataOnTrailingEdge() {
|
|
|
|
|
m_dataOnTrailing = 1;
|
|
|
|
|
SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-25 15:07:55 -04:00
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Configure that the data is stable on the falling edge and the data changes on the rising edge.
|
2018-05-14 18:16:36 -07:00
|
|
|
* Note this gets reversed is setClockActiveLow is set.
|
|
|
|
|
* @deprecated use {@link #setSampleDataOnTrailingEdge()} in most cases.
|
2015-06-25 15:07:55 -04:00
|
|
|
*/
|
2018-05-14 18:16:36 -07:00
|
|
|
@Deprecated
|
2015-06-25 15:07:55 -04:00
|
|
|
public final void setSampleDataOnFalling() {
|
2016-05-20 12:07:40 -04:00
|
|
|
m_dataOnTrailing = 1;
|
|
|
|
|
SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Configure that the data is stable on the rising edge and the data changes on the falling edge.
|
2018-05-14 18:16:36 -07:00
|
|
|
* Note this gets reversed is setClockActiveLow is set.
|
|
|
|
|
* @deprecated use {@link #setSampleDataOnLeadingEdge()} in most cases.
|
2015-06-25 15:07:55 -04:00
|
|
|
*/
|
2018-05-14 18:16:36 -07:00
|
|
|
@Deprecated
|
2015-06-25 15:07:55 -04:00
|
|
|
public final void setSampleDataOnRising() {
|
2016-05-20 12:07:40 -04:00
|
|
|
m_dataOnTrailing = 0;
|
|
|
|
|
SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
2018-05-14 18:16:36 -07:00
|
|
|
|
|
|
|
|
|
2015-06-25 15:07:55 -04:00
|
|
|
/**
|
|
|
|
|
* Configure the chip select line to be active high.
|
|
|
|
|
*/
|
|
|
|
|
public final void setChipSelectActiveHigh() {
|
2015-11-01 09:11:52 -08:00
|
|
|
SPIJNI.spiSetChipSelectActiveHigh(m_port);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Configure the chip select line to be active low.
|
|
|
|
|
*/
|
|
|
|
|
public final void setChipSelectActiveLow() {
|
2015-11-01 09:11:52 -08:00
|
|
|
SPIJNI.spiSetChipSelectActiveLow(m_port);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Write data to the slave device. Blocks until there is space in the output FIFO.
|
2015-06-25 15:07:55 -04:00
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* <p>If not running in output only mode, also saves the data received on the MISO input during
|
|
|
|
|
* the transfer into the receive FIFO.
|
2015-06-25 15:07:55 -04:00
|
|
|
*/
|
|
|
|
|
public int write(byte[] dataToSend, int size) {
|
2017-11-14 00:00:45 -08:00
|
|
|
if (dataToSend.length < size) {
|
|
|
|
|
throw new IllegalArgumentException("buffer is too small, must be at least " + size);
|
|
|
|
|
}
|
|
|
|
|
return SPIJNI.spiWriteB(m_port, dataToSend, (byte) size);
|
2015-11-06 11:12:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Write data to the slave device. Blocks until there is space in the output FIFO.
|
2015-11-06 11:12:00 -08:00
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* <p>If not running in output only mode, also saves the data received on the MISO input during
|
|
|
|
|
* the transfer into the receive FIFO.
|
2015-11-06 11:12:00 -08:00
|
|
|
*
|
2017-11-14 00:00:45 -08:00
|
|
|
* @param dataToSend The buffer containing the data to send.
|
2015-11-06 11:12:00 -08:00
|
|
|
*/
|
|
|
|
|
public int write(ByteBuffer dataToSend, int size) {
|
2017-11-14 00:00:45 -08:00
|
|
|
if (dataToSend.hasArray()) {
|
|
|
|
|
return write(dataToSend.array(), size);
|
|
|
|
|
}
|
2016-05-20 12:07:40 -04:00
|
|
|
if (!dataToSend.isDirect()) {
|
2015-11-06 11:12:00 -08:00
|
|
|
throw new IllegalArgumentException("must be a direct buffer");
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
|
|
|
|
if (dataToSend.capacity() < size) {
|
2015-11-06 11:12:00 -08:00
|
|
|
throw new IllegalArgumentException("buffer is too small, must be at least " + size);
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
2015-11-06 11:12:00 -08:00
|
|
|
return SPIJNI.spiWrite(m_port, dataToSend, (byte) size);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Read a word from the receive FIFO.
|
|
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* <p>Waits for the current transfer to complete if the receive FIFO is empty.
|
2015-06-25 15:07:55 -04:00
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* <p>If the receive FIFO is empty, there is no active transfer, and initiate is false, errors.
|
2015-06-25 15:07:55 -04:00
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* @param initiate If true, this function pushes "0" into the transmit buffer and initiates a
|
|
|
|
|
* transfer. If false, this function assumes that data is already in the receive
|
|
|
|
|
* FIFO from a previous write.
|
2015-06-25 15:07:55 -04:00
|
|
|
*/
|
|
|
|
|
public int read(boolean initiate, byte[] dataReceived, int size) {
|
2017-11-14 00:00:45 -08:00
|
|
|
if (dataReceived.length < size) {
|
|
|
|
|
throw new IllegalArgumentException("buffer is too small, must be at least " + size);
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
2017-11-14 00:00:45 -08:00
|
|
|
return SPIJNI.spiReadB(m_port, initiate, dataReceived, (byte) size);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
2015-11-06 11:12:00 -08:00
|
|
|
/**
|
|
|
|
|
* Read a word from the receive FIFO.
|
|
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* <p>Waits for the current transfer to complete if the receive FIFO is empty.
|
2015-11-06 11:12:00 -08:00
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* <p>If the receive FIFO is empty, there is no active transfer, and initiate is false, errors.
|
2015-11-06 11:12:00 -08:00
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* @param initiate If true, this function pushes "0" into the transmit buffer and initiates
|
|
|
|
|
* a transfer. If false, this function assumes that data is already in the
|
|
|
|
|
* receive FIFO from a previous write.
|
2017-11-14 00:00:45 -08:00
|
|
|
* @param dataReceived The buffer to be filled with the received data.
|
2016-05-20 12:07:40 -04:00
|
|
|
* @param size The length of the transaction, in bytes
|
2015-11-06 11:12:00 -08:00
|
|
|
*/
|
|
|
|
|
public int read(boolean initiate, ByteBuffer dataReceived, int size) {
|
2017-11-14 00:00:45 -08:00
|
|
|
if (dataReceived.hasArray()) {
|
|
|
|
|
return read(initiate, dataReceived.array(), size);
|
|
|
|
|
}
|
2016-05-20 12:07:40 -04:00
|
|
|
if (!dataReceived.isDirect()) {
|
2015-11-06 11:12:00 -08:00
|
|
|
throw new IllegalArgumentException("must be a direct buffer");
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
|
|
|
|
if (dataReceived.capacity() < size) {
|
2015-11-06 11:12:00 -08:00
|
|
|
throw new IllegalArgumentException("buffer is too small, must be at least " + size);
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
2017-11-14 00:00:45 -08:00
|
|
|
return SPIJNI.spiRead(m_port, initiate, dataReceived, (byte) size);
|
2015-11-06 11:12:00 -08:00
|
|
|
}
|
|
|
|
|
|
2015-06-25 15:07:55 -04:00
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Perform a simultaneous read/write transaction with the device.
|
2015-06-25 15:07:55 -04:00
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* @param dataToSend The data to be written out to the device
|
2015-06-25 15:07:55 -04:00
|
|
|
* @param dataReceived Buffer to receive data from the device
|
2016-05-20 12:07:40 -04:00
|
|
|
* @param size The length of the transaction, in bytes
|
2015-06-25 15:07:55 -04:00
|
|
|
*/
|
|
|
|
|
public int transaction(byte[] dataToSend, byte[] dataReceived, int size) {
|
2017-11-14 00:00:45 -08:00
|
|
|
if (dataToSend.length < size) {
|
|
|
|
|
throw new IllegalArgumentException("dataToSend is too small, must be at least " + size);
|
|
|
|
|
}
|
|
|
|
|
if (dataReceived.length < size) {
|
|
|
|
|
throw new IllegalArgumentException("dataReceived is too small, must be at least " + size);
|
|
|
|
|
}
|
|
|
|
|
return SPIJNI.spiTransactionB(m_port, dataToSend, dataReceived, (byte) size);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2015-11-06 11:12:00 -08:00
|
|
|
|
|
|
|
|
/**
|
2017-07-28 22:24:05 -07:00
|
|
|
* Perform a simultaneous read/write transaction with the device.
|
2015-11-06 11:12:00 -08:00
|
|
|
*
|
2017-11-14 00:00:45 -08:00
|
|
|
* @param dataToSend The data to be written out to the device.
|
|
|
|
|
* @param dataReceived Buffer to receive data from the device.
|
2016-05-20 12:07:40 -04:00
|
|
|
* @param size The length of the transaction, in bytes
|
2015-11-06 11:12:00 -08:00
|
|
|
*/
|
|
|
|
|
public int transaction(ByteBuffer dataToSend, ByteBuffer dataReceived, int size) {
|
2017-11-14 00:00:45 -08:00
|
|
|
if (dataToSend.hasArray() && dataReceived.hasArray()) {
|
|
|
|
|
return transaction(dataToSend.array(), dataReceived.array(), size);
|
|
|
|
|
}
|
2016-05-20 12:07:40 -04:00
|
|
|
if (!dataToSend.isDirect()) {
|
2015-11-06 11:12:00 -08:00
|
|
|
throw new IllegalArgumentException("dataToSend must be a direct buffer");
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
|
|
|
|
if (dataToSend.capacity() < size) {
|
2015-11-06 11:12:00 -08:00
|
|
|
throw new IllegalArgumentException("dataToSend is too small, must be at least " + size);
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
|
|
|
|
if (!dataReceived.isDirect()) {
|
2015-11-06 11:12:00 -08:00
|
|
|
throw new IllegalArgumentException("dataReceived must be a direct buffer");
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
|
|
|
|
if (dataReceived.capacity() < size) {
|
2015-11-06 11:12:00 -08:00
|
|
|
throw new IllegalArgumentException("dataReceived is too small, must be at least " + size);
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
2015-11-06 11:12:00 -08:00
|
|
|
return SPIJNI.spiTransaction(m_port, dataToSend, dataReceived, (byte) size);
|
|
|
|
|
}
|
2015-11-22 11:50:49 -08:00
|
|
|
|
2017-12-13 23:41:37 -08:00
|
|
|
/**
|
|
|
|
|
* Initialize automatic SPI transfer engine.
|
|
|
|
|
*
|
|
|
|
|
* <p>Only a single engine is available, and use of it blocks use of all other
|
|
|
|
|
* chip select usage on the same physical SPI port while it is running.
|
|
|
|
|
*
|
|
|
|
|
* @param bufferSize buffer size in bytes
|
|
|
|
|
*/
|
|
|
|
|
public void initAuto(int bufferSize) {
|
|
|
|
|
SPIJNI.spiInitAuto(m_port, bufferSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Frees the automatic SPI transfer engine.
|
|
|
|
|
*/
|
|
|
|
|
public void freeAuto() {
|
|
|
|
|
SPIJNI.spiFreeAuto(m_port);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the data to be transmitted by the engine.
|
|
|
|
|
*
|
|
|
|
|
* <p>Up to 16 bytes are configurable, and may be followed by up to 127 zero
|
|
|
|
|
* bytes.
|
|
|
|
|
*
|
|
|
|
|
* @param dataToSend data to send (maximum 16 bytes)
|
|
|
|
|
* @param zeroSize number of zeros to send after the data
|
|
|
|
|
*/
|
|
|
|
|
public void setAutoTransmitData(byte[] dataToSend, int zeroSize) {
|
|
|
|
|
SPIJNI.spiSetAutoTransmitData(m_port, dataToSend, zeroSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Start running the automatic SPI transfer engine at a periodic rate.
|
|
|
|
|
*
|
|
|
|
|
* <p>{@link #initAuto(int)} and {@link #setAutoTransmitData(byte[], int)} must
|
|
|
|
|
* be called before calling this function.
|
|
|
|
|
*
|
|
|
|
|
* @param period period between transfers, in seconds (us resolution)
|
|
|
|
|
*/
|
|
|
|
|
public void startAutoRate(double period) {
|
|
|
|
|
SPIJNI.spiStartAutoRate(m_port, period);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Start running the automatic SPI transfer engine when a trigger occurs.
|
|
|
|
|
*
|
|
|
|
|
* <p>{@link #initAuto(int)} and {@link #setAutoTransmitData(byte[], int)} must
|
|
|
|
|
* be called before calling this function.
|
|
|
|
|
*
|
|
|
|
|
* @param source digital source for the trigger (may be an analog trigger)
|
|
|
|
|
* @param rising trigger on the rising edge
|
|
|
|
|
* @param falling trigger on the falling edge
|
|
|
|
|
*/
|
|
|
|
|
public void startAutoTrigger(DigitalSource source, boolean rising, boolean falling) {
|
|
|
|
|
SPIJNI.spiStartAutoTrigger(m_port, source.getPortHandleForRouting(),
|
|
|
|
|
source.getAnalogTriggerTypeForRouting(), rising, falling);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stop running the automatic SPI transfer engine.
|
|
|
|
|
*/
|
|
|
|
|
public void stopAuto() {
|
|
|
|
|
SPIJNI.spiStopAuto(m_port);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Force the engine to make a single transfer.
|
|
|
|
|
*/
|
|
|
|
|
public void forceAutoRead() {
|
|
|
|
|
SPIJNI.spiForceAutoRead(m_port);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Read data that has been transferred by the automatic SPI transfer engine.
|
|
|
|
|
*
|
|
|
|
|
* <p>Transfers may be made a byte at a time, so it's necessary for the caller
|
|
|
|
|
* to handle cases where an entire transfer has not been completed.
|
|
|
|
|
*
|
|
|
|
|
* <p>Blocks until numToRead bytes have been read or timeout expires.
|
|
|
|
|
* May be called with numToRead=0 to retrieve how many bytes are available.
|
|
|
|
|
*
|
|
|
|
|
* @param buffer buffer where read bytes are stored
|
|
|
|
|
* @param numToRead number of bytes to read
|
|
|
|
|
* @param timeout timeout in seconds (ms resolution)
|
|
|
|
|
* @return Number of bytes remaining to be read
|
|
|
|
|
*/
|
|
|
|
|
public int readAutoReceivedData(ByteBuffer buffer, int numToRead, double timeout) {
|
|
|
|
|
if (buffer.hasArray()) {
|
|
|
|
|
return readAutoReceivedData(buffer.array(), numToRead, timeout);
|
|
|
|
|
}
|
|
|
|
|
if (!buffer.isDirect()) {
|
|
|
|
|
throw new IllegalArgumentException("must be a direct buffer");
|
|
|
|
|
}
|
|
|
|
|
if (buffer.capacity() < numToRead) {
|
|
|
|
|
throw new IllegalArgumentException("buffer is too small, must be at least " + numToRead);
|
|
|
|
|
}
|
|
|
|
|
return SPIJNI.spiReadAutoReceivedData(m_port, buffer, numToRead, timeout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Read data that has been transferred by the automatic SPI transfer engine.
|
|
|
|
|
*
|
|
|
|
|
* <p>Transfers may be made a byte at a time, so it's necessary for the caller
|
|
|
|
|
* to handle cases where an entire transfer has not been completed.
|
|
|
|
|
*
|
|
|
|
|
* <p>Blocks until numToRead bytes have been read or timeout expires.
|
|
|
|
|
* May be called with numToRead=0 to retrieve how many bytes are available.
|
|
|
|
|
*
|
|
|
|
|
* @param buffer array where read bytes are stored
|
|
|
|
|
* @param numToRead number of bytes to read
|
|
|
|
|
* @param timeout timeout in seconds (ms resolution)
|
|
|
|
|
* @return Number of bytes remaining to be read
|
|
|
|
|
*/
|
|
|
|
|
public int readAutoReceivedData(byte[] buffer, int numToRead, double timeout) {
|
|
|
|
|
if (buffer.length < numToRead) {
|
|
|
|
|
throw new IllegalArgumentException("buffer is too small, must be at least " + numToRead);
|
|
|
|
|
}
|
|
|
|
|
return SPIJNI.spiReadAutoReceivedData(m_port, buffer, numToRead, timeout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the number of bytes dropped by the automatic SPI transfer engine due
|
|
|
|
|
* to the receive buffer being full.
|
|
|
|
|
*
|
|
|
|
|
* @return Number of bytes dropped
|
|
|
|
|
*/
|
|
|
|
|
public int getAutoDroppedCount() {
|
|
|
|
|
return SPIJNI.spiGetAutoDroppedCount(m_port);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static final int kAccumulateDepth = 2048;
|
|
|
|
|
|
2018-05-22 23:33:17 -07:00
|
|
|
private static class Accumulator implements AutoCloseable {
|
2017-12-13 23:41:37 -08:00
|
|
|
Accumulator(int port, int xferSize, int validMask, int validValue, int dataShift,
|
|
|
|
|
int dataSize, boolean isSigned, boolean bigEndian) {
|
|
|
|
|
m_notifier = new Notifier(this::update);
|
|
|
|
|
m_buf = ByteBuffer.allocateDirect(xferSize * kAccumulateDepth);
|
|
|
|
|
m_xferSize = xferSize;
|
|
|
|
|
m_validMask = validMask;
|
|
|
|
|
m_validValue = validValue;
|
|
|
|
|
m_dataShift = dataShift;
|
|
|
|
|
m_dataMax = 1 << dataSize;
|
|
|
|
|
m_dataMsbMask = 1 << (dataSize - 1);
|
|
|
|
|
m_isSigned = isSigned;
|
|
|
|
|
m_bigEndian = bigEndian;
|
|
|
|
|
m_port = port;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-22 23:33:17 -07:00
|
|
|
@Override
|
|
|
|
|
public void close() {
|
2018-05-24 16:56:29 -07:00
|
|
|
m_notifier.close();
|
2017-12-13 23:41:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Notifier m_notifier;
|
|
|
|
|
final ByteBuffer m_buf;
|
|
|
|
|
final Object m_mutex = new Object();
|
|
|
|
|
|
|
|
|
|
long m_value;
|
|
|
|
|
int m_count;
|
|
|
|
|
int m_lastValue;
|
|
|
|
|
|
|
|
|
|
int m_center;
|
|
|
|
|
int m_deadband;
|
|
|
|
|
|
|
|
|
|
final int m_validMask;
|
|
|
|
|
final int m_validValue;
|
|
|
|
|
final int m_dataMax; // one more than max data value
|
|
|
|
|
final int m_dataMsbMask; // data field MSB mask (for signed)
|
|
|
|
|
final int m_dataShift; // data field shift right amount, in bits
|
|
|
|
|
final int m_xferSize; // SPI transfer size, in bytes
|
|
|
|
|
final boolean m_isSigned; // is data field signed?
|
|
|
|
|
final boolean m_bigEndian; // is response big endian?
|
|
|
|
|
final int m_port;
|
|
|
|
|
|
|
|
|
|
void update() {
|
|
|
|
|
synchronized (m_mutex) {
|
|
|
|
|
boolean done = false;
|
|
|
|
|
while (!done) {
|
|
|
|
|
done = true;
|
|
|
|
|
|
|
|
|
|
// get amount of data available
|
|
|
|
|
int numToRead = SPIJNI.spiReadAutoReceivedData(m_port, m_buf, 0, 0);
|
|
|
|
|
|
|
|
|
|
// only get whole responses
|
|
|
|
|
numToRead -= numToRead % m_xferSize;
|
|
|
|
|
if (numToRead > m_xferSize * kAccumulateDepth) {
|
|
|
|
|
numToRead = m_xferSize * kAccumulateDepth;
|
|
|
|
|
done = false;
|
|
|
|
|
}
|
|
|
|
|
if (numToRead == 0) {
|
|
|
|
|
return; // no samples
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// read buffered data
|
|
|
|
|
SPIJNI.spiReadAutoReceivedData(m_port, m_buf, numToRead, 0);
|
|
|
|
|
|
|
|
|
|
// loop over all responses
|
|
|
|
|
for (int off = 0; off < numToRead; off += m_xferSize) {
|
|
|
|
|
// convert from bytes
|
|
|
|
|
int resp = 0;
|
|
|
|
|
if (m_bigEndian) {
|
|
|
|
|
for (int i = 0; i < m_xferSize; ++i) {
|
|
|
|
|
resp <<= 8;
|
|
|
|
|
resp |= ((int) m_buf.get(off + i)) & 0xff;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (int i = m_xferSize - 1; i >= 0; --i) {
|
|
|
|
|
resp <<= 8;
|
|
|
|
|
resp |= ((int) m_buf.get(off + i)) & 0xff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// process response
|
|
|
|
|
if ((resp & m_validMask) == m_validValue) {
|
|
|
|
|
// valid sensor data; extract data field
|
|
|
|
|
int data = resp >> m_dataShift;
|
|
|
|
|
data &= m_dataMax - 1;
|
|
|
|
|
// 2s complement conversion if signed MSB is set
|
|
|
|
|
if (m_isSigned && (data & m_dataMsbMask) != 0) {
|
|
|
|
|
data -= m_dataMax;
|
|
|
|
|
}
|
|
|
|
|
// center offset
|
|
|
|
|
data -= m_center;
|
|
|
|
|
// only accumulate if outside deadband
|
|
|
|
|
if (data < -m_deadband || data > m_deadband) {
|
|
|
|
|
m_value += data;
|
|
|
|
|
}
|
|
|
|
|
++m_count;
|
|
|
|
|
m_lastValue = data;
|
|
|
|
|
} else {
|
|
|
|
|
// no data from the sensor; just clear the last value
|
|
|
|
|
m_lastValue = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Accumulator m_accum = null;
|
|
|
|
|
|
2015-11-22 11:50:49 -08:00
|
|
|
/**
|
|
|
|
|
* Initialize the accumulator.
|
|
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* @param period Time between reads
|
|
|
|
|
* @param cmd SPI command to send to request data
|
|
|
|
|
* @param xferSize SPI transfer size, in bytes
|
|
|
|
|
* @param validMask Mask to apply to received data for validity checking
|
|
|
|
|
* @param validValue After validMask is applied, required matching value for validity checking
|
|
|
|
|
* @param dataShift Bit shift to apply to received data to get actual data value
|
|
|
|
|
* @param dataSize Size (in bits) of data field
|
|
|
|
|
* @param isSigned Is data field signed?
|
|
|
|
|
* @param bigEndian Is device big endian?
|
2015-11-22 11:50:49 -08:00
|
|
|
*/
|
2016-05-20 12:07:40 -04:00
|
|
|
public void initAccumulator(double period, int cmd, int xferSize,
|
|
|
|
|
int validMask, int validValue,
|
|
|
|
|
int dataShift, int dataSize,
|
|
|
|
|
boolean isSigned, boolean bigEndian) {
|
2017-12-13 23:41:37 -08:00
|
|
|
initAuto(xferSize * 2048);
|
|
|
|
|
byte[] cmdBytes = new byte[] {0, 0, 0, 0};
|
|
|
|
|
if (bigEndian) {
|
|
|
|
|
for (int i = xferSize - 1; i >= 0; --i) {
|
|
|
|
|
cmdBytes[i] = (byte) (cmd & 0xff);
|
|
|
|
|
cmd >>= 8;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
cmdBytes[0] = (byte) (cmd & 0xff);
|
|
|
|
|
cmd >>= 8;
|
|
|
|
|
cmdBytes[1] = (byte) (cmd & 0xff);
|
|
|
|
|
cmd >>= 8;
|
|
|
|
|
cmdBytes[2] = (byte) (cmd & 0xff);
|
|
|
|
|
cmd >>= 8;
|
|
|
|
|
cmdBytes[3] = (byte) (cmd & 0xff);
|
|
|
|
|
}
|
|
|
|
|
setAutoTransmitData(cmdBytes, xferSize - 4);
|
|
|
|
|
startAutoRate(period);
|
|
|
|
|
|
|
|
|
|
m_accum = new Accumulator(m_port, xferSize, validMask, validValue, dataShift, dataSize,
|
|
|
|
|
isSigned, bigEndian);
|
|
|
|
|
m_accum.m_notifier.startPeriodic(period * 1024);
|
2015-11-22 11:50:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Frees the accumulator.
|
|
|
|
|
*/
|
|
|
|
|
public void freeAccumulator() {
|
2017-12-25 23:03:22 -05:00
|
|
|
if (m_accum != null) {
|
2018-05-22 23:33:17 -07:00
|
|
|
m_accum.close();
|
2017-12-25 23:03:22 -05:00
|
|
|
m_accum = null;
|
|
|
|
|
}
|
2017-12-13 23:41:37 -08:00
|
|
|
freeAuto();
|
2015-11-22 11:50:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resets the accumulator to zero.
|
|
|
|
|
*/
|
|
|
|
|
public void resetAccumulator() {
|
2017-12-13 23:41:37 -08:00
|
|
|
if (m_accum == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
synchronized (m_accum.m_mutex) {
|
|
|
|
|
m_accum.m_value = 0;
|
|
|
|
|
m_accum.m_count = 0;
|
|
|
|
|
m_accum.m_lastValue = 0;
|
|
|
|
|
}
|
2015-11-22 11:50:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the center value of the accumulator.
|
|
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* <p>The center value is subtracted from each value before it is added to the accumulator. This
|
2015-11-22 11:50:49 -08:00
|
|
|
* is used for the center value of devices like gyros and accelerometers to make integration work
|
|
|
|
|
* and to take the device offset into account when integrating.
|
|
|
|
|
*/
|
|
|
|
|
public void setAccumulatorCenter(int center) {
|
2017-12-13 23:41:37 -08:00
|
|
|
if (m_accum == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
synchronized (m_accum.m_mutex) {
|
|
|
|
|
m_accum.m_center = center;
|
|
|
|
|
}
|
2015-11-22 11:50:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the accumulator's deadband.
|
|
|
|
|
*/
|
|
|
|
|
public void setAccumulatorDeadband(int deadband) {
|
2017-12-13 23:41:37 -08:00
|
|
|
if (m_accum == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
synchronized (m_accum.m_mutex) {
|
|
|
|
|
m_accum.m_deadband = deadband;
|
|
|
|
|
}
|
2015-11-22 11:50:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Read the last value read by the accumulator engine.
|
|
|
|
|
*/
|
|
|
|
|
public int getAccumulatorLastValue() {
|
2017-12-13 23:41:37 -08:00
|
|
|
if (m_accum == null) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
synchronized (m_accum.m_mutex) {
|
|
|
|
|
m_accum.update();
|
|
|
|
|
return m_accum.m_lastValue;
|
|
|
|
|
}
|
2015-11-22 11:50:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Read the accumulated value.
|
|
|
|
|
*
|
|
|
|
|
* @return The 64-bit value accumulated since the last Reset().
|
|
|
|
|
*/
|
|
|
|
|
public long getAccumulatorValue() {
|
2017-12-13 23:41:37 -08:00
|
|
|
if (m_accum == null) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
synchronized (m_accum.m_mutex) {
|
|
|
|
|
m_accum.update();
|
|
|
|
|
return m_accum.m_value;
|
|
|
|
|
}
|
2015-11-22 11:50:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Read the number of accumulated values.
|
|
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* <p>Read the count of the accumulated values since the accumulator was last Reset().
|
2015-11-22 11:50:49 -08:00
|
|
|
*
|
|
|
|
|
* @return The number of times samples from the channel were accumulated.
|
|
|
|
|
*/
|
|
|
|
|
public int getAccumulatorCount() {
|
2017-12-13 23:41:37 -08:00
|
|
|
if (m_accum == null) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
synchronized (m_accum.m_mutex) {
|
|
|
|
|
m_accum.update();
|
|
|
|
|
return m_accum.m_count;
|
|
|
|
|
}
|
2015-11-22 11:50:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Read the average of the accumulated value.
|
|
|
|
|
*
|
|
|
|
|
* @return The accumulated average value (value / count).
|
|
|
|
|
*/
|
|
|
|
|
public double getAccumulatorAverage() {
|
2017-12-13 23:41:37 -08:00
|
|
|
if (m_accum == null) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
synchronized (m_accum.m_mutex) {
|
|
|
|
|
m_accum.update();
|
|
|
|
|
if (m_accum.m_count == 0) {
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
return ((double) m_accum.m_value) / m_accum.m_count;
|
|
|
|
|
}
|
2015-11-22 11:50:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Read the accumulated value and the number of accumulated values atomically.
|
|
|
|
|
*
|
2016-05-20 12:07:40 -04:00
|
|
|
* <p>This function reads the value and count atomically. This can be used for averaging.
|
2015-11-22 11:50:49 -08:00
|
|
|
*
|
|
|
|
|
* @param result AccumulatorResult object to store the results in.
|
|
|
|
|
*/
|
|
|
|
|
public void getAccumulatorOutput(AccumulatorResult result) {
|
|
|
|
|
if (result == null) {
|
|
|
|
|
throw new IllegalArgumentException("Null parameter `result'");
|
|
|
|
|
}
|
2017-12-13 23:41:37 -08:00
|
|
|
if (m_accum == null) {
|
|
|
|
|
result.value = 0;
|
|
|
|
|
result.count = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
synchronized (m_accum.m_mutex) {
|
|
|
|
|
m_accum.update();
|
|
|
|
|
result.value = m_accum.m_value;
|
|
|
|
|
result.count = m_accum.m_count;
|
|
|
|
|
}
|
2015-11-22 11:50:49 -08:00
|
|
|
}
|
2014-07-16 16:24:44 -04:00
|
|
|
}
|