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}