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