/*
* Copyright 2016 Cel Skeggs
*
* This file is part of the CCRE, the Common Chicken Runtime Engine.
*
* The CCRE is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the CCRE. If not, see <http://www.gnu.org/licenses/>.
*/
package ccre.frc;
import java.io.IOException;
import java.nio.ByteBuffer;
import edu.wpi.first.wpilibj.hal.I2CJNI;
class DirectI2C {
public static final byte PORT_ONBOARD = 0, PORT_MXP = 1;
private static final int MAX_I2C_LENGTH = 256; // based on size of byte
private static final ThreadLocal<ByteBuffer> oneByteBuffer = new ThreadLocal<ByteBuffer>() {
protected ByteBuffer initialValue() {
return ByteBuffer.allocateDirect(1);
};
};
private static final int[] port_uses = new int[2];
private static void checkPort(byte port) {
if (port != PORT_ONBOARD && port != PORT_MXP) {
throw new IllegalArgumentException("Invalid I2C port: " + port);
}
}
public static synchronized void init(byte port) {
checkPort(port);
if (port_uses[port]++ == 0) {
I2CJNI.i2CInitialize(port);
}
}
public static synchronized void free(byte port) {
checkPort(port);
if (--port_uses[port] == 0) {
I2CJNI.i2CClose(port);
}
}
// return true on success; false on failure
public static boolean transactUnsafe(byte port, byte address, ByteBuffer send, int sendLen, ByteBuffer recv, int recvLen) {
checkPort(port);
if (!send.isDirect() || !recv.isDirect()) {
throw new IllegalArgumentException("ByteBuffer is not direct!");
}
if (sendLen > send.capacity() || recvLen > recv.capacity()) {
throw new IllegalArgumentException("Length is longer than buffer!");
}
if (sendLen >= MAX_I2C_LENGTH || recvLen >= MAX_I2C_LENGTH) {
throw new IllegalArgumentException("Attempting to send/receive too much data to I2C port: " + sendLen + "/" + recvLen + " (max is " + MAX_I2C_LENGTH + " each way)");
}
// All of the transaction, read, and write functions are thin wrappers
// around ioctl with I2C_RWDR
return I2CJNI.i2CTransaction(port, address, send, (byte) sendLen, recv, (byte) recvLen) == 0;
}
public static void transactSafe(byte port, byte address, ByteBuffer send, int sendLen, ByteBuffer recv, int recvLen) throws IOException {
if (!transactUnsafe(port, address, send, sendLen, recv, recvLen)) {
throw new IOException("I2C transaction failure with " + port + "." + address);
}
}
private static final ByteBuffer nobytes = ByteBuffer.allocateDirect(0);
// no data; just see if anything responds. false on failure/abort.
public static boolean query(byte port, byte address) {
return transactUnsafe(port, address, nobytes, 0, nobytes, 0);
}
public static boolean writeUnsafe(byte port, byte address, ByteBuffer buf, int len) {
if (!buf.isDirect()) {
throw new IllegalArgumentException("Buffer must be direct!");
}
if (buf.capacity() < len) {
throw new IllegalArgumentException("Buffer is not large enough!");
}
if (len >= MAX_I2C_LENGTH) {
throw new IllegalArgumentException("Size is too long:" + len);
}
return I2CJNI.i2CWrite(port, address, buf, (byte) len) == 0;
}
public static void writeSafe(byte port, byte address, ByteBuffer buf, int len) throws IOException {
if (!writeUnsafe(port, address, buf, len)) {
throw new IOException("I2C write failure with " + port + "." + address);
}
}
public static boolean readUnsafe(byte port, byte address, byte registerAddress, ByteBuffer buf, int len) {
if (!buf.isDirect()) {
throw new IllegalArgumentException("Buffer must be direct!");
}
if (len < 1) {
throw new IllegalArgumentException("Read transaction must read at least one byte.");
}
if (buf.capacity() < len) {
throw new IllegalArgumentException("Buffer is not large enough!");
}
if (len >= MAX_I2C_LENGTH) {
throw new IllegalArgumentException("Size is too long:" + len);
}
ByteBuffer send = oneByteBuffer.get();
send.clear();
send.put(registerAddress);
return transactUnsafe(port, address, send, 1, buf, len);
}
public static void readSafe(byte port, byte address, byte registerAddress, ByteBuffer buf, int len) throws IOException {
if (!readUnsafe(port, address, registerAddress, buf, len)) {
throw new IOException("I2C read failure with " + port + "." + address);
}
}
public static boolean readOnlyUnsafe(byte port, byte address, ByteBuffer buf, int len) {
if (!buf.isDirect()) {
throw new IllegalArgumentException("Buffer must be direct!");
}
if (len < 1) {
throw new IllegalArgumentException("Read transaction must read at least one byte.");
}
if (buf.capacity() < len) {
throw new IllegalArgumentException("Buffer is not large enough!");
}
if (len >= MAX_I2C_LENGTH) {
throw new IllegalArgumentException("Size is too long:" + len);
}
return I2CJNI.i2CRead(port, address, buf, (byte) len) == 0;
}
public static void readOnlySafe(byte port, byte address, ByteBuffer buf, int len) throws IOException {
if (!readOnlyUnsafe(port, address, buf, len)) {
throw new IOException("I2C read failure with " + port + "." + address);
}
}
}