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