001/*----------------------------------------------------------------------------*/
002/* Copyright (c) FIRST 2008-2012. 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/*----------------------------------------------------------------------------*/
007package edu.wpi.first.wpilibj;
008
009import java.nio.ByteOrder;
010import java.nio.IntBuffer;
011import java.nio.ByteBuffer;
012
013
014import edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary.tResourceType;
015import edu.wpi.first.wpilibj.communication.UsageReporting;
016import edu.wpi.first.wpilibj.hal.HALLibrary;
017import edu.wpi.first.wpilibj.hal.HALUtil;
018import edu.wpi.first.wpilibj.hal.I2CJNI;
019import edu.wpi.first.wpilibj.util.BoundaryException;
020
021/**
022 * I2C bus interface class.
023 *
024 * This class is intended to be used by sensor (and other I2C device) drivers.
025 * It probably should not be used directly.
026 *
027 */
028public class I2C extends SensorBase {
029        public enum Port {kOnboard(0), kMXP(1);
030                private int value;
031
032                private Port(int value){
033                        this.value = value;
034                }
035
036                public int getValue(){
037                        return this.value;
038                }
039        };
040
041        private Port m_port;
042        private int m_deviceAddress;
043
044    /**
045     * Constructor.
046     *
047         * @param port The I2C port the device is connected to.
048     * @param deviceAddress
049     *            The address of the device on the I2C bus.
050     */
051    public I2C(Port port, int deviceAddress) {
052                ByteBuffer status = ByteBuffer.allocateDirect(4);
053                status.order(ByteOrder.LITTLE_ENDIAN);
054
055        m_port = port;
056        m_deviceAddress = deviceAddress;
057
058                I2CJNI.i2CInitialize((byte)m_port.getValue(), status.asIntBuffer());
059                HALUtil.checkStatus(status.asIntBuffer());
060
061        UsageReporting.report(tResourceType.kResourceType_I2C, deviceAddress);
062    }
063
064        /**
065         * Destructor.
066         */
067        public void free() {
068        }
069
070        /**
071         * Generic transaction.
072         *
073         * This is a lower-level interface to the I2C hardware giving you more
074         * control over each transaction.
075         *
076         * @param dataToSend
077         *            Buffer of data to send as part of the transaction.
078         * @param sendSize
079         *            Number of bytes to send as part of the transaction. [0..6]
080         * @param dataReceived
081         *            Buffer to read data into.
082         * @param receiveSize
083         *            Number of bytes to read from the device. [0..7]
084         * @return Transfer Aborted... false for success, true for aborted.
085         */
086        public synchronized boolean transaction(byte[] dataToSend, int sendSize,
087                        byte[] dataReceived, int receiveSize) {
088                boolean aborted = true;
089
090                ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(sendSize);
091                dataToSendBuffer.put(dataToSend);
092                ByteBuffer dataReceivedBuffer = ByteBuffer.allocateDirect(receiveSize);
093
094                aborted = I2CJNI
095                                .i2CTransaction((byte) m_port.getValue(), (byte) m_deviceAddress,
096                                        dataToSendBuffer, (byte) sendSize,
097                                        dataReceivedBuffer, (byte) receiveSize) != 0;
098                /*if (status.get() == HALUtil.PARAMETER_OUT_OF_RANGE) {
099                        if (sendSize > 6) {
100                                throw new BoundaryException(BoundaryException.getMessage(
101                                                sendSize, 0, 6));
102                        } else if (receiveSize > 7) {
103                                throw new BoundaryException(BoundaryException.getMessage(
104                                                receiveSize, 0, 7));
105                        } else {
106                                throw new RuntimeException(
107                                                HALLibrary.PARAMETER_OUT_OF_RANGE_MESSAGE);
108                        }
109                }
110                HALUtil.checkStatus(status);*/
111                if(receiveSize > 0 && dataReceived != null)
112                {
113                        dataReceivedBuffer.get(dataReceived);
114                }
115                return aborted;
116        }
117
118        /**
119         * Attempt to address a device on the I2C bus.
120         *
121         * This allows you to figure out if there is a device on the I2C bus that
122         * responds to the address specified in the constructor.
123         *
124         * @return Transfer Aborted... false for success, true for aborted.
125         */
126        public boolean addressOnly() {
127                return transaction(null, (byte) 0, null, (byte) 0);
128        }
129
130        /**
131         * Execute a write transaction with the device.
132         *
133         * Write a single byte to a register on a device and wait until the
134         * transaction is complete.
135         *
136         * @param registerAddress
137         *            The address of the register on the device to be written.
138         * @param data
139         *            The byte to write to the register on the device.
140         */
141        public synchronized boolean write(int registerAddress, int data) {
142                byte[] buffer = new byte[2];
143                buffer[0] = (byte) registerAddress;
144                buffer[1] = (byte) data;
145
146                ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(2);
147                dataToSendBuffer.put(buffer);
148
149                return I2CJNI.i2CWrite((byte)m_port.getValue(), (byte) m_deviceAddress, dataToSendBuffer, (byte)buffer.length) < 0;
150        }
151
152        /**
153         * Execute a write transaction with the device.
154         *
155         * Write multiple bytes to a register on a device and wait until the
156         * transaction is complete.
157         *
158         * @param data
159         *            The data to write to the device.
160         */
161        public synchronized boolean writeBulk(byte[] data) {
162                ByteBuffer dataToSendBuffer = ByteBuffer.allocateDirect(data.length);
163                dataToSendBuffer.put(data);
164
165                return I2CJNI.i2CWrite((byte)m_port.getValue(), (byte) m_deviceAddress, dataToSendBuffer, (byte)data.length) < 0;
166        }
167
168        /**
169         * Execute a read transaction with the device.
170         *
171         * Read 1 to 7 bytes from a device. Most I2C devices will auto-increment the
172         * register pointer internally allowing you to read up to 7 consecutive
173         * registers on a device in a single transaction.
174         *
175         * @param registerAddress
176         *            The register to read first in the transaction.
177         * @param count
178         *            The number of bytes to read in the transaction. [1..7]
179         * @param buffer
180         *            A pointer to the array of bytes to store the data read from
181         *            the device.
182         * @return Transfer Aborted... false for success, true for aborted.
183         */
184        public boolean read(int registerAddress, int count, byte[] buffer) {
185                BoundaryException.assertWithinBounds(count, 1, 7);
186                if (buffer == null) {
187                        throw new NullPointerException("Null return buffer was given");
188                }
189                byte[] registerAddressArray = new byte[1];
190                registerAddressArray[0] = (byte) registerAddress;
191
192                return transaction(registerAddressArray, registerAddressArray.length,
193                                buffer, count);
194        }
195
196        /**
197         * Execute a read only transaction with the device.
198         *
199         * Read 1 to 7 bytes from a device. This method does not write any data to prompt
200         * the device.
201         *
202         * @param buffer
203         *            A pointer to the array of bytes to store the data read from
204         *            the device.
205         * @param count
206         *            The number of bytes to read in the transaction. [1..7]
207         * @return Transfer Aborted... false for success, true for aborted.
208         */
209        public boolean readOnly(byte[] buffer, int count) {
210                BoundaryException.assertWithinBounds(count, 1, 7);
211                if (buffer == null) {
212                        throw new NullPointerException("Null return buffer was given");
213                }
214
215                ByteBuffer dataReceivedBuffer = ByteBuffer.allocateDirect(count);
216
217                int retVal = I2CJNI.i2CRead((byte)m_port.getValue(), (byte) m_deviceAddress, dataReceivedBuffer, (byte)count);
218                dataReceivedBuffer.get(buffer);
219                return retVal < 0;
220        }
221
222        /**
223         * Send a broadcast write to all devices on the I2C bus.
224         *
225         * This is not currently implemented!
226         *
227         * @param registerAddress
228         *            The register to write on all devices on the bus.
229         * @param data
230         *            The value to write to the devices.
231         */
232        public void broadcast(int registerAddress, int data) {
233        }
234
235        /**
236         * Verify that a device's registers contain expected values.
237         *
238         * Most devices will have a set of registers that contain a known value that
239         * can be used to identify them. This allows an I2C device driver to easily
240         * verify that the device contains the expected value.
241         *
242         * @pre The device must support and be configured to use register
243         *      auto-increment.
244         *
245         * @param registerAddress
246         *            The base register to start reading from the device.
247         * @param count
248         *            The size of the field to be verified.
249         * @param expected
250         *            A buffer containing the values expected from the device.
251         * @return true if the sensor was verified to be connected
252         */
253        public boolean verifySensor(int registerAddress, int count, byte[] expected) {
254                // TODO: Make use of all 7 read bytes
255                byte[] deviceData = new byte[4];
256                for (int i = 0, curRegisterAddress = registerAddress; i < count; i += 4, curRegisterAddress += 4) {
257                        int toRead = count - i < 4 ? count - i : 4;
258                        // Read the chunk of data. Return false if the sensor does not
259                        // respond.
260                        if (read(curRegisterAddress, toRead, deviceData)) {
261                                return false;
262                        }
263
264                        for (byte j = 0; j < toRead; j++) {
265                                if (deviceData[j] != expected[i + j]) {
266                                        return false;
267                                }
268                        }
269                }
270                return true;
271        }
272}