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