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}