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}