001/*----------------------------------------------------------------------------*/
002/* Copyright (c) FIRST 2008-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;
011
012import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType;
013import edu.wpi.first.wpilibj.hal.HAL;
014import edu.wpi.first.wpilibj.hal.I2CJNI;
015import edu.wpi.first.wpilibj.util.BoundaryException;
016
017/**
018 * I2C bus interface class.
019 *
020 * <p>This class is intended to be used by sensor (and other I2C device) drivers. It probably should
021 * not be used directly.
022 */
023public class I2C extends SensorBase {
024  public enum Port {
025    kOnboard(0), kMXP(1);
026
027    @SuppressWarnings("MemberName")
028    public final int value;
029
030    private Port(int value) {
031      this.value = value;
032    }
033  }
034
035  private final Port m_port;
036  private final int m_deviceAddress;
037
038  /**
039   * Constructor.
040   *
041   * @param port          The I2C port the device is connected to.
042   * @param deviceAddress The address of the device on the I2C bus.
043   */
044  public I2C(Port port, int deviceAddress) {
045    m_port = port;
046    m_deviceAddress = deviceAddress;
047
048    I2CJNI.i2CInitialize((byte) port.value);
049
050    HAL.report(tResourceType.kResourceType_I2C, deviceAddress);
051  }
052
053  /**
054   * Destructor.
055   */
056  public void free() {
057  }
058
059  /**
060   * Generic transaction.
061   *
062   * <p>This is a lower-level interface to the I2C hardware giving you more control over each
063   * transaction.
064   *
065   * @param dataToSend   Buffer of data to send as part of the transaction.
066   * @param sendSize     Number of bytes to send as part of the transaction.
067   * @param dataReceived Buffer to read data into.
068   * @param receiveSize  Number of bytes to read from the device.
069   * @return Transfer Aborted... false for success, true for aborted.
070   */
071  public synchronized boolean transaction(byte[] dataToSend, int sendSize,
072                                          byte[] dataReceived, int receiveSize) {
073    final int status;
074
075    ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(sendSize);
076    if (sendSize > 0 && dataToSend != null) {
077      dataToSendBuffer.put(dataToSend);
078    }
079    ByteBuffer dataReceivedBuffer = ByteBuffer.allocateDirect(receiveSize);
080
081    status = I2CJNI.i2CTransaction((byte) m_port.value, (byte) m_deviceAddress, dataToSendBuffer,
082                                   (byte) sendSize, dataReceivedBuffer, (byte) receiveSize);
083    if (receiveSize > 0 && dataReceived != null) {
084      dataReceivedBuffer.get(dataReceived);
085    }
086    return status < receiveSize;
087  }
088
089  /**
090   * Generic transaction.
091   *
092   * <p>This is a lower-level interface to the I2C hardware giving you more control over each
093   * transaction.
094   *
095   * @param dataToSend   Buffer of data to send as part of the transaction. Must be allocated using
096   *                     ByteBuffer.allocateDirect().
097   * @param sendSize     Number of bytes to send as part of the transaction.
098   * @param dataReceived Buffer to read data into. Must be allocated using {@link
099   *                     ByteBuffer#allocateDirect(int)}.
100   * @param receiveSize  Number of bytes to read from the device.
101   * @return Transfer Aborted... false for success, true for aborted.
102   */
103  public synchronized boolean transaction(ByteBuffer dataToSend, int sendSize,
104                                          ByteBuffer dataReceived, int receiveSize) {
105    if (!dataToSend.isDirect()) {
106      throw new IllegalArgumentException("dataToSend must be a direct buffer");
107    }
108    if (dataToSend.capacity() < sendSize) {
109      throw new IllegalArgumentException("dataToSend is too small, must be at least " + sendSize);
110    }
111    if (!dataReceived.isDirect()) {
112      throw new IllegalArgumentException("dataReceived must be a direct buffer");
113    }
114    if (dataReceived.capacity() < receiveSize) {
115      throw new IllegalArgumentException(
116          "dataReceived is too small, must be at least " + receiveSize);
117    }
118
119    return I2CJNI.i2CTransaction((byte) m_port.value, (byte) m_deviceAddress, dataToSend,
120                                 (byte) sendSize, dataReceived, (byte) receiveSize) < receiveSize;
121  }
122
123  /**
124   * Attempt to address a device on the I2C bus.
125   *
126   * <p>This allows you to figure out if there is a device on the I2C bus that responds to the
127   * address specified in the constructor.
128   *
129   * @return Transfer Aborted... false for success, true for aborted.
130   */
131  public boolean addressOnly() {
132    return transaction((byte[]) null, (byte) 0, null, (byte) 0);
133  }
134
135  /**
136   * Execute a write transaction with the device.
137   *
138   * <p>Write a single byte to a register on a device and wait until the transaction is complete.
139   *
140   * @param registerAddress The address of the register on the device to be written.
141   * @param data            The byte to write to the register on the device.
142   */
143  public synchronized boolean write(int registerAddress, int data) {
144    byte[] buffer = new byte[2];
145    buffer[0] = (byte) registerAddress;
146    buffer[1] = (byte) data;
147
148    ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(2);
149    dataToSendBuffer.put(buffer);
150
151    return I2CJNI.i2CWrite((byte) m_port.value, (byte) m_deviceAddress, dataToSendBuffer,
152                           (byte) buffer.length) < buffer.length;
153  }
154
155  /**
156   * Execute a write transaction with the device.
157   *
158   * <p>Write multiple bytes to a register on a device and wait until the transaction is complete.
159   *
160   * @param data The data to write to the device.
161   */
162  public synchronized boolean writeBulk(byte[] data) {
163    ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(data.length);
164    dataToSendBuffer.put(data);
165
166    return I2CJNI.i2CWrite((byte) m_port.value, (byte) m_deviceAddress, dataToSendBuffer,
167                           (byte) data.length) < data.length;
168  }
169
170  /**
171   * Execute a write transaction with the device.
172   *
173   * <p>Write multiple bytes to a register on a device and wait until the transaction is complete.
174   *
175   * @param data The data to write to the device. Must be created using ByteBuffer.allocateDirect().
176   */
177  public synchronized boolean writeBulk(ByteBuffer data, int size) {
178    if (!data.isDirect()) {
179      throw new IllegalArgumentException("must be a direct buffer");
180    }
181    if (data.capacity() < size) {
182      throw new IllegalArgumentException(
183          "buffer is too small, must be at least " + size);
184    }
185
186    return I2CJNI.i2CWrite((byte) m_port.value, (byte) m_deviceAddress, data, (byte) size) < size;
187  }
188
189  /**
190   * Execute a read transaction with the device.
191   *
192   * <p>Read bytes from a device. Most I2C devices will auto-increment the register pointer
193   * internally allowing you to read consecutive registers on a device in a single transaction.
194   *
195   * @param registerAddress The register to read first in the transaction.
196   * @param count           The number of bytes to read in the transaction.
197   * @param buffer          A pointer to the array of bytes to store the data read from the device.
198   * @return Transfer Aborted... false for success, true for aborted.
199   */
200  public boolean read(int registerAddress, int count, byte[] buffer) {
201    if (count < 1) {
202      throw new BoundaryException("Value must be at least 1, " + count + " given");
203    }
204
205    if (buffer == null) {
206      throw new NullPointerException("Null return buffer was given");
207    }
208    byte[] registerAddressArray = new byte[1];
209    registerAddressArray[0] = (byte) registerAddress;
210
211    return transaction(registerAddressArray, registerAddressArray.length, buffer, count);
212  }
213
214  /**
215   * Execute a read transaction with the device.
216   *
217   * <p>Read bytes from a device. Most I2C devices will auto-increment the register pointer
218   * internally allowing you to read consecutive registers on a device in a single transaction.
219   *
220   * @param registerAddress The register to read first in the transaction.
221   * @param count           The number of bytes to read in the transaction.
222   * @param buffer          A buffer to store the data read from the device. Must be created using
223   *                        ByteBuffer.allocateDirect().
224   * @return Transfer Aborted... false for success, true for aborted.
225   */
226  public boolean read(int registerAddress, int count, ByteBuffer buffer) {
227    if (count < 1) {
228      throw new BoundaryException("Value must be at least 1, " + count + " given");
229    }
230
231    if (!buffer.isDirect()) {
232      throw new IllegalArgumentException("must be a direct buffer");
233    }
234    if (buffer.capacity() < count) {
235      throw new IllegalArgumentException("buffer is too small, must be at least " + count);
236    }
237
238    ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(1);
239    dataToSendBuffer.put(0, (byte) registerAddress);
240
241    return transaction(dataToSendBuffer, 1, buffer, count);
242  }
243
244  /**
245   * Execute a read only transaction with the device.
246   *
247   * <p>Read bytes from a device. This method does not write any data to prompt the device.
248   *
249   * @param buffer A pointer to the array of bytes to store the data read from the device.
250   * @param count  The number of bytes to read in the transaction.
251   * @return Transfer Aborted... false for success, true for aborted.
252   */
253  public boolean readOnly(byte[] buffer, int count) {
254    if (count < 1) {
255      throw new BoundaryException("Value must be at least 1, " + count + " given");
256    }
257
258    if (buffer == null) {
259      throw new NullPointerException("Null return buffer was given");
260    }
261
262    ByteBuffer dataReceivedBuffer = ByteBuffer.allocateDirect(count);
263
264    int retVal = I2CJNI.i2CRead((byte) m_port.value, (byte) m_deviceAddress, dataReceivedBuffer,
265                                (byte) count);
266    dataReceivedBuffer.get(buffer);
267    return retVal < count;
268  }
269
270  /**
271   * Execute a read only transaction with the device.
272   *
273   * <p>Read bytes from a device. This method does not write any data to prompt the device.
274   *
275   * @param buffer A pointer to the array of bytes to store the data read from the device. Must be
276   *               created using ByteBuffer.allocateDirect().
277   * @param count  The number of bytes to read in the transaction.
278   * @return Transfer Aborted... false for success, true for aborted.
279   */
280  public boolean readOnly(ByteBuffer buffer, int count) {
281    if (count < 1) {
282      throw new BoundaryException("Value must be at least 1, " + count
283          + " given");
284    }
285
286    if (!buffer.isDirect()) {
287      throw new IllegalArgumentException("must be a direct buffer");
288    }
289    if (buffer.capacity() < count) {
290      throw new IllegalArgumentException("buffer is too small, must be at least " + count);
291    }
292
293    return I2CJNI.i2CRead((byte) m_port.value, (byte) m_deviceAddress, buffer, (byte) count)
294        < count;
295  }
296
297  /*
298   * Send a broadcast write to all devices on the I2C bus.
299   *
300   * <p>This is not currently implemented!
301   *
302   * @param registerAddress The register to write on all devices on the bus.
303   * @param data            The value to write to the devices.
304   */
305  // public void broadcast(int registerAddress, int data) {
306  // }
307
308  /**
309   * Verify that a device's registers contain expected values.
310   *
311   * <p>Most devices will have a set of registers that contain a known value that can be used to
312   * identify them. This allows an I2C device driver to easily verify that the device contains the
313   * expected value.
314   *
315   * @param registerAddress The base register to start reading from the device.
316   * @param count           The size of the field to be verified.
317   * @param expected        A buffer containing the values expected from the device.
318   * @return true if the sensor was verified to be connected
319   * @pre The device must support and be configured to use register auto-increment.
320   */
321  public boolean verifySensor(int registerAddress, int count,
322                              byte[] expected) {
323    // TODO: Make use of all 7 read bytes
324    ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(1);
325
326    ByteBuffer deviceData = ByteBuffer.allocateDirect(4);
327    for (int i = 0, curRegisterAddress = registerAddress;
328         i < count; i += 4, curRegisterAddress += 4) {
329      int toRead = count - i < 4 ? count - i : 4;
330      // Read the chunk of data. Return false if the sensor does not
331      // respond.
332      dataToSendBuffer.put(0, (byte) curRegisterAddress);
333      if (transaction(dataToSendBuffer, 1, deviceData, toRead)) {
334        return false;
335      }
336
337      for (byte j = 0; j < toRead; j++) {
338        if (deviceData.get(j) != expected[i + j]) {
339          return false;
340        }
341      }
342    }
343    return true;
344  }
345}