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