001/*----------------------------------------------------------------------------*/ 002/* Copyright (c) FIRST 2008-2017. 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 017/** 018 * I2C bus interface class. 019 * 020 * <p>This class is intended to be used by sensor (and other I2C device) drivers. It probably should 021 * not be used directly. 022 */ 023public class I2C extends SensorBase { 024 public enum Port { 025 kOnboard(0), kMXP(1); 026 027 @SuppressWarnings("MemberName") 028 public final int value; 029 030 private Port(int value) { 031 this.value = value; 032 } 033 } 034 035 private final Port m_port; 036 private final int m_deviceAddress; 037 038 /** 039 * Constructor. 040 * 041 * @param port The I2C port the device is connected to. 042 * @param deviceAddress The address of the device on the I2C bus. 043 */ 044 public I2C(Port port, int deviceAddress) { 045 m_port = port; 046 m_deviceAddress = deviceAddress; 047 048 I2CJNI.i2CInitialize((byte) port.value); 049 050 HAL.report(tResourceType.kResourceType_I2C, deviceAddress); 051 } 052 053 /** 054 * Destructor. 055 */ 056 public void free() { 057 } 058 059 /** 060 * Generic transaction. 061 * 062 * <p>This is a lower-level interface to the I2C hardware giving you more control over each 063 * transaction. 064 * 065 * @param dataToSend Buffer of data to send as part of the transaction. 066 * @param sendSize Number of bytes to send as part of the transaction. 067 * @param dataReceived Buffer to read data into. 068 * @param receiveSize Number of bytes to read from the device. 069 * @return Transfer Aborted... false for success, true for aborted. 070 */ 071 public synchronized boolean transaction(byte[] dataToSend, int sendSize, 072 byte[] dataReceived, int receiveSize) { 073 final int status; 074 075 ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(sendSize); 076 if (sendSize > 0 && dataToSend != null) { 077 dataToSendBuffer.put(dataToSend); 078 } 079 ByteBuffer dataReceivedBuffer = ByteBuffer.allocateDirect(receiveSize); 080 081 status = I2CJNI.i2CTransaction((byte) m_port.value, (byte) m_deviceAddress, dataToSendBuffer, 082 (byte) sendSize, dataReceivedBuffer, (byte) receiveSize); 083 if (receiveSize > 0 && dataReceived != null) { 084 dataReceivedBuffer.get(dataReceived); 085 } 086 return status < receiveSize; 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. 094 * 095 * @param dataToSend Buffer of data to send as part of the transaction. Must be allocated using 096 * ByteBuffer.allocateDirect(). 097 * @param sendSize Number of bytes to send as part of the transaction. 098 * @param dataReceived Buffer to read data into. Must be allocated using {@link 099 * ByteBuffer#allocateDirect(int)}. 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(ByteBuffer dataToSend, int sendSize, 104 ByteBuffer dataReceived, int receiveSize) { 105 if (!dataToSend.isDirect()) { 106 throw new IllegalArgumentException("dataToSend must be a direct buffer"); 107 } 108 if (dataToSend.capacity() < sendSize) { 109 throw new IllegalArgumentException("dataToSend is too small, must be at least " + sendSize); 110 } 111 if (!dataReceived.isDirect()) { 112 throw new IllegalArgumentException("dataReceived must be a direct buffer"); 113 } 114 if (dataReceived.capacity() < receiveSize) { 115 throw new IllegalArgumentException( 116 "dataReceived is too small, must be at least " + receiveSize); 117 } 118 119 return I2CJNI.i2CTransaction((byte) m_port.value, (byte) m_deviceAddress, dataToSend, 120 (byte) sendSize, dataReceived, (byte) receiveSize) < receiveSize; 121 } 122 123 /** 124 * Attempt to address a device on the I2C bus. 125 * 126 * <p>This allows you to figure out if there is a device on the I2C bus that responds to the 127 * address specified in the constructor. 128 * 129 * @return Transfer Aborted... false for success, true for aborted. 130 */ 131 public boolean addressOnly() { 132 return transaction((byte[]) null, (byte) 0, null, (byte) 0); 133 } 134 135 /** 136 * Execute a write transaction with the device. 137 * 138 * <p>Write a single byte to a register on a device and wait until the transaction is complete. 139 * 140 * @param registerAddress The address of the register on the device to be written. 141 * @param data The byte to write to the register on the device. 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 148 ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(2); 149 dataToSendBuffer.put(buffer); 150 151 return I2CJNI.i2CWrite((byte) m_port.value, (byte) m_deviceAddress, dataToSendBuffer, 152 (byte) buffer.length) < buffer.length; 153 } 154 155 /** 156 * Execute a write transaction with the device. 157 * 158 * <p>Write multiple bytes to a register on a device and wait until the transaction is complete. 159 * 160 * @param data The data to write to the device. 161 */ 162 public synchronized boolean writeBulk(byte[] data) { 163 ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(data.length); 164 dataToSendBuffer.put(data); 165 166 return I2CJNI.i2CWrite((byte) m_port.value, (byte) m_deviceAddress, dataToSendBuffer, 167 (byte) data.length) < data.length; 168 } 169 170 /** 171 * Execute a write transaction with the device. 172 * 173 * <p>Write multiple bytes to a register on a device and wait until the transaction is complete. 174 * 175 * @param data The data to write to the device. Must be created using ByteBuffer.allocateDirect(). 176 */ 177 public synchronized boolean writeBulk(ByteBuffer data, int size) { 178 if (!data.isDirect()) { 179 throw new IllegalArgumentException("must be a direct buffer"); 180 } 181 if (data.capacity() < size) { 182 throw new IllegalArgumentException( 183 "buffer is too small, must be at least " + size); 184 } 185 186 return I2CJNI.i2CWrite((byte) m_port.value, (byte) m_deviceAddress, data, (byte) size) < size; 187 } 188 189 /** 190 * Execute a read transaction with the device. 191 * 192 * <p>Read bytes from a device. Most I2C devices will auto-increment the register pointer 193 * internally allowing you to read consecutive registers on a device in a single transaction. 194 * 195 * @param registerAddress The register to read first in the transaction. 196 * @param count The number of bytes to read in the transaction. 197 * @param buffer A pointer to the array of bytes to store the data read from the device. 198 * @return Transfer Aborted... false for success, true for aborted. 199 */ 200 public boolean read(int registerAddress, int count, byte[] buffer) { 201 if (count < 1) { 202 throw new BoundaryException("Value must be at least 1, " + count + " given"); 203 } 204 205 if (buffer == null) { 206 throw new NullPointerException("Null return buffer was given"); 207 } 208 byte[] registerAddressArray = new byte[1]; 209 registerAddressArray[0] = (byte) registerAddress; 210 211 return transaction(registerAddressArray, registerAddressArray.length, buffer, count); 212 } 213 214 /** 215 * Execute a read transaction with the device. 216 * 217 * <p>Read bytes from a device. Most I2C devices will auto-increment the register pointer 218 * internally allowing you to read consecutive registers on a device in a single transaction. 219 * 220 * @param registerAddress The register to read first in the transaction. 221 * @param count The number of bytes to read in the transaction. 222 * @param buffer A buffer to store the data read from the device. Must be created using 223 * ByteBuffer.allocateDirect(). 224 * @return Transfer Aborted... false for success, true for aborted. 225 */ 226 public boolean read(int registerAddress, int count, ByteBuffer buffer) { 227 if (count < 1) { 228 throw new BoundaryException("Value must be at least 1, " + count + " given"); 229 } 230 231 if (!buffer.isDirect()) { 232 throw new IllegalArgumentException("must be a direct buffer"); 233 } 234 if (buffer.capacity() < count) { 235 throw new IllegalArgumentException("buffer is too small, must be at least " + count); 236 } 237 238 ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(1); 239 dataToSendBuffer.put(0, (byte) registerAddress); 240 241 return transaction(dataToSendBuffer, 1, buffer, count); 242 } 243 244 /** 245 * Execute a read only transaction with the device. 246 * 247 * <p>Read bytes from a device. This method does not write any data to prompt the device. 248 * 249 * @param buffer A pointer to the array of bytes to store the data read from the device. 250 * @param count The number of bytes to read in the transaction. 251 * @return Transfer Aborted... false for success, true for aborted. 252 */ 253 public boolean readOnly(byte[] buffer, int count) { 254 if (count < 1) { 255 throw new BoundaryException("Value must be at least 1, " + count + " given"); 256 } 257 258 if (buffer == null) { 259 throw new NullPointerException("Null return buffer was given"); 260 } 261 262 ByteBuffer dataReceivedBuffer = ByteBuffer.allocateDirect(count); 263 264 int retVal = I2CJNI.i2CRead((byte) m_port.value, (byte) m_deviceAddress, dataReceivedBuffer, 265 (byte) count); 266 dataReceivedBuffer.get(buffer); 267 return retVal < count; 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. Must be 276 * created using ByteBuffer.allocateDirect(). 277 * @param count The number of bytes to read in the transaction. 278 * @return Transfer Aborted... false for success, true for aborted. 279 */ 280 public boolean readOnly(ByteBuffer buffer, int count) { 281 if (count < 1) { 282 throw new BoundaryException("Value must be at least 1, " + count 283 + " given"); 284 } 285 286 if (!buffer.isDirect()) { 287 throw new IllegalArgumentException("must be a direct buffer"); 288 } 289 if (buffer.capacity() < count) { 290 throw new IllegalArgumentException("buffer is too small, must be at least " + count); 291 } 292 293 return I2CJNI.i2CRead((byte) m_port.value, (byte) m_deviceAddress, buffer, (byte) count) 294 < count; 295 } 296 297 /* 298 * Send a broadcast write to all devices on the I2C bus. 299 * 300 * <p>This is not currently implemented! 301 * 302 * @param registerAddress The register to write on all devices on the bus. 303 * @param data The value to write to the devices. 304 */ 305 // public void broadcast(int registerAddress, int data) { 306 // } 307 308 /** 309 * Verify that a device's registers contain expected values. 310 * 311 * <p>Most devices will have a set of registers that contain a known value that can be used to 312 * identify them. This allows an I2C device driver to easily verify that the device contains the 313 * expected value. 314 * 315 * @param registerAddress The base register to start reading from the device. 316 * @param count The size of the field to be verified. 317 * @param expected A buffer containing the values expected from the device. 318 * @return true if the sensor was verified to be connected 319 * @pre The device must support and be configured to use register auto-increment. 320 */ 321 public boolean verifySensor(int registerAddress, int count, 322 byte[] expected) { 323 // TODO: Make use of all 7 read bytes 324 ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(1); 325 326 ByteBuffer deviceData = ByteBuffer.allocateDirect(4); 327 for (int i = 0, curRegisterAddress = registerAddress; 328 i < count; i += 4, curRegisterAddress += 4) { 329 int toRead = count - i < 4 ? count - i : 4; 330 // Read the chunk of data. Return false if the sensor does not 331 // respond. 332 dataToSendBuffer.put(0, (byte) curRegisterAddress); 333 if (transaction(dataToSendBuffer, 1, deviceData, toRead)) { 334 return false; 335 } 336 337 for (byte j = 0; j < toRead; j++) { 338 if (deviceData.get(j) != expected[i + j]) { 339 return false; 340 } 341 } 342 } 343 return true; 344 } 345}