diff --git a/pi4j-core/src/main/java/com/pi4j/io/SerialCircuitIO.java b/pi4j-core/src/main/java/com/pi4j/io/SerialCircuitIO.java new file mode 100644 index 00000000..8c49b441 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/io/SerialCircuitIO.java @@ -0,0 +1,77 @@ +package com.pi4j.io; + +/** + * A shared interface for SPI and I2C serial communication, centered around the + * "writeThenRead" method that performs "connected" write and read operations. + * + *

For SPI, this means that the chip select keeps active between the write and + * the read operation. + * + *

For I2C, this means that the operation is treated as an atomic + * multibyte-register read. + * + *

If this strong connection between write and read is not intended, please use + * separate write and read calls. + * + *

The write and read calls here delegate to writeThenRead with the write or + * read buffer null in order to require only one native implementation for + * all cases. + */ +public interface SerialCircuitIO extends AutoCloseable { + + /** Reads the full data array. Returns the size of the buffer for historic reasons. */ + default int read(byte[] data) { + return read(data, 0, data.length); + } + + /** + * Read 'length' bytes into 'buffer' at offset 'offset'. + * Returns 'length' for historical reasons. + */ + default int read(byte[] buffer, int offset, int length) { + writeThenRead(null, 0, 0, 0, buffer, offset, length); + return length; + } + + /** Writes the full byte array. Returns the buffer size for historical reasons. */ + default int write(byte[] buffer) { + return write(buffer, 0, buffer.length); + } + + /** + * Writes 'length' bytes from the 'buffer' at offset 'offset'. + * Returns 'length' for historical reasons. + */ + default int write(byte[] buffer, int offset, int length) { + writeThenRead(buffer, offset, length, 0, null, 0, 0); + return length; + } + + default void writeThenRead(byte[] writeBuffer, byte[] readBuffer) { + writeThenRead(writeBuffer, 0, writeBuffer.length, 0, readBuffer, 0, readBuffer.length); + } + + /** + * This function writes bytes to the device and then reads bytes from the device. + * Write data is taken from the 'writeBuffer' byte array from the given 'writeOffset' index to + * the specified 'writeLength' (number of bytes). + * + *

Data read back from the device is then copied to the 'readBuffer' byte array starting + * at the given 'readOffset' using the 'readLength' (number of bytes). + * The 'buffer' and 'read' byte array must be at least the size of their defined 'length' + 'offset'. + *

+ * In addition. both the write and read operation can provide a configurable delay + * allowing the effected chip to complete any processing. The write operation and the read + * operation each have an individual delay value. + * * + * + * @param writeBuffer The write buffer. If null, only a read operation is performed. + * @param writeOffset The start offset in the write buffer + * @param writeLength The number of bytes to write. + * @param readDelayNanos Delay between write and read in usecs + * @param readBuffer The read buffer. If null, only a write operation is performed + * @param readOffset The start offset in the read buffer. + * @param readLength The number of bytes to read. + */ + void writeThenRead(byte[] writeBuffer, int writeOffset, int writeLength, int readDelayNanos, byte[] readBuffer, int readOffset, int readLength); +} diff --git a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java index 842792f7..427666ad 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java +++ b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java @@ -29,8 +29,9 @@ import com.pi4j.io.IO; import com.pi4j.io.IODataReader; import com.pi4j.io.IODataWriter; +import com.pi4j.io.SerialCircuitIO; -import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.concurrent.Callable; /** @@ -43,7 +44,7 @@ * @version $Id: $Id */ public interface I2C - extends IO, IODataWriter, IODataReader, I2CRegisterDataReaderWriter, AutoCloseable { + extends IO, IODataWriter, IODataReader, I2CRegisterDataReaderWriter, SerialCircuitIO, AutoCloseable { /** *

close.

@@ -106,137 +107,51 @@ default int getDevice() { return device(); } - /** - * Method to perform a write of the writeBuffer, and then a read into the readBuffer - * - * @param writeBuffer the buffer to write - * @param readBuffer the buffer to read into - * - * @return the number of bytes read - * - * Note This method uses two atomic operations. start write bytes stop start read bytes stop - * To accomplish this work in a single atomic operation - * see {@link I2CRegisterDataReader#readRegister(byte[], ByteBuffer, int, int)} - * + * Method to write the writeBuffer, and then a read into the readBuffer + * in a single atomic operation. + * + * @param writeBuffer the buffer to write respecting the given length and offset + * @param writeOffset the offset of the array to write + * @param writeSize the number of bytes to write + * @param readDelayNanos delay after writing before reading; currently ignored for i2c. + * @param readBuffer the buffer into which to read the bytes + * @param readOffset the offset in the read buffer at which to insert the read bytes + * @param readSize the number of bytes to read */ - default int writeThenRead(byte[] writeBuffer, byte[] readBuffer) { - return writeThenRead(writeBuffer, writeBuffer.length, 0, readBuffer, readBuffer.length, 0); + default void writeThenRead(byte[] writeBuffer, int writeOffset, int writeSize, int readDelayNanos, byte[] readBuffer, int readOffset, int readSize) { + // Ideally, new implementations support this call directly. + // however, we can emulate it using the multi-byte register call + if (writeSize != writeBuffer.length) { + writeBuffer = Arrays.copyOfRange(writeBuffer, writeOffset, writeOffset + writeSize); + } + readRegister(writeBuffer, readBuffer, readOffset, readSize); } + // -------------------- + // Disambiguation + // --------------------- - /** - * Method to perform a write of the writeBuffer, and then a read into the readBuffer - * - * @param writeSize the number of bytes to write - * @param writeOffset the offset of the array to write - * @param writeBuffer the buffer to write respecting the given length and offset - * @param readSize the number of bytes to read - * @param readOffset the offset in the read buffer at which to insert the read bytes - * @param readBuffer the buffer into which to read the bytes - * - * @return the number of bytes read - * - * Note This method uses two atomic operations. start write bytes stop start read bytes stop - * To accomplish this work in a single atomic operation - * see {@link I2CRegisterDataReader#readRegister(byte[], ByteBuffer, int, int)} - */ - default int writeThenRead(byte[] writeBuffer, int writeSize, int writeOffset, byte[] readBuffer, int readSize, - int readOffset) { - return internalWriteThenRead(writeBuffer, writeSize, writeOffset, readBuffer, readSize, readOffset); + @Override + default int read(byte[] data) { + return SerialCircuitIO.super.read(data); } - - @Deprecated - /** - * Method to perform a write of the writeBuffer, and then a read into the readBuffer - * - * @param writeBuffer the buffer to write - * @param readBuffer the buffer to read into - * - * @return the number of bytes read - * - * Note This method uses two atomic operations. start write bytes stop start read bytes stop - * To accomplish this work in a single atomic operation - * see {@link I2CRegisterDataReader#readRegister(byte[], ByteBuffer, int, int)} - * - */ - default int writeRead(byte[] writeBuffer, byte[] readBuffer) { - return writeRead(writeBuffer, writeBuffer.length, 0, readBuffer, readBuffer.length, 0); + @Override + default int read(byte[] data, int offset, int length) { + return SerialCircuitIO.super.read(data, offset, length); } - @Deprecated - /** - * Method to perform a write of the writeBuffer, and then a read into the readBuffer - * - * @param writeSize the number of bytes to write - * @param writeOffset the offset of the array to write - * @param writeBuffer the buffer to write respecting the given length and offset - * @param readSize the number of bytes to read - * @param readOffset the offset in the read buffer at which to insert the read bytes - * @param readBuffer the buffer into which to read the bytes - * - * @return the number of bytes read - * - * Note This method uses two atomic operations. start write bytes stop start read bytes stop - * To accomplish this work in a single atomic operation - * see {@link I2CRegisterDataReader#readRegister(byte[], ByteBuffer, int, int)} - */ - default int writeRead(byte[] writeBuffer, int writeSize, int writeOffset, byte[] readBuffer, int readSize, - int readOffset) { - return internalWriteThenRead(writeBuffer, writeSize, writeOffset, readBuffer, readSize, readOffset); + @Override + default int write(byte[] data) { + return SerialCircuitIO.super.write(data); } - /** - * Method to perform a write of the given buffer, and then a read into the given buffer - * - * @param writeSize the number of bytes to write - * @param writeOffset the offset of the array to write - * @param writeBuffer the buffer to write respecting the given length and offset - * @param readSize the number of bytes to read - * @param readOffset the offset in the read buffer at which to insert the read bytes - * @param readBuffer the buffer into which to read the bytes - * - * @return the number of bytes read - * - * Note This method uses two atomic operations. start write bytes stop start read bytes stop - * To accomplish this work in a single atomic operation - * see {@link I2CRegisterDataReader#readRegister(byte[], ByteBuffer, int, int)} - */ - default int internalWriteThenRead(byte[] writeBuffer, int writeSize, int writeOffset, byte[] readBuffer, int readSize, - int readOffset) { - - // Check bounds for writeBuffer - if (writeOffset < 0) { - throw new IndexOutOfBoundsException("Write offset cannot be negative!"); - } - if (writeOffset + writeSize > writeBuffer.length) { - throw new IndexOutOfBoundsException( - String.format("Write operation out of bounds. Write buffer length is %d. Yet write offset + size is=%d", - writeBuffer.length, writeOffset + writeSize)); - } - - // Check bounds for readBuffer - if (readOffset < 0) { - throw new IndexOutOfBoundsException("Read offset cannot be negative!"); - } - if (readOffset + readSize > readBuffer.length) { - throw new IndexOutOfBoundsException( - String.format("Read operation out of bounds. Read buffer length is %d. Yet read offset + size is=%d", - readBuffer.length, readOffset + readSize)); - } - - return execute(() -> { - int written = write(writeBuffer, writeOffset, writeSize); - if (written != writeSize) { - throw new IllegalStateException( - "Expected to write " + writeSize + " bytes but only wrote " + written + " bytes"); - } - return read(readBuffer, readOffset, readSize); - }); + @Override + default int write(byte[] data, int offset, int length) { + return SerialCircuitIO.super.write(data, offset, length); } - /** * Get an encapsulated interface for reading and writing to a specific I2C device register * diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java b/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java index 9ce41606..6eeed798 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java @@ -30,6 +30,7 @@ import com.pi4j.io.IO; import com.pi4j.io.IODataReader; import com.pi4j.io.IODataWriter; +import com.pi4j.io.SerialCircuitIO; import java.nio.ByteBuffer; @@ -39,7 +40,7 @@ * @author Robert Savage (http://www.savagehomeautomation.com) * @version $Id: $Id */ -public interface Spi extends IO, AutoCloseable, IODataWriter, IODataReader { +public interface Spi extends IO, AutoCloseable, IODataWriter, IODataReader, SerialCircuitIO { /** * Constant DEFAULT_BUS */ @@ -384,6 +385,30 @@ default int transfer(ByteBuffer buffer, int offset, int length) { return length; } + // -------------------- + // Disambiguation + // --------------------- + + @Override + default int read(byte[] data) { + return SerialCircuitIO.super.read(data); + } + + @Override + default int read(byte[] data, int offset, int length) { + return SerialCircuitIO.super.read(data, offset, length); + } + + @Override + default int write(byte[] data) { + return SerialCircuitIO.super.write(data); + } + + @Override + default int write(byte[] data, int offset, int length) { + return SerialCircuitIO.super.write(data, offset, length); + } + // ------------------------------------------------------------------------------------ // writeThenRead // ------------------------------------------------------------------------------------ @@ -397,21 +422,7 @@ default int transfer(ByteBuffer buffer, int offset, int length) { * @param read Buffer to contain read data */ default void writeThenRead(byte[] write, byte[] read) { - writeThenRead(write, 0, write.length, (short) 0, read, 0, read.length, (short) 0); - } - - /** - * This function writes bytes to the SPI device and then reads bytes from the device - * Write data is taken from the 'buffer' byte array, data - * read back from the SPI device is then copied to the 'read' byte array - * - * @param write the array of bytes to write to the SPI device - * @param writeLength Number bytes written from buffer to the SPI - * @param read Buffer to contain read data - * @param readlength the number of bytes to read (read & read)) - */ - default void writeThenRead(byte[] write, int writeLength, byte[] read, int readlength) { - writeThenRead(write, 0, writeLength, (short) 0, read, 0, readlength, (short) 0); + writeThenRead(write, 0, write.length, (short) 0, read, 0, read.length); } @@ -427,22 +438,20 @@ default void writeThenRead(byte[] write, int writeLength, byte[] read, int readl * allowing the effected SPI chip to complete any processing. The write operation and the read * operation each have an individual delay value. * - * @param write the array of bytes to write to the SPI device and to store read data - * back from the SPI device - * @param writeOffset the starting offset position in the provided buffer to - * start writing to the SPI device from and the position - * used as the starting offset position to place data bytes - * read back from the SPI device. - * @param writeLength Number of bytes written from write buffer - * @param writeDelayUsecs Delay after SPI write record processed by kernel before the - * read SPI record is processed. Value in usecs. - * @param read Buffer to contain read data - * @param readOffset Offset within the read buffer to begin placing read data - * @param readLength The number of bytes to transfer/exchange (read & read)) - * @param readDelayUsecs Delay after SPI read record processed by kernel before the - * read SPI record is processed. Value in usecs. + * @param write the array of bytes to write to the SPI device and to store read data + * back from the SPI device + * @param writeOffset the starting offset position in the provided buffer to + * start writing to the SPI device from and the position + * used as the starting offset position to place data bytes + * read back from the SPI device. + * @param writeLength Number of bytes written from write buffer + * @param readDelayNanos Delay after SPI write record processed by kernel before the + * read SPI record is processed. Value in nanoseconds. + * @param read Buffer to contain read data + * @param readOffset Offset within the read buffer to begin placing read data + * @param readLength The number of bytes to transfer/exchange (read & read)) */ - default void writeThenRead(byte[] write, int writeOffset, int writeLength, short writeDelayUsecs, byte[] read, int readOffset, int readLength, short readDelayUsecs) { + default void writeThenRead(byte[] write, int writeOffset, int writeLength, int readDelayNanos, byte[] read, int readOffset, int readLength) { throw new IllegalStateException("writeThenRead Not supported in this provider. \n See https://www.pi4j.com/documentation/providers/"); } } diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java index a8e719b6..43d2e016 100644 --- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java @@ -397,7 +397,7 @@ public int write(byte[] data, int offset, int length) { } @Override - public void writeThenRead(byte[] write, int writeOffset, int writeLength, short writeDelayUsec, byte[] read, int readOffset, int readNumberOfBytes, short readDelayUsec) { + public void writeThenRead(byte[] write, int writeOffset, int writeLength, int readDelayNanos, byte[] read, int readOffset, int readNumberOfBytes) { final int firstRecord = 0; final int secondRecord = 1; final int totalRecords = 2; @@ -419,7 +419,7 @@ public void writeThenRead(byte[] write, int writeOffset, int writeLength, short txEntry.rx_buf = 0; txEntry.bits_per_word = BITS8; txEntry.speed_hz = config.baud(); - txEntry.delay_usecs = writeDelayUsec; + txEntry.delay_usecs = (short) ((readDelayNanos * 500) / 1000); txEntry.cs_change = 0; txEntry.len = writeLength; @@ -429,7 +429,7 @@ public void writeThenRead(byte[] write, int writeOffset, int writeLength, short rxEntry.tx_buf = 0; rxEntry.bits_per_word = BITS8; rxEntry.speed_hz = config.baud(); - rxEntry.delay_usecs = readDelayUsec; + rxEntry.delay_usecs = (short) (readDelayNanos / 1000); rxEntry.cs_change = 0; rxEntry.len = readNumberOfBytes;