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