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