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  public enum Port {
027    kOnboard(0),
028    kMXP(1);
029
030    public final int value;
031
032    Port(int value) {
033      this.value = value;
034    }
035  }
036
037  private final int m_port;
038  private final int m_deviceAddress;
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    if (port == I2C.Port.kOnboard) {
051      DriverStation.reportWarning(
052          "Onboard I2C port is subject to system lockups. See Known Issues page for details",
053          false);
054    }
055
056    I2CJNI.i2CInitialize((byte) port.value);
057
058    HAL.report(tResourceType.kResourceType_I2C, deviceAddress);
059  }
060
061  public int getPort() {
062    return m_port;
063  }
064
065  public int getDeviceAddress() {
066    return m_deviceAddress;
067  }
068
069  @Override
070  public void close() {
071    I2CJNI.i2CClose(m_port);
072  }
073
074  /**
075   * Generic transaction.
076   *
077   * <p>This is a lower-level interface to the I2C hardware giving you more control over each
078   * transaction. If you intend to write multiple bytes in the same transaction and do not plan to
079   * receive anything back, use writeBulk() instead. Calling this with a receiveSize of 0 will
080   * result in an error.
081   *
082   * @param dataToSend Buffer of data to send as part of the transaction.
083   * @param sendSize Number of bytes to send as part of the transaction.
084   * @param dataReceived Buffer to read data into.
085   * @param receiveSize Number of bytes to read from the device.
086   * @return Transfer Aborted... false for success, true for aborted.
087   */
088  public synchronized boolean transaction(
089      byte[] dataToSend, int sendSize, byte[] dataReceived, int receiveSize) {
090    if (dataToSend.length < sendSize) {
091      throw new IllegalArgumentException("dataToSend is too small, must be at least " + sendSize);
092    }
093    if (dataReceived.length < receiveSize) {
094      throw new IllegalArgumentException(
095          "dataReceived is too small, must be at least " + receiveSize);
096    }
097    return I2CJNI.i2CTransactionB(
098            m_port,
099            (byte) m_deviceAddress,
100            dataToSend,
101            (byte) sendSize,
102            dataReceived,
103            (byte) receiveSize)
104        < 0;
105  }
106
107  /**
108   * Generic transaction.
109   *
110   * <p>This is a lower-level interface to the I2C hardware giving you more control over each
111   * transaction.
112   *
113   * @param dataToSend Buffer of data to send as part of the transaction.
114   * @param sendSize Number of bytes to send as part of the transaction.
115   * @param dataReceived Buffer to read data into.
116   * @param receiveSize Number of bytes to read from the device.
117   * @return Transfer Aborted... false for success, true for aborted.
118   */
119  public synchronized boolean transaction(
120      ByteBuffer dataToSend, int sendSize, ByteBuffer dataReceived, int receiveSize) {
121    if (dataToSend.hasArray() && dataReceived.hasArray()) {
122      return transaction(dataToSend.array(), sendSize, dataReceived.array(), receiveSize);
123    }
124    if (!dataToSend.isDirect()) {
125      throw new IllegalArgumentException("dataToSend must be a direct buffer");
126    }
127    if (dataToSend.capacity() < sendSize) {
128      throw new IllegalArgumentException("dataToSend is too small, must be at least " + sendSize);
129    }
130    if (!dataReceived.isDirect()) {
131      throw new IllegalArgumentException("dataReceived must be a direct buffer");
132    }
133    if (dataReceived.capacity() < receiveSize) {
134      throw new IllegalArgumentException(
135          "dataReceived is too small, must be at least " + receiveSize);
136    }
137
138    return I2CJNI.i2CTransaction(
139            m_port,
140            (byte) m_deviceAddress,
141            dataToSend,
142            (byte) sendSize,
143            dataReceived,
144            (byte) receiveSize)
145        < 0;
146  }
147
148  /**
149   * Attempt to address a device on the I2C bus.
150   *
151   * <p>This allows you to figure out if there is a device on the I2C bus that responds to the
152   * address specified in the constructor.
153   *
154   * @return Transfer Aborted... false for success, true for aborted.
155   */
156  public boolean addressOnly() {
157    return transaction(new byte[0], (byte) 0, new byte[0], (byte) 0);
158  }
159
160  /**
161   * Execute a write transaction with the device.
162   *
163   * <p>Write a single byte to a register on a device and wait until the transaction is complete.
164   *
165   * @param registerAddress The address of the register on the device to be written.
166   * @param data The byte to write to the register on the device.
167   * @return Transfer Aborted... false for success, true for aborted.
168   */
169  public synchronized boolean write(int registerAddress, int data) {
170    byte[] buffer = new byte[2];
171    buffer[0] = (byte) registerAddress;
172    buffer[1] = (byte) data;
173    return I2CJNI.i2CWriteB(m_port, (byte) m_deviceAddress, buffer, (byte) buffer.length) < 0;
174  }
175
176  /**
177   * Execute a write transaction with the device.
178   *
179   * <p>Write multiple bytes to a register on a device and wait until the transaction is complete.
180   *
181   * @param data The data to write to the device.
182   * @return Transfer Aborted... false for success, true for aborted.
183   */
184  public synchronized boolean writeBulk(byte[] data) {
185    return writeBulk(data, data.length);
186  }
187
188  /**
189   * Execute a write transaction with the device.
190   *
191   * <p>Write multiple bytes to a register on a device and wait until the transaction is complete.
192   *
193   * @param data The data to write to the device.
194   * @param size The number of data bytes to write.
195   * @return Transfer Aborted... false for success, true for aborted.
196   */
197  public synchronized boolean writeBulk(byte[] data, int size) {
198    if (data.length < size) {
199      throw new IllegalArgumentException("buffer is too small, must be at least " + size);
200    }
201    return I2CJNI.i2CWriteB(m_port, (byte) m_deviceAddress, data, (byte) size) < 0;
202  }
203
204  /**
205   * Execute a write transaction with the device.
206   *
207   * <p>Write multiple bytes to a register on a device and wait until the transaction is complete.
208   *
209   * @param data The data to write to the device.
210   * @param size The number of data bytes to write.
211   * @return Transfer Aborted... false for success, true for aborted.
212   */
213  public synchronized boolean writeBulk(ByteBuffer data, int size) {
214    if (data.hasArray()) {
215      return writeBulk(data.array(), size);
216    }
217    if (!data.isDirect()) {
218      throw new IllegalArgumentException("must be a direct buffer");
219    }
220    if (data.capacity() < size) {
221      throw new IllegalArgumentException("buffer is too small, must be at least " + size);
222    }
223
224    return I2CJNI.i2CWrite(m_port, (byte) m_deviceAddress, data, (byte) size) < 0;
225  }
226
227  /**
228   * Execute a read transaction with the device.
229   *
230   * <p>Read bytes from a device. Most I2C devices will auto-increment the register pointer
231   * internally allowing you to read consecutive registers on a device in a single transaction.
232   *
233   * @param registerAddress The register to read first in the transaction.
234   * @param count The number of bytes to read in the transaction.
235   * @param buffer A pointer to the array of bytes to store the data read from the device.
236   * @return Transfer Aborted... false for success, true for aborted.
237   */
238  public boolean read(int registerAddress, int count, byte[] buffer) {
239    requireNonNullParam(buffer, "buffer", "read");
240
241    if (count < 1) {
242      throw new BoundaryException("Value must be at least 1, " + count + " given");
243    }
244    if (buffer.length < count) {
245      throw new IllegalArgumentException("buffer is too small, must be at least " + count);
246    }
247
248    byte[] registerAddressArray = new byte[1];
249    registerAddressArray[0] = (byte) registerAddress;
250
251    return transaction(registerAddressArray, registerAddressArray.length, buffer, count);
252  }
253
254  private ByteBuffer m_readDataToSendBuffer;
255
256  /**
257   * Execute a read transaction with the device.
258   *
259   * <p>Read bytes from a device. Most I2C devices will auto-increment the register pointer
260   * internally allowing you to read consecutive registers on a device in a single transaction.
261   *
262   * @param registerAddress The register to read first in the transaction.
263   * @param count The number of bytes to read in the transaction.
264   * @param buffer A buffer to store the data read from the device.
265   * @return Transfer Aborted... false for success, true for aborted.
266   */
267  public boolean read(int registerAddress, int count, ByteBuffer buffer) {
268    if (count < 1) {
269      throw new BoundaryException("Value must be at least 1, " + count + " given");
270    }
271
272    if (buffer.hasArray()) {
273      return read(registerAddress, count, buffer.array());
274    }
275
276    if (!buffer.isDirect()) {
277      throw new IllegalArgumentException("must be a direct buffer");
278    }
279    if (buffer.capacity() < count) {
280      throw new IllegalArgumentException("buffer is too small, must be at least " + count);
281    }
282
283    synchronized (this) {
284      if (m_readDataToSendBuffer == null) {
285        m_readDataToSendBuffer = ByteBuffer.allocateDirect(1);
286      }
287      m_readDataToSendBuffer.put(0, (byte) registerAddress);
288
289      return transaction(m_readDataToSendBuffer, 1, buffer, count);
290    }
291  }
292
293  /**
294   * Execute a read only transaction with the device.
295   *
296   * <p>Read bytes from a device. This method does not write any data to prompt the device.
297   *
298   * @param buffer A pointer to the array of bytes to store the data read from the device.
299   * @param count The number of bytes to read in the transaction.
300   * @return Transfer Aborted... false for success, true for aborted.
301   */
302  public boolean readOnly(byte[] buffer, int count) {
303    requireNonNullParam(buffer, "buffer", "readOnly");
304    if (count < 1) {
305      throw new BoundaryException("Value must be at least 1, " + count + " given");
306    }
307    if (buffer.length < count) {
308      throw new IllegalArgumentException("buffer is too small, must be at least " + count);
309    }
310
311    return I2CJNI.i2CReadB(m_port, (byte) m_deviceAddress, buffer, (byte) count) < 0;
312  }
313
314  /**
315   * Execute a read only transaction with the device.
316   *
317   * <p>Read bytes from a device. This method does not write any data to prompt the device.
318   *
319   * @param buffer A pointer to the array of bytes to store the data read from the device.
320   * @param count The number of bytes to read in the transaction.
321   * @return Transfer Aborted... false for success, true for aborted.
322   */
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 = Math.min(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}