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 public enum Port { 027 kOnboard(0), 028 kMXP(1); 029 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 if (port == I2C.Port.kOnboard) { 051 DriverStation.reportWarning( 052 "Onboard I2C port is subject to system lockups. See Known Issues page for details", 053 false); 054 } 055 056 I2CJNI.i2CInitialize((byte) port.value); 057 058 HAL.report(tResourceType.kResourceType_I2C, deviceAddress); 059 } 060 061 public int getPort() { 062 return m_port; 063 } 064 065 public int getDeviceAddress() { 066 return m_deviceAddress; 067 } 068 069 @Override 070 public void close() { 071 I2CJNI.i2CClose(m_port); 072 } 073 074 /** 075 * Generic transaction. 076 * 077 * <p>This is a lower-level interface to the I2C hardware giving you more control over each 078 * transaction. If you intend to write multiple bytes in the same transaction and do not plan to 079 * receive anything back, use writeBulk() instead. Calling this with a receiveSize of 0 will 080 * result in an error. 081 * 082 * @param dataToSend Buffer of data to send as part of the transaction. 083 * @param sendSize Number of bytes to send as part of the transaction. 084 * @param dataReceived Buffer to read data into. 085 * @param receiveSize Number of bytes to read from the device. 086 * @return Transfer Aborted... false for success, true for aborted. 087 */ 088 public synchronized boolean transaction( 089 byte[] dataToSend, int sendSize, byte[] dataReceived, int receiveSize) { 090 if (dataToSend.length < sendSize) { 091 throw new IllegalArgumentException("dataToSend is too small, must be at least " + sendSize); 092 } 093 if (dataReceived.length < receiveSize) { 094 throw new IllegalArgumentException( 095 "dataReceived is too small, must be at least " + receiveSize); 096 } 097 return I2CJNI.i2CTransactionB( 098 m_port, 099 (byte) m_deviceAddress, 100 dataToSend, 101 (byte) sendSize, 102 dataReceived, 103 (byte) receiveSize) 104 < 0; 105 } 106 107 /** 108 * Generic transaction. 109 * 110 * <p>This is a lower-level interface to the I2C hardware giving you more control over each 111 * transaction. 112 * 113 * @param dataToSend Buffer of data to send as part of the transaction. 114 * @param sendSize Number of bytes to send as part of the transaction. 115 * @param dataReceived Buffer to read data into. 116 * @param receiveSize Number of bytes to read from the device. 117 * @return Transfer Aborted... false for success, true for aborted. 118 */ 119 public synchronized boolean transaction( 120 ByteBuffer dataToSend, int sendSize, ByteBuffer dataReceived, int receiveSize) { 121 if (dataToSend.hasArray() && dataReceived.hasArray()) { 122 return transaction(dataToSend.array(), sendSize, dataReceived.array(), receiveSize); 123 } 124 if (!dataToSend.isDirect()) { 125 throw new IllegalArgumentException("dataToSend must be a direct buffer"); 126 } 127 if (dataToSend.capacity() < sendSize) { 128 throw new IllegalArgumentException("dataToSend is too small, must be at least " + sendSize); 129 } 130 if (!dataReceived.isDirect()) { 131 throw new IllegalArgumentException("dataReceived must be a direct buffer"); 132 } 133 if (dataReceived.capacity() < receiveSize) { 134 throw new IllegalArgumentException( 135 "dataReceived is too small, must be at least " + receiveSize); 136 } 137 138 return I2CJNI.i2CTransaction( 139 m_port, 140 (byte) m_deviceAddress, 141 dataToSend, 142 (byte) sendSize, 143 dataReceived, 144 (byte) receiveSize) 145 < 0; 146 } 147 148 /** 149 * Attempt to address a device on the I2C bus. 150 * 151 * <p>This allows you to figure out if there is a device on the I2C bus that responds to the 152 * address specified in the constructor. 153 * 154 * @return Transfer Aborted... false for success, true for aborted. 155 */ 156 public boolean addressOnly() { 157 return transaction(new byte[0], (byte) 0, new byte[0], (byte) 0); 158 } 159 160 /** 161 * Execute a write transaction with the device. 162 * 163 * <p>Write a single byte to a register on a device and wait until the transaction is complete. 164 * 165 * @param registerAddress The address of the register on the device to be written. 166 * @param data The byte to write to the register on the device. 167 * @return Transfer Aborted... false for success, true for aborted. 168 */ 169 public synchronized boolean write(int registerAddress, int data) { 170 byte[] buffer = new byte[2]; 171 buffer[0] = (byte) registerAddress; 172 buffer[1] = (byte) data; 173 return I2CJNI.i2CWriteB(m_port, (byte) m_deviceAddress, buffer, (byte) buffer.length) < 0; 174 } 175 176 /** 177 * Execute a write transaction with the device. 178 * 179 * <p>Write multiple bytes to a register on a device and wait until the transaction is complete. 180 * 181 * @param data The data to write to the device. 182 * @return Transfer Aborted... false for success, true for aborted. 183 */ 184 public synchronized boolean writeBulk(byte[] data) { 185 return writeBulk(data, data.length); 186 } 187 188 /** 189 * Execute a write transaction with the device. 190 * 191 * <p>Write multiple bytes to a register on a device and wait until the transaction is complete. 192 * 193 * @param data The data to write to the device. 194 * @param size The number of data bytes to write. 195 * @return Transfer Aborted... false for success, true for aborted. 196 */ 197 public synchronized boolean writeBulk(byte[] data, int size) { 198 if (data.length < size) { 199 throw new IllegalArgumentException("buffer is too small, must be at least " + size); 200 } 201 return I2CJNI.i2CWriteB(m_port, (byte) m_deviceAddress, data, (byte) size) < 0; 202 } 203 204 /** 205 * Execute a write transaction with the device. 206 * 207 * <p>Write multiple bytes to a register on a device and wait until the transaction is complete. 208 * 209 * @param data The data to write to the device. 210 * @param size The number of data bytes to write. 211 * @return Transfer Aborted... false for success, true for aborted. 212 */ 213 public synchronized boolean writeBulk(ByteBuffer data, int size) { 214 if (data.hasArray()) { 215 return writeBulk(data.array(), size); 216 } 217 if (!data.isDirect()) { 218 throw new IllegalArgumentException("must be a direct buffer"); 219 } 220 if (data.capacity() < size) { 221 throw new IllegalArgumentException("buffer is too small, must be at least " + size); 222 } 223 224 return I2CJNI.i2CWrite(m_port, (byte) m_deviceAddress, data, (byte) size) < 0; 225 } 226 227 /** 228 * Execute a read transaction with the device. 229 * 230 * <p>Read bytes from a device. Most I2C devices will auto-increment the register pointer 231 * internally allowing you to read consecutive registers on a device in a single transaction. 232 * 233 * @param registerAddress The register to read first in the transaction. 234 * @param count The number of bytes to read in the transaction. 235 * @param buffer A pointer to the array of bytes to store the data read from the device. 236 * @return Transfer Aborted... false for success, true for aborted. 237 */ 238 public boolean read(int registerAddress, int count, byte[] buffer) { 239 requireNonNullParam(buffer, "buffer", "read"); 240 241 if (count < 1) { 242 throw new BoundaryException("Value must be at least 1, " + count + " given"); 243 } 244 if (buffer.length < count) { 245 throw new IllegalArgumentException("buffer is too small, must be at least " + count); 246 } 247 248 byte[] registerAddressArray = new byte[1]; 249 registerAddressArray[0] = (byte) registerAddress; 250 251 return transaction(registerAddressArray, registerAddressArray.length, buffer, count); 252 } 253 254 private ByteBuffer m_readDataToSendBuffer; 255 256 /** 257 * Execute a read transaction with the device. 258 * 259 * <p>Read bytes from a device. Most I2C devices will auto-increment the register pointer 260 * internally allowing you to read consecutive registers on a device in a single transaction. 261 * 262 * @param registerAddress The register to read first in the transaction. 263 * @param count The number of bytes to read in the transaction. 264 * @param buffer A buffer to store the data read from the device. 265 * @return Transfer Aborted... false for success, true for aborted. 266 */ 267 public boolean read(int registerAddress, int count, ByteBuffer buffer) { 268 if (count < 1) { 269 throw new BoundaryException("Value must be at least 1, " + count + " given"); 270 } 271 272 if (buffer.hasArray()) { 273 return read(registerAddress, count, buffer.array()); 274 } 275 276 if (!buffer.isDirect()) { 277 throw new IllegalArgumentException("must be a direct buffer"); 278 } 279 if (buffer.capacity() < count) { 280 throw new IllegalArgumentException("buffer is too small, must be at least " + count); 281 } 282 283 synchronized (this) { 284 if (m_readDataToSendBuffer == null) { 285 m_readDataToSendBuffer = ByteBuffer.allocateDirect(1); 286 } 287 m_readDataToSendBuffer.put(0, (byte) registerAddress); 288 289 return transaction(m_readDataToSendBuffer, 1, buffer, count); 290 } 291 } 292 293 /** 294 * Execute a read only transaction with the device. 295 * 296 * <p>Read bytes from a device. This method does not write any data to prompt the device. 297 * 298 * @param buffer A pointer to the array of bytes to store the data read from the device. 299 * @param count The number of bytes to read in the transaction. 300 * @return Transfer Aborted... false for success, true for aborted. 301 */ 302 public boolean readOnly(byte[] buffer, int count) { 303 requireNonNullParam(buffer, "buffer", "readOnly"); 304 if (count < 1) { 305 throw new BoundaryException("Value must be at least 1, " + count + " given"); 306 } 307 if (buffer.length < count) { 308 throw new IllegalArgumentException("buffer is too small, must be at least " + count); 309 } 310 311 return I2CJNI.i2CReadB(m_port, (byte) m_deviceAddress, buffer, (byte) count) < 0; 312 } 313 314 /** 315 * Execute a read only transaction with the device. 316 * 317 * <p>Read bytes from a device. This method does not write any data to prompt the device. 318 * 319 * @param buffer A pointer to the array of bytes to store the data read from the device. 320 * @param count The number of bytes to read in the transaction. 321 * @return Transfer Aborted... false for success, true for aborted. 322 */ 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}